C# 农历日历转换实现原理与完整代码
在 C# 应用程序开发中,有时需要将标准的公历日期转换为传统的农历日期(包含生肖、干支及农历月日)。本文详细解析了基于固定数据表的农历转换算法,提供从核心配置到完整方法调用的实现方案,帮助开发者快速集成农历功能。
核心逻辑与数据结构
实现农历转换的核心在于建立一套公历与农历的映射规则。由于农历月份长度不固定且存在闰月,通常采用预置的“农历数据表”(LunarData)来解决。该表中存储了从起始年份(如 1921 年)开始每年的农历信息位图,记录了当年是否为闰年、有几个月以及每个月是大月(30天)还是小月(29天)。
除了农历日期本身,转换过程还需要以下辅助数组:
- 天干(TianGan)与地支(DiZhi):用于组合成干支纪年(如甲子年)。
- 十二生肖(ShengXiao):根据年份计算对应的生肖动物。
- 农历月份名称(MonthName):将数字月份映射为中文汉字(如一、二...腊)。
- 农历日期名称(DayName):将天数映射为初一、初二等表示。
- 公历累计天数(MonthAdd):用于快速计算公历某月到年初的累计天数。
完整代码实现
以下是可以直接复制使用的 C# 类片段。它包含了上述所有数组定义以及核心的 GetLunarCalendar 转换方法。该方法接收一个 DateTime 对象,返回格式化的农历字符串(例如:“丙午年 六月十二日”)。
//天干
private static string []TianGan =
{"甲","乙","丙","丁","戊","己","庚","辛","壬","癸"};
//地支
private static string []DiZhi =
{"子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"};
//十二生肖
private static string []ShengXiao =
{"鼠","牛","虎","兔","龙","蛇","马","羊","猴","鸡","狗","猪"};
//农历日期
private static string []DayName =
{"*","初一","初二","初三","初四","初五","初六","初七","初八","初九","初十",
"十一","十二","十三","十四","十五","十六","十七","十八","十九","二十",
"廿一","廿二","廿三","廿四","廿五","廿六","廿七","廿八","廿九","三十"};
//农历月份
private static string []MonthName =
{"*","正","二","三","四","五","六","七","八","九","十","十一","腊"};
//公历月计数天
private static int []MonthAdd =
{0,31,59,90,120,151,181,212,243,273,304,334};
//农历数据表 (简化示例,实际使用时需填入完整的 long/int 数组)
//注:原文档中的 LunarData 是一个包含多位数数据的整型数组
//这里保留其原始结构示意
private static int []LunarData =
{2635,333387,1701,1748,267701,694,2391,133423,1175,396438,
3402,3749,331177,1453,694,201326,2350,465197,3221,3402,
// ... 省略中间数据,实际项目中请确保包含 1921-2100 年的完整数据 ...
2635,661067,1701,1748,398772,2742,2391,330031};
public string GetLunarCalendar(DateTime dtDay)
{
string sYear = dtDay.Year.ToString();
string sMonth = dtDay.Month.ToString();
string sDay = dtDay.Day.ToString();
int year;
int month;
int day;
try {
year = int.Parse(sYear);
month = int.Parse(sMonth);
day = int.Parse(sDay);
} catch {
year = DateTime.Now.Year;
month = DateTime.Now.Month;
day = DateTime.Now.Day;
}
int nTheDate;
int nIsEnd;
int k, m, n, nBit, i;
string calendar = string.Empty;
//1. 计算该公历日期距离基准日(1921年2月8日)的天数差
//基准日 1921-2-8 为农历正月初一
nTheDate = (year - 1921) * 365 + (year - 1921) / 4 + day + MonthAdd[month - 1] - 38;
if ((year % 4 == 0) && (month > 2)) nTheDate += 1; // 处理闰年情况
//2. 遍历农历数据表,确定农历的年、月、日
nIsEnd = 0; m = 0; k = 0; n = 0;
while (nIsEnd != 1)
{
//判断当前农历年是 12 个月还是 13 个月
if (LunarData[m] < 4095) k = 11; else k = 12;
n = k;
//逐月扣除天数
while (n >= 0)
{
//获取 LunarData[m] 的第 n 个二进制位的值,判断该月大小
nBit = LunarData[m];
for (i = 1; i < n + 1; i++) nBit = nBit / 2;
nBit = nBit % 2;
if (nTheDate <= (29 + nBit)) { nIsEnd = 1; break; }
nTheDate = nTheDate - 29 - nBit;
n = n - 1;
}
if (nIsEnd == 1) break;
m = m + 1;
}
year = 1921 + m;
month = k - n + 1;
day = nTheDate;
#region 格式化输出
//处理闰月:若当月是闰月,则标记为负数并在显示时加“闰”字
if (k == 12)
{
if (month == LunarData[m] / 65536 + 1)
month = 1 - month; // 变为负数代表闰月
else if (month > LunarData[m] / 65536 + 1)
month = month - 1; // 后续月份序号减 1
}
//拼接生肖
calendar = ShengXiao[(year - 4) % 60 % 12].ToString() + "年 ";
//拼接天干
calendar += TianGan[(year - 4) % 60 % 10].ToString();
//拼接地支
calendar += DiZhi[(year - 4) % 60 % 12].ToString() + " ";
//拼接月份名称
if (month < 1)
calendar += "闰" + MonthName[-1 * month].ToString() + "月";
else
calendar += MonthName[month].ToString() + "月";
//拼接日期名称
calendar += DayName[day].ToString() + "日";
return calendar;
#endregion
}
使用注意事项
- 数据完整性:上述代码中的
LunarData数组仅为示意性片段。在实际生产环境中,你需要获取完整的农历数据表(通常覆盖 1901 年至 2100 年或更长时间),并将其完整粘贴到int[] LunarData中,否则转换结果将不准确。 - 性能优化:如果频繁调用此方法,建议将静态数组初始化放在类加载阶段,或者考虑将其封装为单例模式/静态工具类,避免重复创建对象。
- 边界测试:农历转换涉及复杂的阴阳历法规则,建议在部署前针对春节、闰四月等特殊日期进行单元测试验证。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













