求某公历年农历新年公历日期的算法

Posted

技术标签:

【中文标题】求某公历年农历新年公历日期的算法【英文标题】:Algorithm to find the Gregorian date of the Chinese New Year of a certain Gregorian year 【发布时间】:2015-08-23 12:16:45 【问题描述】:

我正在制作一个驱动程序来计算给定时间跨度内的各种假期。所以,我需要找到所有中国假期(农历新年、清明节、端午节等)的公历日期。我使用著名的“复活节算法”来计算耶稣受难日、复活节星期一、升天节和惠特星期一;但是,我对它的理解还不够好,无法适应中国历法。

我发现了类似的问题,但他们经常从公历到中文:

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

最后一个链接非常有帮助,但我仍然不确定如何以可以帮助我的方式实现该算法。任何建议或代码将不胜感激!

这是我的耶稣受难日算法:

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);
            
          

【问题讨论】:

你不需要自己做这个,.NET有一个内置的ChineseLunisolarCalendar类。 复活节算法不太可能适用于任何其他阴历:“因为日期是基于日历的春分而不是天文的,所以根据朱利安进行的计算之间存在差异日历和现代公历。”关键词:“而不是天文数字”链接:en.wikipedia.org/wiki/Computus 【参考方案1】:

对于农历新年,我认为这会起作用:

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 类尚未更新为支持 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

【讨论】:

完美运行!我稍微改变了一下,只创建一个新的 DateTime 而不是启动所有 Int32,但非常感谢! 不幸的是,我的 .NET 版本会引发异常:'GetDaysInMonth' 和 'GetMonth'。暂时没有修复 @JamesVeug 确切的异常类型、消息和堆栈跟踪是什么? @Dai 使用 int month = chinese.GetMonth(utcNow);例外:GetMonth System.Globalization.CCEastAsianLunisolarCalendar.GetMonth(DateTime 日期)(在 /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Globalization/CalendricalCalculations.cs:1929)System.Globalization.EastAsianLunisolarCalendar.GetMonth (日期时间)(在 /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Globalization/EastAsianLunisolarCalendar.cs:159) @JamesVeug 看起来您使用的是 Mono 而不是 .NET Framework。 Mono 可能不支持与 NetFx 相同的日历。【参考方案2】:

由于中国历法非常复杂(并且基于天文考虑),因此没有简单的方法可以计算在所有情况下都正确的农历新年公历日期。 (有一些“经验法则”在几年内总是失效。)有关基本理论,请参阅here,对于执行此操作的 Windows 软件(和其他中国历法计算),请参阅 here

【讨论】:

以上是关于求某公历年农历新年公历日期的算法的主要内容,如果未能解决你的问题,请参考以下文章

怎样用生日算年龄

公历农历日期

万年历公历农历查询

swift 时间选择器第三方。公历转农历,农历转公历。

swift 时间选择器第三方。公历转农历,农历转公历。

swift 时间选择器第三方。公历转农历,农历转公历。