【问题标题】:Algorithm to calculate the start day of the next month计算下个月开始日期的算法
【发布时间】:2025-12-02 22:20:09
【问题描述】:

我正在编写一个简单的日历类。我正在尝试重载operator++ 以使用它将日历移动到下个月。但是,我找到下个月开始日期的算法不太正确。

calendar calendar::operator ++(int)
{
   int hold;
   calendar cal = *this;

   month++;
   if (month > December)
   {
      month = January;
      year++;
      if (year == 0)
         year++;
   }
   previousStartDay = startDay;
   startDay = nextStartDay;
   nextStartDay = findNextStartDay();
   return cal;
}

int calendar::findNextStartDay() const
{
   int monthLength,
       day = startDay;

   monthLength = findMonthLength(false);
   monthLength -= 28;
   day += monthLength;
   if (day > Saturday)
      day -= Saturday;
   return day;
}

一月定义为0,十二月为11,周日为0,周六为6。startDay、previousStartDay、nextStartDay、month、year都是私有类变量

当我在 2013 年测试这个时,日期直到 3 月都是正确的。此时它将下一个开始日设为星期二而不是星期一。

我也试过这个:

int calendar::findNextStartDay() const
{
   int monthLength,
       day = startDay;

   monthLength = findMonthLength(false);
   monthLength -= 28;
   day -= monthLength;
   if (day < Sunday)
      day += Saturday;
   return day;
}

但是,它也给出了相同的结果。

编辑:

我计算的是闰年。这是我来自findMonthLength() 的代码,用于确定是否存在。

if ((!(year % 4) && (year % 100)) || !(year % 400))
   monthLength = 29;
else
   monthLength = 28;

【问题讨论】:

  • 2012 年 3 月的开始日期是星期四?
  • 你说三月的结果是错误的。二月是具有奇怪闰日规则的月份。嗯
  • 对不起,我的意思是 2013 年。现在更正...
  • @brian beuning 当我找到月份长度时,我正在纠正闰年。随着它继续前进,它也会变得更远。在四月,它将下个月的开始日期定为星期日,即星期三。

标签: c++ time calendar


【解决方案1】:

问题分析

假设我们在 3 月,并且您的开始日期是正确的(星期五,5 日)。

您的 findNextStartDay 算法会发现 monthlength 等于 3 (31-28),然后日期将是 2 (8 - 6),即星期二 (2) 而不是星期一 (1)...

让我们通过运行算法来看看为什么会出错(findNextStartDay 的第一个版本):

一月:31-28 = 3,day = 2(星期二)+ 3 = 5(星期五),这是 2 月的正确开始日期。

2 月:28 - 28 = 0,day = 5(星期五)+ 0 = 5(星期五),这是 3 月的正确开始日期。

三月:31-28 = 3,天 = 5(星期五)+ 3 - 6(星期六)= 2(星期二),这是错误的四月开始日期。

错误说明

问题在于,当您从溢出的结果中减去星期六(多于星期六)时,您将在计数中剩下一天(即:您减去的天数比您想要的少)。

想想你最终得到day == 7 的情况。你会想要星期天(比星期六多一个 - 循环增加),那么你必须删除 7,而不是 6,否则你会得到星期一!

错误在于循环增量:在正确的算法中,1 超过 6(即 7)必须回到 0,2 超过 6(即 8)必须回到 1,依此类推。

在您的算法中,超过 6(即 7)的 1 会返回到 1,忽略可怜的 0(星期日),并且每次出现这种情况时都会使一周中的某一天消失。

如果你减去Saturday + 1,你会得到下个月的正确日期,以防“工作日溢出”。

错误修复

简而言之,改变这一行:

day -= Saturday;

day -= (Saturday + 1);

但是,请考虑将您的代码审查为更简洁的算法版本!

一个小技巧是使用模运算符进行循环加法:

day = ((day + monthlength) % (Saturday + 1))

【讨论】:

    【解决方案2】:

    boost 给你几个不错的examples。在这里,我根据示例中的一个实现了boost::gregorian。此代码采用年、月并打印下个月第一天的日期和星期几:

    #include <cstdlib>
    #include <boost/date_time/gregorian/gregorian.hpp>
    #include <iostream>
    #include <stdio.h>
    
    int main(int argc, char** argv) {
    
        using namespace boost::gregorian;
    
        greg_year year(1400);
        greg_month month(1);
    
        // get a month and a year from the user
        try {
          int y, m;
          std::cout << "   Enter Year(ex: 2002): ";
          std::cin >> y;
          year = greg_year(y);
          std::cout << "   Enter Month(1..12): ";
          std::cin >> m;
          month = greg_month(m);
        }
        catch(bad_year by) {
          std::cout << "Invalid Year Entered: " << by.what() << '\n'
            << "Using minimum values for month and year." << std::endl;
        }
        catch(bad_month bm) {
          std::cout << "Invalid Month Entered" << bm.what() << '\n'
            << "Using minimum value for month. " << std::endl;
        }
    
        // create date and add one day to the end of month
        date d(year, month, 1);
        d=(year,month,d.end_of_month());
        date_duration dd(1);
        d += dd;
        // print date
        std::cout << d << " " << d.day_of_week() << std::endl;
        return 0;
    }
    

    示例输出:

    输入年份(例如:2002):2013

    输入月份(1..12):3

    2013 年 4 月 1 日星期一

    运行成功(总时间:6s)


    使用std::vector:

    boost::gregorian::date d1(2013,boost::gregorian::Jan,31);
    boost::gregorian::date d2(2013,boost::gregorian::Feb,28);
    boost::gregorian::date d3(2013,boost::gregorian::Mar,31);
    
    std::vector<boost::gregorian::date > v;
    v.push_back(d1);
    v.push_back(d2);
    v.push_back(d3);
    
    boost::gregorian::date_duration duration(1);
    
    for(std::vector<boost::gregorian::date >::iterator it=v.begin();it!=v.end();it++){
        *it+=duration;
        std::cout << *it <<" "<< (*it).day_of_week() << std::endl;
    }
    

    【讨论】:

    • @kennycoc 这就是你想要达到的目标吗?
    • 这很好,但我宁愿使用我自己的算法。如果我不是刚开始编程,我可能会使用它,但我认为如果我自己先努力寻找算法,我会学得更快。
    • 如果你正在学习,那看起来不错。但我不得不说:不要在你打算与任何人分享的任何项目中自己动手。有龙……你真的很想利用一个可靠的库,比如 C++ 中的 boost 或 Javascript 中的 moment。
    【解决方案3】:

    我认为问题出在 2 月,因为这个月可以有 29 天或 28 天(取决于该年是否是 bissextile 年)。您可以为这个月创建一个 if 语句。 您可以通过使用 mod 运算符判断年份是否为 bissextile 和 if: the year % 4=! 0 比二月有 28 天,否则你有 29 天。 希望对您有所帮助!

    【讨论】:

    • 你计算闰年的方法是错误的,尽管很多人都犯了同样的错误。见en.wikipedia.org/wiki/Leap_year
    • 在我看到你的评论后,我读到了一些关于:如何确定这一年是否是闰年,我发现这种方法仅适用于公元前 44 年之前的一年(感谢公历改革)。但这并不完全正确......一开始人们不明白“4年一次”是什么意思,并且有一个时期(45BC和9BC之间)每3年有一个闰年.接下来是一段完全没有闰年的时期(公元前 8 年到公元 8 年)。
    • 是的,但如果它也能被 100 整除,那就不是。除非它也能被 400 整除。但这并不重要,因为我已经考虑过了。我更新了我的原始帖子以澄清。
    • @Tiberiu Lepadatu 我不太担心以前的日历。我宁愿我们当前的日历无限期地向后延长。至少现在是这样。
    • 我不明白一些事情:在*文章中说,如果年份可以被 100 整除但不能被 400 整除,这意味着年份不是闰年,但在另一篇*文章中说1000年是闰年。 en.wikipedia.org/wiki/1000
    【解决方案4】:

    此代码为您提供上个月的第一天和最后一天。我用它在我的应用程序中设置 TDateTimePicker 组件。

    Word Year, Month, Day;
    TDateTime datum_tdatetime = Date();
    
    // first day of actual month
    datum_tdatetime.DecodeDate(&year, &month, &day);
    day = 1;
    datum_tdatetime = EncodeDate(year, month, day);
    // last day of previous month
    datum_tdatetime -= 1;
    // first day of previous month
    datum_tdatetime.DecodeDate(&year, &month, &day);
    day = 1;
    datum_tdatetime = EncodeDate(year, month, day);
    

    【讨论】:

    • 这是干什么用的?请说明一些有用的信息。