简介:一份面向MySQL开发者与数据建模人员的公历、农历日历数据表资源,覆盖1900至2100年两百余年的完整日期数据,适用于节假日判断、农历生日提醒、排班统计及日期维度分析等业务系统。包内含两个SQL脚本,分别构建公历表与农历表:公历表包含日期、星期、周末及节假日标记等字段,农历表记录农历年月日、中文月份与日期名称、节日布尔值,两表可依据公历日期字段关联,导入MySQL后可直接查询使用,省去自行整理历法数据的环节。资源整体为约1.07MB的zip压缩包,共2个SQL文件,结构精简、体积小,导入后即可投入使用。目前已有300人学习下载。借助该预置日历维度表,开发者能够高效完成两个日期之间的天数计算、工作日与节假日筛选、农历与公历互换等常见需求,同时为报表、排班、提醒类功能提供稳定基础数据,提升查询效率与代码可维护性,也降低了日期逻辑的重复开发成本。
1. MySQL日历数据表:公历农历两百年对照,一张表解决日期换算与节日查询
做业务系统的人都有体会,日期里的"坑"往往不在公历,而在农历:春节对应哪一天、今年有没有闰月、客户生日是腊月廿三换算成公历是多少。手写农历算法不是不行,而是要处理月相、闰月、节气一堆逻辑,一个分支没写对,整张报表就翻车。这套 MySQL 日历数据表把 1900-2100 年每一公历日对应的农历年、月、日、闰月标记、干支、生肖和节气都存成了普通数据库行,导入即可用。适合做报表日历、预约排班、民俗类应用以及任何需要农历换算的后端服务,不用再为农历算法维护一套黑匣子代码。
2. 表结构与数据设计:为什么用单表存两百年日历
2.1 字段设计与类型选型:DATE 与 VARCHAR 混用时的注意点
这套数据表的核心思路是"一行一个公历日期",用公历日期做主键,再把农历信息拆成结构化字段。第一次打开表结构时,重点看这几个字段设计。
CREATE TABLE `calendar_data` ( `solar_date` DATE NOT NULL COMMENT '公历日期', `solar_year` SMALLINT NOT NULL COMMENT '公历年', `solar_month` TINYINT NOT NULL COMMENT '公历月', `solar_day` TINYINT NOT NULL COMMENT '公历日', `lunar_date` VARCHAR(20) DEFAULT NULL COMMENT '农历日期,如2023-01-15', `lunar_year` SMALLINT DEFAULT NULL COMMENT '农历年', `lunar_month` TINYINT DEFAULT NULL COMMENT '农历月,闰月重复取值', `lunar_day` TINYINT DEFAULT NULL COMMENT '农历日', `is_leap_month` TINYINT DEFAULT '0' COMMENT '是否闰月,1为闰月', `gz_year` VARCHAR(10) DEFAULT NULL COMMENT '干支纪年,如癸卯', `zodiac` VARCHAR(10) DEFAULT NULL COMMENT '生肖', `solar_term` VARCHAR(20) DEFAULT NULL COMMENT '节气名称,非节气日为空' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;字段选型上,date 类型用于公历日期是合理的,因为范围在 1900-2100 年之间,date 只占 3 字节且可以直接用 MySQL 日期函数。农历字段没有用 date 类型,因为农历数据本质上是"字符串历法",用 VARCHAR 存2023-01-15这种格式化文本更直观,后续查询也方便 LIKE 匹配。不过要注意,如果要做农历日期的范围比较,VARCHAR 排序会出现2023-01-15和2023-1-5不一致的情况,所以建议同时保留拆好的 lunar_year、lunar_month、lunar_day 三个整数列,排序和范围过滤都走整数列。
is_leap_month 是这套表里最容易忽略的字段。农历闰月时,lunar_month 会重复,例如某年有两个五月,第二个五月的 is_leap_month 为 1。查询时必须把 is_leap_month 作为过滤条件,否则会拿到两条记录。
2.2 数据范围与闰月标记:1900/2100 边界和农历年数据完整性
数据范围是 1900-01-31 到 2100-12-31,注意公历从 1900-01-31 开始,并不是从 1900-01-01 开始。原因很简单:1900 年 1 月 1 日到 1 月 30 日对应的农历还在 1899 年,制表时对 1899 年的数据做了裁剪。所以实际使用中,如果业务要求支持更早的日期,例如查询 1899 年腊月的数据,这套表会查不到。这是一个明确边界,不是数据缺失。
闰月标记采用 is_leap_month 的 0/1 方式,数据生成时对农历闰月单独做了一次校正。校验数据时可以用一个已知闰年的例子:2023 年是农历闰二月,查一下 2023 年 3 月 22 日到 4 月 19 日之间的记录,lunar_month 都是 2,其中 3 月 22 日到 4 月 19 日之间有一条记录 is_leap_month 为 1。如果这个区间内查不到两条 lunar_month=2 的记录,说明导入的数据不完整。
另外,节气字段只有节气当天才有值,其余日期为 NULL。这个设计可以在查询时直接过滤,不用额外关联节气表。但要注意节气准确性依赖数据来源,个别年份的节气时间对应公历日期可能有一天的偏差,做精细历法推算时不要完全依赖这一列。
3. 导入与查询:从 SQL 文件到一条农历查询
3.1 建表与三种导入方式:mysqldump、source 与 GUI 导入的选择
拿到数据文件后,第一步是建表并导入数据。常见做法有三种,按环境选择。
方式一:命令行 source 导入,适合 Linux 服务器和本地终端。
mysql -uroot -p your_database_name < calendar_data.sql导入前先确认 SQL 文件里是否包含建表语句。如果文件只包含 INSERT 语句,需要先手动执行 2.1 节的建表语句,再导入数据。source 导入的优点是快,缺点是一旦中途报错,后续语句会停止执行,所以导入完成后要检查表行数。两百年大约 7 万多行,正常几秒内完成。
方式二:mysqldump 导入,适合从另一台 MySQL 实例迁移。
mysqldump -u root -p your_database_name calendar_data > calendar_backup.sql mysql -u root -p target_database_name < calendar_backup.sql这种方式会把表结构和数据一起导出,导入后表名不变,适用于需要同时迁移多个表的情况。要注意导出时加上--default-character-set=utf8mb4,否则中文的农历字段和干支在导入时可能变成乱码。
方式三:Navicat 或 DBeaver 导入,适合 Windows 桌面环境。直接在对应表上右键"导入向导",选择 SQL 文件或 CSV 文件。用 GUI 导入的好处是能看到每一行的插入错误信息,但遇到大批量数据时,如果源文件里每行都带 INSERT 语句,建议先在编辑器里把多条 INSERT 语句合并成一条多 VALUES 的形式,插入速度会提升不少。
导入完成后,做一次基础校验:
SELECT COUNT(*) FROM calendar_data; SELECT MIN(solar_date), MAX(solar_date) FROM calendar_data;正常行数应该覆盖 1900-01-31 到 2100-12-31 的每一天,检查最小值和最大值是否符合预期,同时单独查一下 2023 年农历闰二月的记录是否存在。这一步看起来多余,但很多项目上线后才发现数据少了一年,就是因为导入时跳过了末尾几条。
3.2 公历转农历:一条 SQL 拿完整农历字段和当日节气
业务中最常见需求是给定一个公历日期,查出对应的农历和节气。直接用主键过滤即可。
SELECT solar_date, lunar_date, gz_year, zodiac, lunar_month, lunar_day, is_leap_month, solar_term FROM calendar_data WHERE solar_date = '2024-02-10';这条 SQL 会返回 2024 年 2 月 10 日对应的农历 2024-01-01,干支甲辰,生肖龙。注意 lunar_date 字段是格式化字符串,如果程序里要拿农历月份做逻辑判断,建议用 lunar_month 和 lunar_day 两个整数列,而不是去解析字符串。
如果需要查询一段时间范围内的农历,例如生成一个月日历视图,SQL 改成条件过滤:
SELECT solar_date, lunar_month, lunar_day, is_leap_month, CASE WHEN is_leap_month = 1 THEN CONCAT('闰', lunar_month, '月') ELSE CONCAT(lunar_month, '月') END AS month_label FROM calendar_data WHERE solar_date BETWEEN '2024-01-01' AND '2024-01-31' ORDER BY solar_date;这里 CASE 语句把闰月转换成"闰2月"这种可读文本,前端拿去渲染日历格会方便很多。注意排序直接用 solar_date,因为公历日期连续且是主键,排序效率很高,不要用 lunar_date 排序。
3.3 农历反查公历:同一月两条记录时闰月参数怎么传
另一个常见需求是从农历日期反查公历日期。例如用户提交"农历 2023 年二月初一",正常应该返回公历 2023-02-20,但由于农历存在闰二月,2023 年有两个二月初一,一个在闰二月。如果 SQL 只按 lunar_month 和 lunar_day 过滤,会拿到两条记录。
SELECT solar_date FROM calendar_data WHERE lunar_year = 2023 AND lunar_month = 2 AND lunar_day = 1 AND is_leap_month = 0 ORDER BY solar_date;is_leap_month = 0 表示取非闰月,如果业务需要找闰二月初一,把条件改成 is_leap_month = 1。这里容易踩坑的是:用户界面通常只有一个下拉框选月份,没有单独让用户选择"是否闰月"的选项。我一般会在后端先查对应农历年是否存在闰月,如果存在,再让用户选择是闰月还是正月的月份,避免查询返回多行导致接口报错。
还有一种查法是直接利用 lunar_date 字符串:
SELECT solar_date FROM calendar_data WHERE lunar_date = '2023-02-01' AND is_leap_month = 0;这种方式简洁,但前提是数据里 lunar_date 字段的格式必须统一为两位零填充,例如 2023-02-01 而不是 2023-2-1。如果源数据没做零填充,字符串匹配会漏数据,所以建议优先用整数列查询。
4. 避坑:日历数据表最常见的六个翻车点
4.1 现象:1900-02-01 之前所有日期都查不到农历
从业务接口传入一个 1900 年 1 月 15 日的日期,查出来的 lunar_date 是 NULL,但同一年 3 月份的数据正常。
原因:数据表起点是 1900-01-31,这之前的公历日期没有收录,因为对应农历还是 1899 年。如果表里建了一个唯一索引 solar_date,那 1900 年 1 月的日期根本无法插入,这是设计边界。
解决:在接口层做日期范围校验,凡是早于 1900-01-31 的请求,直接返回"暂不支持"或降级到简单算法。不要尝试在 SQL 层补数据,因为 1899 年的农历数据需要另一套换算逻辑,硬补会引入新的不一致。
4.2 现象:查农历十月到腊月的记录,返回结果为空
写了一条这样的 SQL:
SELECT solar_date, lunar_date FROM calendar_data WHERE solar_year = 2024 AND lunar_month BETWEEN 10 AND 12;返回 0 行,查 2024 年 1 月的数据却有显示。
原因:混淆了公历年和农历年。2024 年 1 月的农历还属于 2023 年腊月,solar_year = 2024 与 lunar_month = 12 在数据里根本不搭。农历年从正月初一开始,到腊月廿九或三十结束,跨了两个公历年。
解决:涉及农历跨年查询时,统一使用 lunar_year 字段。上面的 SQL 改成WHERE lunar_year = 2024 AND lunar_month BETWEEN 10 AND 12就能正确返回 2024 年农历十到腊月的数据。这个坑在写报表统计时尤其常见,比如统计"农历今年每个月的订单量",必须先确定统计口径是农历年还是公历年。
4.3 现象:日期范围查询越来越慢,7 万行表竟然要 200ms
最开始用 lunar_date LIKE '2024-02%' 这样的写法查某一农历月份的数据,数据量上来后响应明显变慢。
原因:lunar_date 是 VARCHAR 列且没有索引,LIKE 前缀查询虽然可以用普通索引,但没有建索引就只能全表扫描。solar_date 虽然是主键,但如果查询条件不用它,优化器不会主动走主键。
解决:给常用查询列加索引。
ALTER TABLE calendar_data ADD INDEX idx_lunar_date (lunar_date); ALTER TABLE calendar_data ADD INDEX idx_lunar_ym (lunar_year, lunar_month);加完索引后,按农历年月过滤秒回。注意 idx_lunar_ym 这类复合索引覆盖了"农历年+农历月"的组合查询,单查 lunar_year 也能走这个索引,但单查 lunar_month 走不了,所以如果业务还要按单月查询,可以再加一个单独的 lunar_month 索引,但一般情况下复合索引够用了。
4.4 现象:程序读出的闰月标记全是 0,闰二月不见了
某个时间段的数据查询出来,lunar_month = 2 只有一条,检查数据库里也没有 is_leap_month = 1 的记录。
原因:导入的 SQL 文件可能做过二次加工,把 is_leap_month 字段丢弃了,或者导入时选了部分列。另一个可能是数据源本身没有包含闰月数据。
解决:先在库里做一次汇总验证。
SELECT lunar_year, lunar_month, is_leap_month, COUNT(*) FROM calendar_data GROUP BY lunar_year, lunar_month, is_leap_month HAVING is_leap_month = 1 ORDER BY lunar_year;1900-2100 年间至少有 30 多个闰月,这条查询应该返回几十行结果。如果返回 0 行,说明数据文件有问题,不要继续做业务开发,先换一份完整的数据源。
4.5 现象:zhongwen农历字段显示为问号或乱码
导入后查 lunar_date,显示???或2023-??-??,但公历数字字段正常。
原因:建表时未指定 utf8mb4,MySQL 默认用 latin1 字符集存储中文,或者导入时连接字符集不是 utf8mb4。
解决:删表重建,建表语句里指定 CHARSET=utf8mb4,导入前在客户端执行SET NAMES utf8mb4;。如果是命令行 source 导入,可以加参数--default-character-set=utf8mb4。已经导入乱码的表,直接清空重导比改字符集更省事。
4.6 现象:按公历日期查农历时,除夕和春节差了一天
某年查腊月三十,结果是 NULL,除夕那天 lunar_day 是 29 而不是 30。
原因:农历腊月不一定是三十天,有些年份只有二十九,除夕是农历最后一天,可能是腊月廿九。这个不是数据错误,而是业务逻辑没考虑农历月长不固定。
解决:查询某年农历最后一天,不要写死 lunar_day = 30,而是找到该农历年下一个正月初一的前一天。一个实用写法是按农历年分组后,对每个 lunar_month、lunar_day 排序取最大值:
SELECT lunar_year, MAX(lunar_day) AS last_day FROM calendar_data WHERE lunar_month = 12 AND is_leap_month = 0 GROUP BY lunar_year;如果这个 lunar_year 对应的公历期间,腊月最后一天是 29,那除夕日期就用 lunar_day = 29 去查。也可以反查正月初一前一天,做法是先找到lunar_year + 1的 1 月 1 日对应的公历日期,再减一天,这样语义更清晰。
5. 进阶:用日历表算生肖、干支和公历节日,附一套数据正确性抽查法
日历数据表除了农历换算,还能直接提供干支和生肖,省去序数计算。数据里已经带 gz_year 和 zodiac 字段,直接查即可,但如果你要自己校验逻辑,可以按月干地支公式验证。比如农历 2023 年应为癸卯,属兔,用 SQL 查:
SELECT solar_date, lunar_date, gz_year, zodiac FROM calendar_data WHERE lunar_date = '2023-01-01';返回癸卯卯兔,说明干支字段与农历年对齐。接下来可以做一次抽样校验:挑几个业内公认的农历锚点日期,例如 2000 年 2 月 5 日是农历 2000 年正月初一,2024 年 2 月 10 日也是正月初一。写成 SQL 批量验证:
SELECT solar_date, lunar_date, lunar_month, lunar_day FROM calendar_data WHERE (solar_date = '2000-02-05' AND lunar_month = 1 AND lunar_day = 1) OR (solar_date = '2024-02-10' AND lunar_month = 1 AND lunar_day = 1);两条都返回 1 行,说明农历正月初一的数据是准的。然后再抽一个闰月锚点:2023 年闰二月,公历 2023-03-22 是闰二月初一,查这条记录是否满足 is_leap_month = 1。
SELECT solar_date, lunar_month, lunar_day, is_leap_month FROM calendar_data WHERE solar_date = '2023-03-22';is_leap_month 为 1 且 lunar_month = 2、lunar_day = 1,这份数据的闰月标记才可信。还有节气字段,可以抽查 2024 年立春是否在 2 月 4 日。这种锚点验证法我每次拿到新表都会先跑一遍,只查三五个关键日期就能发现大部分数据问题,比全表比对快得多。
如果要实现"农历生日提醒"这类应用,有一个实用技巧:不要直接按月日匹配 lunar_date,而是用 lunar_month 和 lunar_day 匹配,同时把 is_leap_month 条件带入。用户出生在闰月的处理方式一般有两种,默认忽略闰月,只提醒正月的生日。
SELECT user_id, user_name FROM users u JOIN calendar_data c ON c.lunar_month = u.birth_lunar_month AND c.lunar_day = u.birth_lunar_day AND c.lunar_year = 2025 AND c.is_leap_month = 0;这里把日历表当维度表 JOIN 用户表,一次性算出 2025 年所有用户农历生日的公历日期。注意农历新年当天开始,数据才会出现 lunar_year = 2025 的记录,所以即时查询要按当前农历年动态拼接。从那以后我每次做日历类功能,都会强制走一遍锚点验证、索引检查、跨年口径确认这三步,再小的日期表也不敢跳过。希望帮到你。
本文还有配套的精品资源,点击获取