求某公历年农历新年公历的算法
Algorithm to find the Gregorian date of the Chinese New Year of a certain Gregorian year
我正在制作一个驱动程序来计算给定时间跨度内的各种假期。所以,我需要找到所有中国节日(农历新年、清明节、端午节等)的公历日期。我使用著名的 'Easter algorithm' 来计算耶稣受难日、复活节星期一、耶稣升天日和圣灵降临节星期一;但是,我对它的理解还不够好,无法将其适应中国历法。
我发现了类似的问题,但他们经常从公历转为中文:
Moon / Lunar Phase Algorithm
Calculating lunar/lunisolar holidays in python
http://www.herongyang.com/year/program.html
http://www.hermetic.ch/cal_stud/ch_year.htm
最后一个 link 非常有帮助,但我仍然不确定如何以对我有帮助的方式实施该算法。任何建议或代码将不胜感激!
这是我的耶稣受难日算法:
private void GetGoodFridayOccurances(DateTime startDate, DateTime endDate, List<ObservedHoliday> observedHolidays, StandardHoliday holiday)
{
for (DateTime date = startDate; date <= endDate; date = date.AddYears(1))
{
#region Finding the Day of Easter Algorithm
int day, month;
int firstTwo = date.Year / 100;
int remainderMod = date.Year % 19;
int pfmDate = (firstTwo - 15) / 2 + 202 - 11 * remainderMod;
#region switches
switch (firstTwo)
{
case 21:
case 24:
case 25:
case 27:
case 28:
case 29:
case 30:
case 31:
case 32:
case 34:
case 35:
case 38:
pfmDate = pfmDate - 1;
break;
case 33:
case 36:
case 37:
case 39:
case 40:
pfmDate = pfmDate - 2;
break;
}
#endregion
pfmDate = pfmDate % 30;
int tA = pfmDate + 21;
if (pfmDate == 29)
tA = tA - 1;
if (pfmDate == 29 && remainderMod > 10)
tA = tA - 1;
//Find next sunday
int tB = (tA - 19) % 7;
int tC = (40 - firstTwo) % 4;
if (tC == 3 || tC > 1)
tC = tC + 1;
pfmDate = date.Year % 100;
int tD = (pfmDate + pfmDate / 4) % 7;
int tE = ((20 - tB - tC - tD) % 7) + 1;
day = tA + tE;
if (day > 31)
{
day = day - 31;
month = 4;
}
else
{
month = 3;
}
#endregion
DateTime observed = new DateTime(date.Year, month, day).AddDays(-2);
ObservedHoliday obsdate = new ObservedHoliday(holiday);
if (startDate == endDate && startDate.Day == observed.Day)
{
obsdate.DateObserved = observed;
observedHolidays.Add(obsdate);
}
else if (startDate != endDate && observed >= startDate)
{
obsdate.DateObserved = observed;
observedHolidays.Add(obsdate);
}
}
对于农历新年,我认为这行得通:
using System;
using System.Globalization;
public static ( Int32 year, Int32 month, Int32 day ) GetDateOfChineseNewYear()
{
ChineseLunisolarCalendar chinese = new ChineseLunisolarCalendar();
GregorianCalendar gregorian = new GregorianCalendar();
DateTime utcNow = DateTime.UtcNow;
// Get Chinese New Year of current UTC date/time
DateTime chineseNewYear = chinese.ToDateTime( utcNow.Year, 1, 1, 0, 0, 0, 0 );
// Convert back to Gregorian (you could just query properties of `chineseNewYear` directly, but I prefer to use `GregorianCalendar` for consistency:
Int32 year = gregorian.GetYear( chineseNewYear );
Int32 month = gregorian.GetMonth( chineseNewYear );
Int32 day = gregorian.GetDayOfMonth( chineseNewYear );
return ( year, month, day );
}
.NET 6 支持:
现在 .NET 6 (finally) has the DateOnly
type(我们已经要求它 20 年了......),这有效:
(遗憾的是 .NET 6 的 Calendar
class 尚未更新以支持 DateOnly
,但手动处理很简单):
private static readonly ChineseLunisolarCalendar _chineseCal = new ChineseLunisolarCalendar();
private static readonly GregorianCalendar _gregorianCal = new GregorianCalendar();
public static DateOnly GetGregorianDateOfChineseNewYear()
{
return GetGregorianDateOfChineseNewYear( DateTime.UtcNow.Year );
}
public static DateOnly GetGregorianDateOfChineseNewYear( Int32 gregorianYear )
{
// Get Chinese New Year of current UTC date/time
DateTime chineseNewYear = _chineseCal.ToDateTime( year: gregorianYear, month: 1, day: 1, /*hms:*/ 0, 0, 0, 0 );
// Convert back to Gregorian (you could just query properties of `chineseNewYear` directly, but I prefer to use `GregorianCalendar` for consistency:
Int32 year = _gregorianCal.GetYear( chineseNewYear );
Int32 month = _gregorianCal.GetMonth( chineseNewYear );
Int32 day = _gregorianCal.GetDayOfMonth( chineseNewYear );
return new DateOnly( year, month, day, _gregorianCal );
}
所以运行这个...
Console.WriteLine( "Gregorian year: {0}, Chinese New Year: {1:ddd} {1}", 2021, GetGregorianDateOfChineseNewYear( 2021 ) );
Console.WriteLine();
Console.WriteLine( "Next 10 years:" );
for( Int32 i = 2022; i < 2030; i++ )
{
Console.WriteLine( "Gregorian year: {0}, Chinese New Year: {1:ddd} {1}", i, GetGregorianDateOfChineseNewYear( i ) );
}
...给我这个输出:
Gregorian year: 2021, Chinese New Year: Sat 2021-02-12
Next 10 years:
Gregorian year: 2022, Chinese New Year: Tue 2022-02-01
Gregorian year: 2023, Chinese New Year: Sun 2023-01-22
Gregorian year: 2024, Chinese New Year: Sat 2024-02-10
Gregorian year: 2025, Chinese New Year: Wed 2025-01-29
Gregorian year: 2026, Chinese New Year: Tue 2026-02-17
Gregorian year: 2027, Chinese New Year: Sat 2027-02-06
Gregorian year: 2028, Chinese New Year: Wed 2028-01-26
Gregorian year: 2029, Chinese New Year: Tue 2029-02-13
Gregorian year: 2030, Chinese New Year: Sun 2030-02-03
Gregorian year: 2031, Chinese New Year: Thu 2031-01-23
由于中国历法非常复杂(并且基于天文考虑),因此没有简单的方法来计算在所有情况下都正确的农历新年公历。 (有一些 'rules of thumb' 总是会失败几年。)有关基础理论,请参阅 here and for Windows software to do this (and other Chinese Calendar calculations) see here
我正在制作一个驱动程序来计算给定时间跨度内的各种假期。所以,我需要找到所有中国节日(农历新年、清明节、端午节等)的公历日期。我使用著名的 'Easter algorithm' 来计算耶稣受难日、复活节星期一、耶稣升天日和圣灵降临节星期一;但是,我对它的理解还不够好,无法将其适应中国历法。
我发现了类似的问题,但他们经常从公历转为中文:
Moon / Lunar Phase Algorithm
Calculating lunar/lunisolar holidays in python
http://www.herongyang.com/year/program.html
http://www.hermetic.ch/cal_stud/ch_year.htm
最后一个 link 非常有帮助,但我仍然不确定如何以对我有帮助的方式实施该算法。任何建议或代码将不胜感激!
这是我的耶稣受难日算法:
private void GetGoodFridayOccurances(DateTime startDate, DateTime endDate, List<ObservedHoliday> observedHolidays, StandardHoliday holiday)
{
for (DateTime date = startDate; date <= endDate; date = date.AddYears(1))
{
#region Finding the Day of Easter Algorithm
int day, month;
int firstTwo = date.Year / 100;
int remainderMod = date.Year % 19;
int pfmDate = (firstTwo - 15) / 2 + 202 - 11 * remainderMod;
#region switches
switch (firstTwo)
{
case 21:
case 24:
case 25:
case 27:
case 28:
case 29:
case 30:
case 31:
case 32:
case 34:
case 35:
case 38:
pfmDate = pfmDate - 1;
break;
case 33:
case 36:
case 37:
case 39:
case 40:
pfmDate = pfmDate - 2;
break;
}
#endregion
pfmDate = pfmDate % 30;
int tA = pfmDate + 21;
if (pfmDate == 29)
tA = tA - 1;
if (pfmDate == 29 && remainderMod > 10)
tA = tA - 1;
//Find next sunday
int tB = (tA - 19) % 7;
int tC = (40 - firstTwo) % 4;
if (tC == 3 || tC > 1)
tC = tC + 1;
pfmDate = date.Year % 100;
int tD = (pfmDate + pfmDate / 4) % 7;
int tE = ((20 - tB - tC - tD) % 7) + 1;
day = tA + tE;
if (day > 31)
{
day = day - 31;
month = 4;
}
else
{
month = 3;
}
#endregion
DateTime observed = new DateTime(date.Year, month, day).AddDays(-2);
ObservedHoliday obsdate = new ObservedHoliday(holiday);
if (startDate == endDate && startDate.Day == observed.Day)
{
obsdate.DateObserved = observed;
observedHolidays.Add(obsdate);
}
else if (startDate != endDate && observed >= startDate)
{
obsdate.DateObserved = observed;
observedHolidays.Add(obsdate);
}
}
对于农历新年,我认为这行得通:
using System;
using System.Globalization;
public static ( Int32 year, Int32 month, Int32 day ) GetDateOfChineseNewYear()
{
ChineseLunisolarCalendar chinese = new ChineseLunisolarCalendar();
GregorianCalendar gregorian = new GregorianCalendar();
DateTime utcNow = DateTime.UtcNow;
// Get Chinese New Year of current UTC date/time
DateTime chineseNewYear = chinese.ToDateTime( utcNow.Year, 1, 1, 0, 0, 0, 0 );
// Convert back to Gregorian (you could just query properties of `chineseNewYear` directly, but I prefer to use `GregorianCalendar` for consistency:
Int32 year = gregorian.GetYear( chineseNewYear );
Int32 month = gregorian.GetMonth( chineseNewYear );
Int32 day = gregorian.GetDayOfMonth( chineseNewYear );
return ( year, month, day );
}
.NET 6 支持:
现在 .NET 6 (finally) has the DateOnly
type(我们已经要求它 20 年了......),这有效:
(遗憾的是 .NET 6 的 Calendar
class 尚未更新以支持 DateOnly
,但手动处理很简单):
private static readonly ChineseLunisolarCalendar _chineseCal = new ChineseLunisolarCalendar();
private static readonly GregorianCalendar _gregorianCal = new GregorianCalendar();
public static DateOnly GetGregorianDateOfChineseNewYear()
{
return GetGregorianDateOfChineseNewYear( DateTime.UtcNow.Year );
}
public static DateOnly GetGregorianDateOfChineseNewYear( Int32 gregorianYear )
{
// Get Chinese New Year of current UTC date/time
DateTime chineseNewYear = _chineseCal.ToDateTime( year: gregorianYear, month: 1, day: 1, /*hms:*/ 0, 0, 0, 0 );
// Convert back to Gregorian (you could just query properties of `chineseNewYear` directly, but I prefer to use `GregorianCalendar` for consistency:
Int32 year = _gregorianCal.GetYear( chineseNewYear );
Int32 month = _gregorianCal.GetMonth( chineseNewYear );
Int32 day = _gregorianCal.GetDayOfMonth( chineseNewYear );
return new DateOnly( year, month, day, _gregorianCal );
}
所以运行这个...
Console.WriteLine( "Gregorian year: {0}, Chinese New Year: {1:ddd} {1}", 2021, GetGregorianDateOfChineseNewYear( 2021 ) );
Console.WriteLine();
Console.WriteLine( "Next 10 years:" );
for( Int32 i = 2022; i < 2030; i++ )
{
Console.WriteLine( "Gregorian year: {0}, Chinese New Year: {1:ddd} {1}", i, GetGregorianDateOfChineseNewYear( i ) );
}
...给我这个输出:
Gregorian year: 2021, Chinese New Year: Sat 2021-02-12 Next 10 years: Gregorian year: 2022, Chinese New Year: Tue 2022-02-01 Gregorian year: 2023, Chinese New Year: Sun 2023-01-22 Gregorian year: 2024, Chinese New Year: Sat 2024-02-10 Gregorian year: 2025, Chinese New Year: Wed 2025-01-29 Gregorian year: 2026, Chinese New Year: Tue 2026-02-17 Gregorian year: 2027, Chinese New Year: Sat 2027-02-06 Gregorian year: 2028, Chinese New Year: Wed 2028-01-26 Gregorian year: 2029, Chinese New Year: Tue 2029-02-13 Gregorian year: 2030, Chinese New Year: Sun 2030-02-03 Gregorian year: 2031, Chinese New Year: Thu 2031-01-23
由于中国历法非常复杂(并且基于天文考虑),因此没有简单的方法来计算在所有情况下都正确的农历新年公历。 (有一些 'rules of thumb' 总是会失败几年。)有关基础理论,请参阅 here and for Windows software to do this (and other Chinese Calendar calculations) see here