C#获取中国农历信息

本文介绍如何使用ChineseLunisolarCalendar类获取农历日期信息。

ChineseLunisolarCalendar类定义在System.Globalization命名空间,常用的方法包括:

  • GetSexagenaryYear()方法,根据DateTime数据计算农历年份,即一个甲子中的第几年,返回值为1到60,年份名称的格式是“天干+地支”。
  • GetCelestialStem()方法,根据甲子年份计算天干值,返回值为1到10,对应甲、乙、丙、丁、戊、己、庚、辛、壬、癸。
  • GetTerrestrialBranch()方法,根据甲子年份计算地支值,返回值为1到12,对应子、丑、寅、卯、辰、巳、午、未、申、酉、戌、亥。
  • GetMonth()方法,根据DateTime数据计算农历月份,返回值为1到13。
  • GetLeapMonth()方法,根据DateTime中的年份返回农历闰月是第几个月,如返回8表示闰七月。农历当年没有闰月时返回0。
  • GetDayOfMonth()方法,根据DateTime数据计算农历月份中的第几天,返回1到30。

为方便使用,我们在cfx/CnDate.cs文件中封装CnDate类,下面的代码是类中包含的字段和数据数组。

C#
using System;
using System.Globalization;

namespace cfx
{
    public class CnDate
    {
        protected DateTime myDate;
        protected int myCnYear;
        protected int myCnMonth;
        protected int myCnDay;
        protected int myLeapMonth;
        protected int myTianGan;
        protected int myDiZhi;
        // 十天干
        protected static string[] tianGan =
            {" ", "甲","乙","丙","丁","戊","己","庚","辛","壬","癸" };
        // 十二地支
        protected static string[] diZhi =
            { " ","子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"};
        // 生肖
        protected static string[] shengXiao =
            { " ","鼠","牛","虎","兔","龙","蛇","马","羊","猴","鸡","狗","猪"};
        // 月份名称
        protected static string[] cnMonth =
            {"","正月","二月","三月","四月","五月","六月",
                "七月","八月","九月","十月","冬月","腊月"};
        // 
        protected static string[] cnDay =
        {
            "","初一","初二","初三","初四","初五"
                ,"初六","初七","初八","初九","初十"
                ,"十一","十二","十三","十四","十五"
                ,"十六","十七","十八","十九","二十"
                ,"廿一","廿二","廿三","廿四","廿五"
                ,"廿六","廿七","廿八","廿九","三十"
        };
    }
}

其中的字段包括:

  • myDate,DateTime类型,包含指定的公历日期。
  • myCnYear,int类型,保存农历年份,1到60。
  • myCnMonth,int类型,保存农历月份,1到13。
  • myCnDay,int类型,保存农历在月份中的第几天,1到30。
  • myLeapMonth,int类型,保存农历闰月是一年中的第几个月。没有闰月是为0。
  • myTianGan,int类型,保存天干值,1到10。
  • myDiZhi,int类型,保存地支值,1到12。

tianGan数组,定义了十天干的名称,请注意,数组的第一个元素为空,这么是为了通过索引快速对应1到10的天干名称,以下的数组也都是这样。

diZhi数组,定义十二地支的名称。

shengXiao数组,十二地支对应的生肖名称,即属相名称。

cnMonth数组,农历月份的基本名称。

cnDay数组,农历日子的名称。

接下来,会在构造函数中计算DateTime数据对应的农历信息,如下面的代码。

C#
//
        public CnDate(DateTime dt)
        {
            ChineseLunisolarCalendar cale = new ChineseLunisolarCalendar();
            myDate = dt;
            // 农历年份,甲子年份
            myCnYear = cale.GetSexagenaryYear(myDate);
            // 天干
            myTianGan = cale.GetCelestialStem(myCnYear);
            // 地支
            myDiZhi = cale.GetTerrestrialBranch(myCnYear);
            // 农历月份
            myCnMonth = cale.GetMonth(myDate);
            // 农历闰月
            if (myDate.Month < 3 && myCnMonth > 9)
                myLeapMonth = cale.GetLeapMonth(myDate.Year - 1);
            else
                myLeapMonth = cale.GetLeapMonth(myDate.Year);
            // 农历月中第几天
            myCnDay = cale.GetDayOfMonth(myDate);
        }
        //
        public CnDate() : this(DateTime.Now) { }
        //
        public CnDate(int year, int month, int day) :
            this(new DateTime(year, month, day))
        { }

代码中共有三个版本的构造函数,第一个构造函数包含了农历信息的计算方法,需要一个DateTime类型的参数,其中,大部分信息直接通过ChineseLunisolarCalendar类的方法获取即可,需要特别注意闰月的计算,当公历为一月或二月,而农历为冬月或腊月,使用GetLeapMonth()方法返回闰月时,参数应设置为公历的前一年。

接下来的两个构造函数都会调用第一个版本,没有设置参数时,会使用系统的当前时间;最后一个构造函数可以通过公历的年、月、日设置日期信息。

得到农历的数值信息后,可以使用方法或只读属性返回对应的文本信息,如下面的代码。

C#
//
        public string YearName
        {
            get
            {
                return tianGan[myTianGan] + diZhi[myDiZhi];
            }
        }
        //
        public string MonthName
        {
            get
            {
                if (myLeapMonth > 0)
                {
                    if (myCnMonth < myLeapMonth)
                        return cnMonth[myCnMonth];
                    else if (myCnMonth == myLeapMonth)
                        return "闰" + cnMonth[myCnMonth - 1];
                    else
                        return cnMonth[myCnMonth - 1];
                }
                else
                {
                    return cnMonth[myCnMonth];
                }
            }
        }
        //
        public string DayName
        {
            get
            {
                return cnDay[myCnDay];
            }
        }
        //
        public string ShengXiao
        {
            get
            {
                return shengXiao[myDiZhi];
            }
        }
        //
        new public string ToString()
        {
            return string.Format("农历 {0}({1})年 {2}{3}",
                YearName, ShengXiao, MonthName, DayName);
        }

代码中,YearName只读属性会通过天干和地支组成农历年份名称。

MonthName只读属性中需要注意闰月的情况,农历当年包含闰月时,myLeapMonth会大于0,此时,当前月小于myLeapMonth时直接返回对应的月份名称;当前月等于myLeapMonth时表示为闰月,月份基本名称是myLeapMonth减1对应的名称,需要在月份名称前添加“闰”字;当前月大于myLeapMonth时,使用当前月数值减1,并返回对应的月份名称。农历当年没有闰月时,直接返回对应的月份名称即可。

DayName只读属性,从cnDay数组返回对应的日子名称。

ShengXiao只读属性,从shengXiao数组返回对应的生肖名称。

ToString()方法,返回完整的农历日期信息,可以根据需要修改显示内容和格式。

下面的代码,在Program.cs文件中测试CnDate类的应用。

C#
using System;
using cfx;

namespace csfx_demo
{
    class Program
    {
        static void Main(string[] args)
        {
            CnDate cd = new CnDate(2026,7,19);
            Console.WriteLine(cd.YearName);
            Console.WriteLine(cd.MonthName);
            Console.WriteLine(cd.DayName);
            Console.WriteLine(cd.ShengXiao);
            Console.WriteLine(cd.ToString());
        }
    }
}

代码中,指定公历日期为2026年7月19日,显示的信息如下图所示。

获取农历日期信息

农历日期中还可以添加一些其它功能,如判断农历节日,下面的代码会使用GetFestival()方法返回一些常见的农历节日。

C#
        // 返回非闰月的真实月份
        protected int GetRealMonth()
        {
            if (myLeapMonth > 0)
            {
                if (myCnMonth < myLeapMonth) return myCnMonth;
                else return myCnMonth - 1;
            }
            else
            {
                return myCnMonth;
            }
        }
        // 返回农历节日
        public string GetFestival()
        {
            int m = GetRealMonth(), d = myCnDay;
            if (m == 1 && d == 1) return "春节";
            else if (m == 1 && d == 15) return "元宵节";
            else if (m == 5 && d == 5) return "端午节";
            else if (m == 8 && d == 15) return "中秋节";
            // 除夕
            CnDate nextDay = new CnDate(myDate.AddDays(1));
            if (nextDay.myCnMonth == 1 && nextDay.myCnDay == 1)
                return "除夕";
            //
            return "";
        }

代码中,GetRealMonth()方法会返回实际的月份值,不包括闰月月份。GetFestival()方法中则会根据实际的月份和日子判断节日,如正月初一为春节、正月十五为元宵节、五月初五为端午节、八月十五为中秋节;这里需要注意除夕的判断,因为腊月可能有29天或30天,也就是说除夕未必就是大年三十,但有一点是肯定的,除夕过后就是正月初一,代码中也是根据这一逻辑判断。

下面的代码,在Program.cs文件中测试GetFestival()方法的应用。

C#
using System;
using cfx;

namespace csfx_demo
{
    class Program
    {
        static void Main(string[] args)
        {
            CnDate cd = new CnDate(2026,6,19);
            Console.WriteLine(cd.GetFestival());
        }
    }
}

执行代码会显示“端午节”。