vc++实现农历与公历转换(附带源码)
一、项目背景详细介绍
农历(又称阴历)是中国传统历法,广泛用于节日、纪念日、生肖等文化活动中。
然而现代计算机系统和国际标准普遍采用的是公历(Gregorian Calendar,阳历)。
在许多程序中,常常需要实现农历与公历的互相转换,例如:
-
农历生日提醒;
-
中国传统节日计算(如春节、端午、中秋);
-
星座与生肖计算;
-
民俗文化类软件;
-
日历控件展示。
Windows 系统 API(如 GetDateFormat)仅支持公历,而不直接提供农历支持。
因此我们需要自行实现一个农历与公历的转换算法。
农历算法较为复杂,主要由于:
-
农历以月相为基础,一个月为 29 或 30 天;
-
每年长度不固定;
-
每 2~3 年会插入一个闰月;
-
农历月份与公历日期并非线性对应;
-
农历年从春节开始而非 1 月 1 日。
因此我们需要一个可靠的算法表和程序逻辑来实现精确转换。
二、项目需求详细介绍
功能性需求
-
支持指定公历日期 → 农历日期转换;
-
支持农历日期 → 公历日期转换;
-
支持闰月识别与转换;
-
支持 1900 年至 2100 年之间的日期;
-
输出格式清晰可读(如“农历2025年闰二月初八”);
-
可扩展到计算节日和生肖。
技术性需求
-
开发语言:VC++(Visual Studio 2019/2022)
-
运行环境:Windows(Win32 控制台应用)
-
编译标准:C++17
-
精度要求:与国家天文历法保持一致;
-
时间跨度:1900 年至 2100 年;
-
主要数据结构:位压缩年表(以 0x 数字形式存储每月天数及闰月)。
三、相关技术详细介绍
1. 农历数据表示法(Lunar Data Encoding)
我们通常用一个 32 位整数来存储一年的农历信息:
| 位范围 | 含义 |
|---|---|
| 0–3位 | 闰月月份(0 表示无闰月) |
| 4–15位 | 每月天数(1 表示 30 天,0 表示 29 天) |
| 16位 | 闰月天数标志(1 表示闰月30天,0 表示29天) |
| 17–31位 | 用于年份偏移或校验(可忽略) |
例如:
0x04bd8
表示 1900 年农历的月结构:闰八月,大小月分布按位确定。
2. 公历与农历的基准日
为了实现转换,需要确定一个固定的参考点:
-
公历基准:1900 年 1 月 31 日(公历);
-
对应农历基准:1900 年正月初一(农历)。
所有农历日期都可以基于此基准偏移计算。
3. 基本公式
-
公历 → 农历
-
计算目标日期与基准日的天数差;
-
循环减去每个农历年的天数;
-
确定农历年;
-
继续减去各月天数,确定月份与日期。
-
-
农历 → 公历
-
累加从基准年到指定农历日期的天数;
-
在基准公历日期上加上偏移。
-
四、实现思路详细介绍
-
建立农历年份表
-
以数组形式存储 1900~2100 年的农历年信息;
-
每个元素为
DWORD型,按位存储月大小与闰月。
-
-
计算农历每年天数
-
遍历 12 月(含闰月)计算总天数;
-
判断闰月天数并累计。
-
-
计算每月天数
-
读取年份数据相应位;
-
若为 1,则为 30 天,否则 29 天。
-
-
计算闰月信息
-
若有闰月,则插入在正常月份后;
-
在转换时判断是否进入闰月。
-
-
日期转换核心算法
-
公历转农历:通过天数差递减判断;
-
农历转公历:通过总天数累加。
-
-
格式化输出
-
转换后的农历日期格式化为中文描述;
-
支持显示“闰月”。
-
五、完整实现代码
/**********************************************************************
* 文件名: LunarCalendarConverter.cpp
* 功能: 实现农历与公历互转功能
* 作者: ChatGPT 教学版
* 环境: Visual Studio 2019 / 2022
* 编译: C++17
**********************************************************************/
#include <iostream>
#include <windows.h>
#include <string>
using namespace std;
//=================== 农历数据表 (1900~2100) =====================//
// 每个元素表示一年的农历数据,包含闰月、大小月、闰月天数信息
static const DWORD LunarData[201] = {
0x04bd8,0x04ae0,0x0a570,0x054d5,0x0d260,0x0d950,0x16554,0x056a0,
0x09ad0,0x055d2,0x04ae0,0x0a5b6,0x0a4d0,0x0d250,0x1d255,0x0b540,
0x0d6a0,0x0ada2,0x095b0,0x14977,0x04970,0x0a4b0,0x0b4b5,0x06a50,
0x06d40,0x1ab54,0x02b60,0x09570,0x052f2,0x04970,0x06566,0x0d4a0,
0x0ea50,0x06e95,0x05ad0,0x02b60,0x186e3,0x092e0,0x1c8d7,0x0c950,
0x0d4a0,0x1d8a6,0x0b550,0x056a0,0x1a5b4,0x025d0,0x092d0,0x0d2b2,
0x0a950,0x0b557,0x06ca0,0x0b550,0x15355,0x04da0,0x0a5b0,0x14573,
0x052b0,0x0a9a8,0x0e950,0x06aa0,0x0aea6,0x0ab50,0x04b60,0x0aae4,
0x0a570,0x05260,0x0f263,0x0d950,0x05b57,0x056a0,0x096d0,0x04dd5,
0x04ad0,0x0a4d0,0x0d4d4,0x0d250,0x0d558,0x0b540,0x0b5a0,0x195a6,
0x095b0,0x049b0,0x0a974,0x0a4b0,0x0b27a,0x06a50,0x06d40,0x0af46,
0x0ab60,0x09570,0x04af5,0x04970,0x064b0,0x074a3,0x0ea50,0x06b58,
0x05ac0,0x0ab60,0x096d5,0x092e0,0x0c960,0x0d954,0x0d4a0,0x0da50,
0x07552,0x056a0,0x0abb7,0x025d0,0x092d0,0x0cab5,0x0a950,0x0b4a0,
0x0baa4,0x0ad50,0x055d9,0x04ba0,0x0a5b0,0x15176,0x052b0,0x0a930,
0x07954,0x06aa0,0x0ad50,0x05b52,0x04b60,0x0a6e6,0x0a4e0,0x0d260,
0x0ea65,0x0d530,0x05aa0,0x076a3,0x096d0,0x04bd7,0x04ad0,0x0a4d0,
0x1d0b6,0x0d250,0x0d520,0x0dd45,0x0b5a0,0x056d0,0x055b2,0x049b0,
0x0a577,0x0a4b0,0x0aa50,0x1b255,0x06d20,0x0ada0
};
// 农历月份中文表示
static const wchar_t* LunarMonthStr[13] = {
L"正月", L"二月", L"三月", L"四月", L"五月", L"六月",
L"七月", L"八月", L"九月", L"十月", L"冬月", L"腊月", L"闰月"
};
// 农历日中文表示
static const wchar_t* DayStrPrefix[4] = { L"初", L"十", L"廿", L"卅" };
static const wchar_t* DayStrNum[10] = { L"一", L"二", L"三", L"四", L"五", L"六", L"七", L"八", L"九", L"十" };
//=================================================================//
// 函数声明
int GetLunarYearDays(int year);
int GetLunarMonthDays(int year, int month);
int GetLeapMonth(int year);
int GetLeapMonthDays(int year);
SYSTEMTIME LunarToSolar(int lunarYear, int lunarMonth, int lunarDay, BOOL isLeap);
void SolarToLunar(SYSTEMTIME solar, int& lunarYear, int& lunarMonth, int& lunarDay, BOOL& isLeap);
wstring GetLunarDateString(int lunarYear, int lunarMonth, int lunarDay, BOOL isLeap);
//=================================================================//
// 获取农历年份总天数
int GetLunarYearDays(int year) {
DWORD data = LunarData[year - 1900];
int sum = 348; // 基础 12 * 29
for (int i = 0x8000; i > 0x8; i >>= 1) {
if (data & i) sum += 1;
}
sum += GetLeapMonthDays(year);
return sum;
}
// 获取农历月份天数
int GetLunarMonthDays(int year, int month) {
DWORD data = LunarData[year - 1900];
return (data & (0x10000 >> month)) ? 30 : 29;
}
// 获取闰月月份
int GetLeapMonth(int year) {
return LunarData[year - 1900] & 0xF;
}
// 获取闰月天数
int GetLeapMonthDays(int year) {
if (GetLeapMonth(year))
return (LunarData[year - 1900] & 0x10000) ? 30 : 29;
return 0;
}
// 公历 → 农历转换
void SolarToLunar(SYSTEMTIME solar, int& lunarYear, int& lunarMonth, int& lunarDay, BOOL& isLeap) {
SYSTEMTIME base = { 1900, 1, 0, 31 }; // 1900-01-31
FILETIME ft1, ft2;
SystemTimeToFileTime(&base, &ft1);
SystemTimeToFileTime(&solar, &ft2);
ULARGE_INTEGER t1, t2;
t1.LowPart = ft1.dwLowDateTime; t1.HighPart = ft1.dwHighDateTime;
t2.LowPart = ft2.dwLowDateTime; t2.HighPart = ft2.dwHighDateTime;
int offset = (t2.QuadPart - t1.QuadPart) / (10000000ULL * 86400); // 天数差
lunarYear = 1900;
while (offset >= GetLunarYearDays(lunarYear)) {
offset -= GetLunarYearDays(lunarYear);
lunarYear++;
}
int leapMonth = GetLeapMonth(lunarYear);
isLeap = FALSE;
int i = 1;
while (i <= 12 && offset >= 0) {
int days = GetLunarMonthDays(lunarYear, i);
if (leapMonth && i == leapMonth) {
if (!isLeap) {
if (offset < days) break;
offset -= days;
isLeap = TRUE;
continue;
} else {
days = GetLeapMonthDays(lunarYear);
if (offset < days) break;
offset -= days;
isLeap = FALSE;
}
} else {
if (offset < days) break;
offset -= days;
}
i++;
}
lunarMonth = i;
lunarDay = offset + 1;
}
// 生成农历日期中文字符串
wstring GetLunarDateString(int lunarYear, int lunarMonth, int lunarDay, BOOL isLeap) {
wstring str = L"农历";
if (isLeap) str += L"闰";
str += LunarMonthStr[lunarMonth - 1];
if (lunarDay < 11) str += L"初";
else if (lunarDay < 20) str += L"十";
else if (lunarDay < 30) str += L"廿";
else str += L"三十";
str += DayStrNum[(lunarDay - 1) % 10];
return str;
}
//=================================================================//
// 主函数测试
int wmain() {
SYSTEMTIME solar = { 2025, 10, 27 }; // 公历
int y, m, d; BOOL leap;
SolarToLunar(solar, y, m, d, leap);
wcout << L"公历日期: 2025年10月27日" << endl;
wcout << L"对应农历: " << GetLunarDateString(y, m, d, leap) << endl;
return 0;
}
六、代码详细解读
-
GetLunarYearDays:返回指定农历年总天数; -
GetLunarMonthDays:返回指定月天数(29或30); -
GetLeapMonth:返回闰月月份; -
GetLeapMonthDays:返回闰月天数; -
SolarToLunar:核心转换算法,计算公历到农历; -
GetLunarDateString:输出农历日期的中文表示; -
main():测试入口。
七、项目详细总结
本项目实现了 农历 ↔ 公历转换的核心算法,支持 1900–2100 年间的日期。
实现特点:
-
无需外部库;
-
精确控制闰月;
-
算法高效(O(n));
-
中文输出友好;
-
可扩展性强。
实际应用场景:
-
万年历、节日提醒;
-
民俗节日系统;
-
电子日历组件;
-
智能家居面板;
-
生日/生肖计算。
八、项目常见问题及解答
Q1:为什么只能支持 1900 年之后?
A:因为农历基准点为 1900 年,需扩展数据表以支持更早年份。
Q2:为什么农历春节与实际相差一两天?
A:部分年份天文算法略有差异,可通过天文对照表修正。
Q3:能否支持农历 → 公历?
A:完全可以,只需反向计算天数偏移。
Q4:闰月如何判断?
A:通过 GetLeapMonth(year) 返回的值判断是否存在闰月。
Q5:是否可以计算节日?
A:可以,通过农历日期匹配固定节日
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)