65.9K
CodeProject 正在变化。 阅读更多。
Home

使用末日法则计算任何一天的星期几

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2002年6月28日

3分钟阅读

viewsIcon

154290

downloadIcon

2075

末日法则的C++实现,用于确定星期几。

引言

John Conway 发明了一种巧妙而简单的方法来计算一年中的任何一天,称为末日法则。我强烈推荐阅读 S.W. Graham 的文章,其中详细描述了该算法。基本上,通过知道给定世纪的 2 月 28 日或 29 日(闰年)是星期几,以及所有其他月份对应的日期是星期几,就可以确定一年中的任何一天。下表总结了哪些整数代表星期几以及哪些天代表月份和世纪的末日。

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

  • 年份部分是 55
  • 日期是 5
  • 1900 年的末日世纪是 3
  • 11 月的末日月份是 7
  • 每年闰年,末日都会增加一天。
  • 我们将末日世纪 + 年份部分 + Floor(年份部分 ÷ 4) % 7 相加
  • 对于此示例:(3 + 55 + floor(55 ÷ 4)) % 7 = 1
  • 由于 11 月的末日早于日期,我们必须计算偏移量。我们知道末日是第 7 天,而第 5 天是前 2 天,或者与一周后的同一天相同,即第 12 天,或者比第 7 天晚 5 天。因此,我们将 5 加到末日,即 12。12 ÷ 7 的余数是 5,这就是正确的日期。
  • 最后,我们需要考虑闰年,所以 5 + 1 = 6,即星期六。

COleDateTime 类的日期限制为 公元 100 年 1 月 1 日公元 9999 年 12 月 31 日,而CTime 类的日期限制为 公元 1970 年 1 月 1 日公元 2038 年 1 月 18 日DoomsdayDate 类可用于查找任何范围内(包括公元前日期)的星期几。

DoomsdayDate

class DoomsdayDate
{
	//-----------------------------------------------------
	// JulianBeforeGregorian refers to the weird dates
	// between October 5-14, 1582 which were deleted from
	// the Gregorian calendar.  Technically the days never
	// existed.  The default is false which sets the date
	// as invalid.
	//-----------------------------------------------------
	DoomsdayDate(bool JulianBeforeGregorian = true);

	bool AD() { return ad_; }
	bool BC() { return !ad_; }

	// pass in ad = false for B.C. dates
	//--------------------------------------------------
	bool Set(int month, int day, int year, bool ad=true);

	// Find the first, second, third, or fourth
	// weekday in the specified month and year.
	// Only A.D. dates are supported.
	//----------------------------------------------
	bool SetFirst(int weekday, int month, int year);
	bool SetSecond(int weekday, int month, int year);
	bool SetThird(int weekday, int month, int year);
	bool SetFourth(int weekday, int month, int year);

	const char* WeekdayStr(int weekDay);

	// returns day of month
	//---------------------------
	int GetDay() { return day_; }

	// returns int month
	// 1 = January
	// 2 = February
	// ...
	//-------------------------------
	int GetMonth() { return month_; }

	// returns int year
	//-----------------------------
	int GetYear() { return year_; }

	// 0 == Sunday
	// 1 == Monday
	// 2 == Tuesday
	// 3 == Wednesday
	// 4 == Thursday
	// 5 == Friday
	// 6 == Saturday
	//------------------
	int Weekday();

	void Print();
}

DoomsdayDate 星期几函数

末日法则的核心是Weekday函数。

int Weekday()
{
	int r = -1;
	int x = 0, y = 0;
	int ddcentury = -1;
	int ddmonth = DoomsdayMonth(month_);
	if( gregorian_ ) // Gregorian Calendar
	{
		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_ ) // B.C -> AD Julian
	{
		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 // Julian Calendar
	{
		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 用法

默认构造函数接受一个布尔值,该值指示是否应在 1582 年 10 月 15 日之前使用儒略历,默认为 true。由于格里高利历省略了 1582 年 10 月 5 日至 14 日,因此这些不是有效日期。如果传入 false,则所有日期都与格里高利历同步。

示例项目接受以下参数

MM DD YYYY [BC]

其中 BC 是可选的,用于指示日期是否为公元前。它还将输出 COleDateTime 计算出的星期几以进行比较。以下是一些示例程序的有趣结果

DoomsdayDate COleDateTime
Saturday 10/14/1066 Sunday 10/14/1066
Sunday 12/7/1941 Sunday 12/7/1941
Sunday 10/23/4004 B.C. 无法处理公元前日期
Friday 12/31/9999 Friday 12/31/9999
Saturday 1/1/10000 ¦¦¦¦¦¦¦¦¦¦¦¦¦¦ 1/1/10000

美国的法定节假日

以下代码使用DoomsdayDate类来计算所有美国的法定节假日。它包含在LegalHoliday.h文件中。

namespace // *see note below
{
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;
}

DoomsdayDate WashingtonsBirthday(int year)
{
    DoomsdayDate dd;
    dd.SetThird(Weekday::MONDAY, Month::FEB, year);
    return dd;
}

DoomsdayDate MemorialDay(int year)
{
    DoomsdayDate dd;
    dd.SetFourth(Weekday::MONDAY, Month::MAY, year);
    return dd;
}

DoomsdayDate IndependenceDay(int year)
{
    return DoomsdayDate(Month::JUL, 4, year);
}

DoomsdayDate LaborDay(int year)
{
    DoomsdayDate dd;
    dd.SetFirst(Weekday::MONDAY, Month::SEP, year);
    return dd;
}

DoomsdayDate ColumbusDay(int year)
{
    DoomsdayDate dd;
    dd.SetSecond(Weekday::MONDAY, Month::OCT, year);
    return dd;
}

DoomsdayDate VeteransDay(int year)
{
    return DoomsdayDate(Month::NOV, 11, year);
}

DoomsdayDate ThanksgivingDay(int year)
{
    DoomsdayDate dd;
    dd.SetFourth(Weekday::THURSDAY, Month::NOV, year);
    return dd;
}

DoomsdayDate ChristmasDay(int year)
{
    return DoomsdayDate(Month::DEC, 25, year);
}
};

今年的输出如下

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

*关于匿名命名空间的说明

C++ 标准关于匿名或未命名命名空间及其用途的规定如下。

第 7.3.1.1 节第 2 段

"在使用 static 关键字声明命名空间作用域中的对象时,已弃用;未命名命名空间提供了更优越的替代方案。尽管未命名命名空间中的实体可能具有外部链接,但它们有效地限定了一个仅对其翻译单元唯一的名称,因此永远无法从任何其他翻译单元看到。"

许可证

本文未附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论区联系作者。

作者可能使用的许可证列表可以在此处找到。

© . All rights reserved.