Algorithm to find the Gregorian date of the Chinese New Year of a certain Gregorian year

5.7k views Asked by At

I am making a driver to calculate various holidays in a given time span. So, I need to find the Gregorian dates of all the Chinese Holidays (Chinese New Year, QingMing Festival, Dragon Boat Festival, etc). I used the famous 'Easter algorithm' for Good Friday, Easter Monday, Ascension Day, and Whit Monday calculations; however, I don't understand it well enough to adapt it for the Chinese calendar.

I have found similar questions, but they often go from Gregorian to Chinese:

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

The last link was exremely helpful but I'm still not sure how to implement that algorithm in a way that can help me. Any advice or code would be greatly appreciated!

Here is my Good Friday Algorithm:

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

There are 2 answers

5
Dai On BEST ANSWER

For Chinese New Year, I think this would work:

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 Support:

Now that .NET 6 (finally) has the DateOnly type (after we've been asking for it for 20 years now...), this works:

(Unfortunately .NET 6's Calendar class hasn't yet been updated to support DateOnly, but it's straightforward to handle manually):

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

So running this...

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

...gives me this output:

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
0
Peter Meyer On

Because the Chinese Calendar is very complex (and based astronomical considerations) there is no simple way to calculate the Gregorian date of a Chinese New Year which is correct in all cases. (There are some 'rules of thumb' which always fail for some years.) For the underlying theory see here and for Windows software to do this (and other Chinese Calendar calculations) see here