计算任意日期星期的Doomsday算法

Doomsday算法是由John Conway发明的一种简单而巧妙的方法,用于计算任意日期的星期。要了解该算法的详细原理,强烈推荐阅读S.W. Graham的文章。基本上,通过知道给定世纪的2月28日或29日(闰年)是星期几,以及这个星期几对于所有其他月份的对应关系,就可以确定任何一天的星期几。下表总结了哪些整数代表工作日,以及每个月和世纪的末日是星期几。

由于格里高利历每400年重复一次,实际上只需要记住四个世纪。例如,1955年11月5日是星期几?(更重要的是,哪部电影使用了这个日期?)

年份部分是55,日期是5,1900年的世纪末日是3。每个闰年,末日增加1。加上世纪末日 + 年份部分 + floor(年份部分 ÷ 4) % 7。对于这个例子:(3 + 55 + floor(55 ÷ 4)) % 7 = 1。由于11月的末日在5日之后,必须计算出偏移量。知道末日是7日,5日是2天之前或一周后的同一天,即12日或7日之后的5天。因此,将末日增加5,得到12。12 ÷ 7的余数是5,这是正确的星期几。

最后,需要考虑闰年,所以5 + 1 = 6,即星期六。

COleDateTime类的时间限制是从1月1日100年到12月31日9999年,CTime类的时间限制是从1月1日1970年到1月18日2038年。DoomsdayDate类可以用来找到任何范围内的星期几,包括公元前的日期。

DoomsdayDateC++

以下是DoomsdayDate类的实现,它包含了计算星期几的核心函数Weekday。

class DoomsdayDate { // ... 省略其他成员和方法 ... int Weekday(); void Print(); };

Weekday函数是Doomsday算法的核心。以下是Weekday函数的实现:

int DoomsdayDate::Weekday() { int r = -1; int x = 0, y = 0; int ddcentury = -1; int ddmonth = DoomsdayMonth(month_); if (gregorian_) { // 格里高利历 int century = year_ - (year_ % 100); ddcentury = DoomsdayCentury(century); if (ddcentury < 0) return -1; if (ddmonth < 0) return -1; if (ddmonth > day_) { weekday_ = (7 - ((ddmonth - day_) % 7) + ddmonth); } else { weekday_ = day_; } x = (weekday_ - ddmonth); x %= 7; y = ddcentury + (year_ - century) + (floor((year_ - century) / 4)); y %= 7; r = (x + y) % 7; } else if (!ad_) { // 公元前 -> 公元 儒略历 int dd = -1; if (year_ > 699) { dd = (year_ - (year_ % 700) + 701) - year_; } else { dd = (year_ - (year_ % 28) + 29) - year_; } if (dd > 0) { ddcentury = (((dd - (dd % 100)) / 100) * 6) % 7; x = ((dd % 100) % 7) + (int)floor((dd % 100) / 4) % 7; if (ddmonth > day_) y = ddmonth + day_; else y = day_ - ddmonth; y %= 7; x = ddcentury + x; x %= 7; r = (x + y) % 7; } } else { // 儒略历 ddcentury = (((year_ - (year_ % 100)) / 100) * 6) % 7; x = ((year_ % 100) % 7) + (int)floor((year_ % 100) / 4) % 7; if (ddmonth > day_) y = ddmonth + day_; else y = day_ - ddmonth; y %= 7; x = ddcentury + x; x %= 7; r = (x + y) % 7; } weekday_ = r; return weekday_; }

DoomsdayDate类的使用示例:

DoomsdayDate date(true); // 默认使用格里高利历 date.Set(11, 5, 1955); // 设置日期为1955年11月5日 date.Print(); // 打印日期和星期

默认构造函数接受一个布尔值,指示是否在1582年10月15日之前使用儒略历,默认为true。由于1582年10月5日至14日被格里高利历删除,它们不是有效日期。如果传递false,则所有日期都与格里高利历同步。

示例项目接受以下参数:MM DD YYYY [BC],其中BC是可选的,表示日期是公元前。它还将输出COleDateTime计算的星期几以供比较。

计算美国法定假日的日期

以下代码使用DoomsdayDate类计算所有美国法定假日的日期。

namespace { DoomsdayDate NewYears(int year) { return DoomsdayDate(Month::JAN, 1, year); } DoomsdayDate MartinLutherKingJr(int year) { DoomsdayDate dd; dd.SetThird(Weekday::MONDAY, Month::JAN, year); return dd; } // ... 省略其他假日的函数 ... };

对于今年,生成了以下输出:

Holidays 2002 ======================================= New Years is on a Tuesday 1/1/2002 Martin Luther King Jr. is on Monday 1/21/2002 Washington's Birthday is on Monday 2/18/2002 Memorial Day is on Monday 5/27/2002 Independence Day is on a Thursday 7/4/2002 Labor Day is on Monday 9/2/2002 Columbus Day is on Monday 10/14/2002 Veterans Day is on a Monday 11/11/2002 Thanksgiving Day is on a Thursday 11/28/2002 Christmas Day is on a Wednesday 12/25/2002
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485