【问题标题】:Offset for a calendar program日历程序的偏移量
【发布时间】:2013-11-05 01:12:12
【问题描述】:

该程序采用自 1753 年以来的任何用户输入年份和月份,并为其创建日历。但是,我遇到了偏移量问题,即月份开始的那一天。据我所知,这只是关闭的偏移量,其他一切似乎都很好。 这是我的代码。

#include <iostream>
#include <iomanip>
using namespace std;


int getMonth(int month);
int getYear(int year);
int computeOffset(int year, int month);
int numDaysYear(int year);
int numDaysMonth(int year, int month);
bool isLeapYear(int year);
void display(int year, int month, int offset);


/**********************************************************************
 * This function will call all the functions necessary to make a calendar
 * for any given month and year.
 ***********************************************************************/
int main()
{
   int numDays;
   int offset;
   int month;
   int year;

   month = getMonth(month);

   year = getYear(year);

   offset = computeOffset(year, month);

   display(year, month, offset);

   return 0;
}

/***********************************************************************
 * Gets the month number.
 **********************************************************************/
int getMonth(int month)
{
   cout << "Enter a month number: ";
   cin >> month;

   while ( month < 1 || month > 12)
   {
      cout << "Month must be between 1 and 12.\n"
           << "Enter a month number: ";
      cin >> month;
   }

   return month;
}

/***********************************************************************
 * Gets the year.
 **********************************************************************/
int getYear(int year)
{
   cout << "Enter year: ";
   cin >> year;

   while ( year < 1753)
   {
      cout << "Year must be 1753 or later.\n"
           << "Enter year: ";
      cin >> year;
   }
   return year;
}

/***********************************************************************
 * Computes the offset.
 **********************************************************************/

int computeOffset(int year, int month)
{
   int offset = 0;
   int count = year - 1753;
   for ( int iYear = 0; iYear < count; iYear++)
   {
      offset = ( offset + 365 + isLeapYear(year)) % 7;
   }

   for ( int iMonth = 1; iMonth < month; iMonth++)
   {
      offset = ( offset + numDaysMonth(year, iMonth)) % 7;
   }

   return offset;
}



/***********************************************************************
 * Computes the number of days in the given year.
 **********************************************************************/
int numDaysYear(int year)
{
   int daysYear = 365 + isLeapYear(year);
   return daysYear;
}

/***********************************************************************
 * Calculates the number of days in the given month.
 **********************************************************************/
int numDaysMonth(int year, int month)
{
   int daysMonth;

   if ( month == 1)
      daysMonth = 31;
   else if ( month == 2)
   {
      if (isLeapYear(year) == true)
         daysMonth = 29;
      else
         daysMonth = 28;
   }
   else if ( month == 3)
      daysMonth = 31;
   else if ( month == 4)
      daysMonth = 30;
   else if ( month == 5)
      daysMonth = 31;
   else if ( month == 6)
      daysMonth = 30;
   else if ( month == 7)
      daysMonth = 31;
   else if ( month == 8)
      daysMonth = 31;
   else if ( month == 9)
      daysMonth = 30;
   else if ( month == 10)
      daysMonth = 31;
   else if ( month == 11)
      daysMonth = 30;
   else if ( month == 12)
      daysMonth = 31;

   return daysMonth;
}

/***********************************************************************
 * Determines if given year is a leap year.
 **********************************************************************/
bool isLeapYear(int year)
{
   if ( year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
      return true;
   else
      return false;
}
 /**********************************************************************
 * Displays the calender table.
 **********************************************************************/
void display(int year, int month, int offset)
 {
    int dayOfWeek;
    int day;

    cout << endl;
    if ( month == 1)
       cout << "January";
    else if ( month == 2)
       cout << "February";
    else if ( month == 3)
       cout << "March";
    else if ( month == 4)
       cout << "April";
    else if ( month == 5)
       cout << "May";
    else if ( month == 6)
       cout << "June";
    else if ( month == 7)
       cout << "July";
    else if ( month == 8)
       cout << "August";
    else if ( month == 9)
       cout << "September";
    else if ( month == 10)
       cout << "October";
    else if ( month == 11)
       cout << "November";
    else if ( month == 12)
       cout << "December";


    cout << ", " << year << "\n";
    // Display month header
    cout << "  Su  Mo  Tu  We  Th  Fr  Sa\n";

    // Gets the correct offset width and end the line on the right
    //day of the week
    if (offset == 0)
    {
       day = 2;
       cout << setw(6);
    }
    else if (offset == 1)
    {
       day = 3;
       cout << setw(10);
    }
    else if (offset == 2)
    {
       day = 4;
       cout << setw(14);
    }
    else if (offset == 3)
    {
       day = 5;
       cout << setw(18);
    }
    else if (offset == 4)
    {
       day = 6;
       cout << setw(22);
    }
    else if (offset == 5)
    {
       day = 7;
       cout << setw(26);
    }
    else if (offset == 6)
    {
       day = 1;
       cout << setw(2);
    }
    else
       cout << "Error offset must be >= 0 and <=6\n";

    // The loop for displaying the days and ending the line in the right place
    for ( dayOfWeek = 1; dayOfWeek <= numDaysMonth(year, month); dayOfWeek++ )
    {
       cout << "  " <<  setw(2) << dayOfWeek;
       ++day;
       if (day == 8)
       {
          cout << "\n";
          day = 1;
       }
    }
    if ( day >= 2 && day <= 7)
       cout << "\n";

    return;
 }`

【问题讨论】:

  • 是否有 {year, month} 知道输出不正确?

标签: c++ calendar offset


【解决方案1】:

旧问题的新答案。新答案的理由:该领域有更好的工具和技术。

这个答案大量使用了这个free, open-source header-only library。我将从最高级别开始提出这个答案,并深入到较低级别的细节。但在任何时候,我们都不必进行详细的历法计算。 "date.h" 为我们处理。

这里是main

#include "date.h"
#include <iomanip>
#include <ostream>
#include <string>
#include <iostream>

int
main()
{
    print_calendar_year(std::cout);
}

这只是为我输出:

January 2016
  S  M  T  W  T  F  S
                 1  2
  3  4  5  6  7  8  9
 10 11 12 13 14 15 16
 17 18 19 20 21 22 23
 24 25 26 27 28 29 30
 31

February 2016
  S  M  T  W  T  F  S
     1  2  3  4  5  6
  7  8  9 10 11 12 13
 14 15 16 17 18 19 20
 21 22 23 24 25 26 27
 28 29

March 2016
  S  M  T  W  T  F  S
        1  2  3  4  5
  6  7  8  9 10 11 12
 13 14 15 16 17 18 19
 20 21 22 23 24 25 26
 27 28 29 30 31

April 2016
  S  M  T  W  T  F  S
                 1  2
  3  4  5  6  7  8  9
 10 11 12 13 14 15 16
 17 18 19 20 21 22 23
 24 25 26 27 28 29 30

May 2016
  S  M  T  W  T  F  S
  1  2  3  4  5  6  7
  8  9 10 11 12 13 14
 15 16 17 18 19 20 21
 22 23 24 25 26 27 28
 29 30 31

June 2016
  S  M  T  W  T  F  S
           1  2  3  4
  5  6  7  8  9 10 11
 12 13 14 15 16 17 18
 19 20 21 22 23 24 25
 26 27 28 29 30

July 2016
  S  M  T  W  T  F  S
                 1  2
  3  4  5  6  7  8  9
 10 11 12 13 14 15 16
 17 18 19 20 21 22 23
 24 25 26 27 28 29 30
 31

August 2016
  S  M  T  W  T  F  S
     1  2  3  4  5  6
  7  8  9 10 11 12 13
 14 15 16 17 18 19 20
 21 22 23 24 25 26 27
 28 29 30 31

September 2016
  S  M  T  W  T  F  S
              1  2  3
  4  5  6  7  8  9 10
 11 12 13 14 15 16 17
 18 19 20 21 22 23 24
 25 26 27 28 29 30

October 2016
  S  M  T  W  T  F  S
                    1
  2  3  4  5  6  7  8
  9 10 11 12 13 14 15
 16 17 18 19 20 21 22
 23 24 25 26 27 28 29
 30 31

November 2016
  S  M  T  W  T  F  S
        1  2  3  4  5
  6  7  8  9 10 11 12
 13 14 15 16 17 18 19
 20 21 22 23 24 25 26
 27 28 29 30

December 2016
  S  M  T  W  T  F  S
              1  2  3
  4  5  6  7  8  9 10
 11 12 13 14 15 16 17
 18 19 20 21 22 23 24
 25 26 27 28 29 30 31

可以打印明年的日历:

using namespace date::literals;
print_calendar_year(std::cout, 2017_y);

我将从声明开始:

这是一个类型安全系统。

文字2017_ydate::year 类型的对象,而不是简单的整数。具有表示yearmonth 的类型意味着混淆这些概念的可能性要小得多。错误往往会在编译时被发现。

print_calendar_year 很简单:

void
print_calendar_year(std::ostream& os, date::year y = current_year())
{
    using namespace date;
    for (auto ym = y/jan; ym < y/jan + years{1}; ym += months{1})
    {
        print_calendar_month(os, ym);
        os << '\n';
    }
}

表达式year/month 创建了一个名为date::year_month 的类型,它只不过是一个简单的结构{year, month}。所以这个函数简单地设置了一个循环,从 y 年的 1 月迭代到下一个 1 月,不包括下一个 1 月。这一切都非常可读。请注意,不允许使用“bare ints”。一切都有一个非整数类型。

print_calendar_month 是橡胶与道路的交汇处:

void
print_calendar_month(std::ostream& os, date::year_month ym = current_year_month())
{
    using namespace std;
    using namespace date;
    os << format("%B %Y\n", sys_days{ym/1});
    os << "  S  M  T  W  T  F  S\n";
    auto wd = unsigned{weekday{ym/1}};
    os << string(wd*3, ' ');
    auto const e = (ym/last).day();
    for (day d = 1_d; d <= e; wd = 0)
    {
        for (; wd < 7 && d <= e; ++wd, ++d)
            os << setw(3) << unsigned{d};
        os << '\n';
    }
}

os &lt;&lt; format("%B %Y\n", sys_days{ym/1}); 是打印每个月的标题(例如January 2016)。这些是类似 strftime 的格式化标志,将尊重当前全局 std::locale 的本地化设置(操作系统支持的程度)。

子表达式ym/1 创建一个类型date::year_month_day,它代表指定月份和年份的第一天。 date::year_month_day 是一个持有{year, month, day} 的简单类。

sys_days 是基于system_clockchrono::time_point,精度为daysdate::format 可以采用任何精度 system_clock time_point 并使用类似 strftime 的格式化标志对其进行格式化。如图所示,year_month_day 可以转换为sys_days。这是从{year, month, day} 字段类型到串行{count of days} 类型的转换。

os &lt;&lt; " S M T W T F S\n"; 显然会打印出日历的星期几标题。

auto wd = unsigned{weekday{ym/1}}; 查找该月第一天的星期几,并使用编码[Sun == 0, Sat == 6]weekday 转换为unsigned。 [注意: gcc 需要语法unsigned(weekday{ym/1})。它不喜欢{} for unsigned。 — 尾注]

os &lt;&lt; string(wd*3, ' '); 只是在每月第一天之前的每一天打印出 3 个空格来填充第一行。

auto const e = (ym/last).day();date::day 类型的常量,它等于今年和月份组合的月份的最后一天。

for (day d = 1_d; d &lt;= e; wd = 0)

从第 1 天循环开始,直到该月的最后一天(含),并在每次迭代时将 unsigned wd 设置回星期日的编码。

for (; wd &lt; 7 &amp;&amp; d &lt;= e; ++wd, ++d):直到你到达周末或月末,增加一周中的某一天和一个月中的某一天。

os &lt;&lt; setw(3) &lt;&lt; unsigned{d};:将月份中的日期转换为unsigned,并以 3 个空格的宽度右对齐打印出来。

os &lt;&lt; '\n';打印一周后返回。

这就是程序的大部分内容!几乎所有棘手的日历逻辑都封装在这两行代码中:

auto wd = unsigned{weekday{ym/1}};
auto const e = (ym/last).day();

为了完整起见,这里是获取当前date::year 和当前date::year_month 的函数:

date::year_month
current_year_month()
{
    using namespace std::chrono;
    using namespace date;
    year_month_day ymd = floor<days>(system_clock::now());
    return ymd.year()/ymd.month();
}

date::year
current_year()
{
    using namespace std::chrono;
    using namespace date;
    year_month_day ymd = floor<days>(system_clock::now());
    return ymd.year();
}

这两个都只是使用floor将从system_clock::now()返回的system_clock::time_point截断为days的精度,然后将天数精度time_point转换为date::year_month_day类型。然后这种类型有 yearmonth 的 getter 来挑选所需的部分日历类型。

更新

好吧,TemplateRex 在下面问了一个我一开始不想回答的问题,然后我忍不住了,因为答案突出了"date.h" 的强大功能。 ;-)

问题是:

你能像这样把日历打印成 3x4 格式吗?

 January 2016            February 2016           March 2016          
 Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
                 1  2        1  2  3  4  5  6           1  2  3  4  5
  3  4  5  6  7  8  9     7  8  9 10 11 12 13     6  7  8  9 10 11 12
 10 11 12 13 14 15 16    14 15 16 17 18 19 20    13 14 15 16 17 18 19
 17 18 19 20 21 22 23    21 22 23 24 25 26 27    20 21 22 23 24 25 26
 24 25 26 27 28 29 30    28 29                   27 28 29 30 31      
 31                                                                  

 April 2016              May 2016                June 2016           
 Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
                 1  2     1  2  3  4  5  6  7              1  2  3  4
  3  4  5  6  7  8  9     8  9 10 11 12 13 14     5  6  7  8  9 10 11
 10 11 12 13 14 15 16    15 16 17 18 19 20 21    12 13 14 15 16 17 18
 17 18 19 20 21 22 23    22 23 24 25 26 27 28    19 20 21 22 23 24 25
 24 25 26 27 28 29 30    29 30 31                26 27 28 29 30      

 July 2016               August 2016             September 2016      
 Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
                 1  2        1  2  3  4  5  6                 1  2  3
  3  4  5  6  7  8  9     7  8  9 10 11 12 13     4  5  6  7  8  9 10
 10 11 12 13 14 15 16    14 15 16 17 18 19 20    11 12 13 14 15 16 17
 17 18 19 20 21 22 23    21 22 23 24 25 26 27    18 19 20 21 22 23 24
 24 25 26 27 28 29 30    28 29 30 31             25 26 27 28 29 30   
 31                                                                  

 October 2016            November 2016           December 2016       
 Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
                    1           1  2  3  4  5                 1  2  3
  2  3  4  5  6  7  8     6  7  8  9 10 11 12     4  5  6  7  8  9 10
  9 10 11 12 13 14 15    13 14 15 16 17 18 19    11 12 13 14 15 16 17
 16 17 18 19 20 21 22    20 21 22 23 24 25 26    18 19 20 21 22 23 24
 23 24 25 26 27 28 29    27 28 29 30             25 26 27 28 29 30 31
 30 31                                                               

显然如此,因为我不打算手动输入以上所有内容! ;-)

它需要重写 print_calendar_year 并引入一些新功能,最值得注意的是:

void
print_line_of_calendar_month(std::ostream& os, date::year_month ym, unsigned line,
                             date::weekday firstdow);

此函数仅打印与year_month ym 关联的日历的一行,并且是这种 3x4 格式的核心。

我还认为让这个程序可本地化会很有趣,这样可以打印出所需的星期几,以及月份和星期几的本地化名称(与std::locale 在您的平台上允许)。

这些行编号为 [0, infinity]。第 0 行打印出月份年份,例如 January 2016。第 1 行打印出星期标题:Su Mo Tu We Th Fr Sa。然后第 [2, infinity] 行打印出月份中的日期。

为什么是无穷大?

因为不同的月份需要不同的行数,所以我希望能够告诉year/month 打印下一行,即使它不需要(因为本季度的另一个月需要它)。因此,当您要求日历打印出不需要的行时,它只会输出正确数量的 ' ' 以用于填充目的。

足够的介绍,这是功能:

void
print_line_of_calendar_month(std::ostream& os, date::year_month ym, unsigned line,
                             date::weekday firstdow)
{
    using namespace std;
    using namespace date;
    switch (line)
    {
    case 0:
        os << left << setw(21) << format(os.getloc(), " %B %Y", sys_days{ym/1}) << right;
        break;
    case 1:
        {
        auto sd = sys_days{ym/firstdow[1]};
        for (auto const esd = sd + weeks{1}; sd < esd; sd += days{1})
        {
            auto d = format(os.getloc(), "%a", sd);
            d.resize(2);
            os << ' ' << d;
        }
        break;
        }
    case 2:
        {
        auto wd = weekday{ym/1};         // This line and the next are the "offset"
        os << string((wd-firstdow).count()*3, ' '); // referred to in the question.
        auto d = 1_d;
        do
        {
            os << setw(3) << unsigned(d);
            ++d;
        } while (++wd != firstdow);
        break;
        }
    default:
        {
        unsigned index = line - 2;
        auto sd = sys_days{ym/1};
        if (weekday{sd} == firstdow)
            ++index;
        auto ymdw = ym/firstdow[index];
        if (ymdw.ok())
        {
            auto d = year_month_day{ymdw}.day();
            auto const e = (ym/last).day();
            auto wd = firstdow;
            do
            {
                os << setw(3) << unsigned(d);
            } while (++wd != firstdow && ++d <= e);
            os << string((firstdow-wd).count()*3, ' ');
        }
        else
            os << string(21, ' ');
        break;
        }
    }
}

所以打开行号[0, infinity],并且对于每个行号,做正确的事:

0. 打印出月份年份标题。

这将传递给format localeos 以获取本地化月份名称。

1. 打印出星期几标题。

这将传递给formatoslocale 以获取本地化的工作日名称,并打印前2 个字符。这(不幸的是)仅在这些是多字节字符时才大致正确,但这篇文章主要是关于日历,而不是 Unicode。

2. 打印出第一周,可能以空格为前缀。前缀的空格数是 3*(当月的第一天超过一周的第一天的天数)。然后追加天数,直到到达一周的最后一天。请注意,工作日减法始终以 7 为模,因此您不必担心星期几的底层编码。工作日形成一个圆形范围。这确实需要类似于 do-while 的内容,而不是传统的 for 在一周内循环所有天时。

3 - infinity。啊,这是有趣的部分。

"date.h" 中有一个类型叫做year_month_weekday,它是一个存储{year, month, weekday, unsigned} 的类型。您可以通过以下方式指定母亲节:5 月的第二个星期日:sun[2]/may/2016。这个表达式创建了一个结构{2016, 5, 0, 2}(粗略地说)。因此,如果switch 出现在此处,那么我们正在寻找本月和本年的[第一个,最后一个] 星期日,其中确切的索引取决于line,以及我们是否在线打印了星期日2.

另外,这个库允许使用 any 索引:

auto ymdw = ym/firstdow[index];

index 可以是 1,也可以是 57。以上行编译,不是运行时错误。

但几个月不能有 57 个星期日(或星期一或其他)!

没问题。您可以询问ym/firstdow[index] 是否为有效日期。这就是下一行的作用:

if (ymdw.ok())

如果日期有效,那么您有工作要做。否则,您只需打印出一个空白行。

如果我们有工作要做,请将 year_month_weekday 转换为 year_month_day,以便您可以从中获取月份中的日期 (d)。并找到该月的最后一天:

auto const e = (ym/last).day();

然后从一周的第一天迭代到先到者:月末或一周的最后一天。打印出每个地点的月份日期。然后,如果您没有在一周的最后一天结束,则打印空格以填充到一周的最后一天。

我们已经完成了print_line_of_calendar_month!请注意,在此级别上从未输出换行符。在这个级别上甚至没有输出月间填充。每个日历的宽度正好是 21char,并且可以打印到任意行数。

现在我们需要另一个小工具:一个日历月在开始填充空白行之前需要多少行?

unsigned
number_of_lines_calendar(date::year_month ym, date::weekday firstdow)
{
    using namespace date;
    return ceil<weeks>((weekday{ym/1} - firstdow) +
                       ((ym/last).day() - day{0})).count() + 2;
}

这是一个月的天数,加上从一周的第一天到本月的第一天的天数,再加上 2 行用于星期几标题和年份 -月标题。最后的小数周被四舍五入!

注意事项:

  1. 从一周的第一天到每月的第一天的天数就是:(weekday{ym/1} - firstdow)

  2. 每月的天数在这里编码为((ym/last).day() - day{0})。请注意,day{0} 不是有效的day,但在减法中仍然有用:day - day 给出了chrono::duration days 的结果。另一种说法是((ym/last).day() - day{1} + days{1})

  3. 请注意,此处使用ceil&lt;weeks&gt;days 的数量转换为weeks 的数量,如果转换不准确,则向上舍入到下一个weeks。 1 周 == 1 行。此汇总说明在一周的最后一天之前结束的最后一周。

现在print_calendar_year 可以用这些原语重写:

void
print_calendar_year(std::ostream& os, unsigned const cols = 3,
                    date::year const y = current_year(),
                    date::weekday const firstdow = date::sun)
{
    using namespace date;
    if (cols == 0 || 12 % cols != 0)
        throw std::runtime_error("The number of columns " + std::to_string(cols)
                                 + " must be one of [1, 2, 3, 4, 6, 12]");
    // Compute number of lines needed for each calendar month
    unsigned ml[12] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
    for (auto& m : ml)
        m = number_of_lines_calendar(y/month{m}, firstdow);
    for (auto r = 0u; r < 12/cols; ++r) // for each row
    {
        const auto lines = *std::max_element(std::begin(ml) + (r*cols),
                                             std::begin(ml) + ((r+1)*cols));
        for (auto l = 0u; l < lines; ++l) // for each line
        {
            for (auto c = 0u; c < cols; ++c) // for each column
            {
                if (c != 0)
                    os << "   ";
                print_line_of_calendar_month(os, y/month{r*cols + c+1}, l, firstdow);
            }
            os << '\n';
        }
        os << '\n';
    }
}

首先计算每个月需要多少行:

// Compute number of lines needed for each calendar month
unsigned ml[12] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
for (auto& m : ml)
    m = number_of_lines_calendar(y/month{m}, firstdow);

然后对于每个“日历行”,通过搜索 ml 的真子集来找到该行所需的行数。

然后对于每一行,对于每个“日历列”,打印出该列对应日历月的行。

在每一行之后打印一个'\n'

在每个日历行之后,打印一个'\n'

请注意,我们仍然不需要深入研究历法算术。在这个级别,我们需要知道“每周 7 天”、“每天 3 个空格”和“每个日历行 12/cols 月”。

在 macOS 上此驱动程序:

using namespace date::literals;
std::cout.imbue(std::locale("de_DE"));
print_calendar_year(std::cout, 3, 2016_y, mon);

输出:

 Januar 2016             Februar 2016            März 2016          
 Mo Di Mi Do Fr Sa So    Mo Di Mi Do Fr Sa So    Mo Di Mi Do Fr Sa So
              1  2  3     1  2  3  4  5  6  7        1  2  3  4  5  6
  4  5  6  7  8  9 10     8  9 10 11 12 13 14     7  8  9 10 11 12 13
 11 12 13 14 15 16 17    15 16 17 18 19 20 21    14 15 16 17 18 19 20
 18 19 20 21 22 23 24    22 23 24 25 26 27 28    21 22 23 24 25 26 27
 25 26 27 28 29 30 31    29                      28 29 30 31         

 April 2016              Mai 2016                Juni 2016           
 Mo Di Mi Do Fr Sa So    Mo Di Mi Do Fr Sa So    Mo Di Mi Do Fr Sa So
              1  2  3                       1           1  2  3  4  5
  4  5  6  7  8  9 10     2  3  4  5  6  7  8     6  7  8  9 10 11 12
 11 12 13 14 15 16 17     9 10 11 12 13 14 15    13 14 15 16 17 18 19
 18 19 20 21 22 23 24    16 17 18 19 20 21 22    20 21 22 23 24 25 26
 25 26 27 28 29 30       23 24 25 26 27 28 29    27 28 29 30         
                         30 31                                       

 Juli 2016               August 2016             September 2016      
 Mo Di Mi Do Fr Sa So    Mo Di Mi Do Fr Sa So    Mo Di Mi Do Fr Sa So
              1  2  3     1  2  3  4  5  6  7              1  2  3  4
  4  5  6  7  8  9 10     8  9 10 11 12 13 14     5  6  7  8  9 10 11
 11 12 13 14 15 16 17    15 16 17 18 19 20 21    12 13 14 15 16 17 18
 18 19 20 21 22 23 24    22 23 24 25 26 27 28    19 20 21 22 23 24 25
 25 26 27 28 29 30 31    29 30 31                26 27 28 29 30      

 Oktober 2016            November 2016           Dezember 2016       
 Mo Di Mi Do Fr Sa So    Mo Di Mi Do Fr Sa So    Mo Di Mi Do Fr Sa So
                 1  2        1  2  3  4  5  6              1  2  3  4
  3  4  5  6  7  8  9     7  8  9 10 11 12 13     5  6  7  8  9 10 11
 10 11 12 13 14 15 16    14 15 16 17 18 19 20    12 13 14 15 16 17 18
 17 18 19 20 21 22 23    21 22 23 24 25 26 27    19 20 21 22 23 24 25
 24 25 26 27 28 29 30    28 29 30                26 27 28 29 30 31   
 31                                                                  

您的标准可能会因您的 std::lib/OS 支持本地化的程度而异。但是现在您可以使用任何年份,使用任何年份,使用一周中的任何一天作为第一天,以月份列打印日历周,并使用任何语言环境(对语言环境的模操作系统支持)。

这是print_calendar_year(std::cout, 4, 2017_y); 的输出

 January 2017            February 2017           March 2017              April 2017          
 Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
  1  2  3  4  5  6  7              1  2  3  4              1  2  3  4                       1
  8  9 10 11 12 13 14     5  6  7  8  9 10 11     5  6  7  8  9 10 11     2  3  4  5  6  7  8
 15 16 17 18 19 20 21    12 13 14 15 16 17 18    12 13 14 15 16 17 18     9 10 11 12 13 14 15
 22 23 24 25 26 27 28    19 20 21 22 23 24 25    19 20 21 22 23 24 25    16 17 18 19 20 21 22
 29 30 31                26 27 28                26 27 28 29 30 31       23 24 25 26 27 28 29
                                                                         30                  

 May 2017                June 2017               July 2017               August 2017         
 Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
     1  2  3  4  5  6                 1  2  3                       1           1  2  3  4  5
  7  8  9 10 11 12 13     4  5  6  7  8  9 10     2  3  4  5  6  7  8     6  7  8  9 10 11 12
 14 15 16 17 18 19 20    11 12 13 14 15 16 17     9 10 11 12 13 14 15    13 14 15 16 17 18 19
 21 22 23 24 25 26 27    18 19 20 21 22 23 24    16 17 18 19 20 21 22    20 21 22 23 24 25 26
 28 29 30 31             25 26 27 28 29 30       23 24 25 26 27 28 29    27 28 29 30 31      
                                                 30 31                                       

 September 2017          October 2017            November 2017           December 2017       
 Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
                 1  2     1  2  3  4  5  6  7              1  2  3  4                    1  2
  3  4  5  6  7  8  9     8  9 10 11 12 13 14     5  6  7  8  9 10 11     3  4  5  6  7  8  9
 10 11 12 13 14 15 16    15 16 17 18 19 20 21    12 13 14 15 16 17 18    10 11 12 13 14 15 16
 17 18 19 20 21 22 23    22 23 24 25 26 27 28    19 20 21 22 23 24 25    17 18 19 20 21 22 23
 24 25 26 27 28 29 30    29 30 31                26 27 28 29 30          24 25 26 27 28 29 30
                                                                         31                  

【讨论】:

  • 是否可以选择将日历打印为 4 行 3 列,就像在 @EricNiebler 的 CppCon15 演讲中一样? (src, slides, video)
  • @TemplateRex:不,超出了这个答案的范围。然而,它不会太难做,并且 date.h 库仍然会处理所有的日历细节。我可能会从重构print_calendar_month 开始,以调用一个新函数print_line_of_calendar_month,然后在一年的几个季度中迭代,然后在输出中的行上迭代,然后在该季度的几个月中迭代。最里面的循环会调用print_line_of_calendar_month(os, ym, line)
  • @TemplateRex:好的,你抓住了我。 :-) 无法抗拒挑战。
  • 很棒的补充!
【解决方案2】:

此代码未解决您的问题(我无法确定问题所在),但将其与您的版本进行对比可能会提供信息。

#include <iomanip>
#include <iostream>
#include <string>
#include <vector>

using namespace std;


int getMonth();
int getYear();
int computeOffset(int year, int month);
int numDaysYear(int year);
int numDaysMonth(int year, int month);
bool isLeapYear(int year);
void display(int year, int month, int offset);


/**********************************************************************
 * This function will call all the functions necessary to make a calendar
 * for any given month and year.
 ***********************************************************************/
int main()
{
   int offset;
   int month;
   int year;

   month = getMonth();
   year = getYear();
   offset = computeOffset(year, month);

   display(year, month, offset);

   return 0;
}

/***********************************************************************
 * Gets the month number.
 **********************************************************************/
int getMonth()
{
   int month = 0;
   cout << "Enter a month number: ";
   cin >> month;

   while (month < 1 || month > 12)
   {
      cout << "Month must be between 1 and 12.\n"
           << "Enter a month number: ";
      cin >> month;
   }

   return month;
}

/***********************************************************************
 * Gets the year.
 **********************************************************************/
int getYear()
{
   int year = 0;
   cout << "Enter year: ";
   cin >> year;

   while ( year < 1753)
   {
      cout << "Year must be 1753 or later.\n"
           << "Enter year: ";
      cin >> year;
   }
   return year;
}

/***********************************************************************
 * Computes the offset.
 **********************************************************************/

int computeOffset(int year, int month)
{
   int offset = 0;
   int count = year - 1753;
   for (int iYear = 0; iYear < count; iYear++)
   {
      offset = (offset + 365 + isLeapYear(year)) % 7;
   }

   for (int iMonth = 1; iMonth < month; iMonth++)
   {
      offset = (offset + numDaysMonth(year, iMonth)) % 7;
   }

   return offset;
}



/***********************************************************************
 * Computes the number of days in the given year.
 **********************************************************************/
int numDaysYear(int year)
{
   return 365 + isLeapYear(year);
}

/***********************************************************************
 * Calculates the number of days in the given month.
 **********************************************************************/
int numDaysMonth(int year, int month)
{
   std::vector<int> days { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
   int daysMonth = days[month-1];

   if (month == 2 && isLeapYear(year))
   {
       daysMonth = 29;
   }

   return daysMonth;
}

/***********************************************************************
 * Determines if given year is a leap year.
 **********************************************************************/
bool isLeapYear(int year)
{
   return (year % 400 == 0) || (year % 100 != 0 && year % 4 == 0);
}
 /**********************************************************************
 * Displays the calender table.
 **********************************************************************/
void display(int year, int month, int offset)
 {
    int day;

    cout << endl;
    std::vector<std::string> mth { "January", "February", "March",
      "April", "May", "June", "July", "August", "September",
      "October", "November", "December" };
    cout << mth[month-1];

    cout << ", " << year << "\n";
    // Display month header
    cout << "  Su  Mo  Tu  We  Th  Fr  Sa\n";

    // Gets the correct offset width and end the line on the right
    // day of the week
    if (0 <= offset && offset <= 6)
    {
        day = ((offset + 1) % 7) + 1;
        cout << setw((day - 2) * 4 + 6);
    }
    else
       cout << "Error offset must be >= 0 and <=6\n";

    // The loop for displaying the days and ending the line in the right place
    for (int dayOfWeek = 1; dayOfWeek <= numDaysMonth(year, month); dayOfWeek++ )
    {
       cout << "  " <<  setw(2) << dayOfWeek;
       ++day;
       if (day == 8)
       {
          cout << "\n";
          day = 1;
       }
    }
    if (day >= 2 && day <= 7)
       cout << "\n";

    return;
 }

【讨论】:

    【解决方案3】:

    我知道这是一年多以前发布的,但我确实为未来的访问者提供了解决方案。在原始帖子的 computeOffset 函数中,该函数实际上并没有计算总共过去了多少天。你需要计算已经过去了多少年,那些年有多少天,以及那些年后几个月的天数,如果这有意义的话。这是我的工作 computeOffset 函数的代码:

    int computeOffset(int month, int year)
     {
       int numDaysYear = 0;
       int yearCounter = 0;
       int monthCounter = 0;
       int months = 1  // NOT the same at the "month" variable coming in
    
       // This loop counts how many days have passed in the full years up to the given year
       for (int iYear = 1753; iYear < year; iYear++)
       {
          numDaysYear = numDaysYear + 365;
    
          if (isLeapYear(iYear))                          
             yearCounter++;
       }
    
       // This loop counts the days in the remaining months after all the years
       for (int iMonth = 1; iMonth < month; iMonth++)
       {
          monthCounter = monthCounter + numDaysMonth(months, year);  //MONTHS not month
          months++;
       }
    
       int offset = (numDaysYear + yearCounter + monthCoutner) % 7;
    
       return offset;
    

    希望这是有道理的,如果某些变量的名称很奇怪,或者样式不是您习惯的那样,请见谅。希望这会有所帮助!

    【讨论】:

      猜你喜欢
      • 2016-12-02
      • 2019-07-02
      • 2018-11-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-26
      • 1970-01-01
      • 2015-08-12
      相关资源
      最近更新 更多