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类可以用来找到任何范围内的星期几,包括公元前的日期。
以下是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