☰
MySQL万年历数据库:1970-2100年日历表导入与业务应用指南
2026/10/9 20:57:15 网站建设 项目流程

简介:这是一份覆盖1970年1月1日至2100年12月31日的完整万年历MySQL数据库资源,面向需要日期维度数据的开发者、数据分析人员及后端工程师,可用于日历查询、节假日统计、报表按日聚合等场景,省去自行推算农历与公历对应关系的繁琐工作。压缩包共3个文件,以1个sql脚本为主,内含完整建表语句与逐日插入语句,另附2张png截图用于展示数据表结构与运行效果,整体约872KB,导入前注意将数据库编码设为utf8,创建后直接执行脚本即可使用。目前已有1433人学习下载,说明该数据在日期处理类项目中具备一定实用价值。数据时间跨度长达131年,字段信息齐全,可直接作为基础维表接入业务系统,减少手工造数与校验成本,适合对数据完整性要求较高的中高级开发者参考使用。

1. 从 1970 到 2100:一份能直接拉进 MySQL 的万年历数据库

做排班系统、考勤统计、节假日判断或者金融计息模块时,绕不开一个基础问题:某一天到底是星期几、是不是节假日、属于哪个周、农历对应哪一天。很多团队一开始想用代码算,写完发现闰年、大小月、跨年周序这些边界能把人逼疯,最后还是要落一张日历表。这份资源就是干这个的——一份覆盖 1970-01-01 到 2100-12-31 的 MySQL 万年历数据库,包含建表语句和插入语句,编码 utf8,建完库直接导入就能用。它适合后端、数据开发、报表工程师,也适合做考勤、排班、账期类业务的人。下面我按实际拆包和导入的流程,把这份 SQL 怎么用、参数怎么设、哪里容易翻车讲清楚。

2. 拆开这份 SQL:表结构、字段含义与日期跨度设计

2.1 为什么日历要落库而不是现算

日历计算看着简单,实际是个玄学重灾区。公历闰年规则是「四年一闰、百年不闰、四百年再闰」,农历更复杂,涉及朔望月和二十四节气,纯代码实现要么引第三方库,要么自己维护一张对照表。业务上真正高频的查询是「给定日期返回星期、周序、是否周末、是否节假日」,这类查询如果每次都走函数计算,在批量统计场景下会拖慢执行计划,也没法建索引。

落库的核心价值有三个:第一,查询变成一次主键或索引命中,毫秒级返回;第二,节假日、调休这类人为定义的字段可以随时改,不用改代码重新发版;第三,跨年周序(ISO 周)这种容易算错的东西,提前算好存进去,业务侧直接读。1970 到 2100 这个跨度覆盖了绝大多数系统的生命周期,往前够到 Unix 时间戳起点,往后留了七十多年余量,对一般业务足够。

2.2 表结构长什么样

这份 SQL 的建表语句是典型的单表日历结构,主键落在日期字段上。常见做法是日期列用date类型而不是varchar,这样能直接用 MySQL 的日期函数和范围查询。下面是我按这类资源常见结构还原的建表语句,字段命名和类型以实际 SQL 文件为准,导入前建议先打开看一眼:

CREATE TABLE `calendar` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增主键', `c_date` date NOT NULL COMMENT '公历日期,格式 yyyy-MM-dd', `c_year` smallint(6) NOT NULL COMMENT '公历年份', `c_month` tinyint(4) NOT NULL COMMENT '公历月份 1-12', `c_day` tinyint(4) NOT NULL COMMENT '公历日 1-31', `c_week` tinyint(4) NOT NULL COMMENT '星期,0=周日 1=周一 ... 6=周六', `c_week_name` varchar(8) NOT NULL COMMENT '星期中文名,如 星期一', `c_week_of_year` tinyint(4) DEFAULT NULL COMMENT '当年第几周,ISO 周序', `c_quarter` tinyint(4) DEFAULT NULL COMMENT '季度 1-4', `c_is_weekend` tinyint(1) DEFAULT '0' COMMENT '是否周末 1=是 0=否', `c_is_holiday` tinyint(1) DEFAULT '0' COMMENT '是否法定节假日 1=是 0=否', `c_holiday_name` varchar(32) DEFAULT NULL COMMENT '节假日名称,如 春节', `c_lunar_date` varchar(32) DEFAULT NULL COMMENT '农历日期描述', PRIMARY KEY (`id`), UNIQUE KEY `uk_c_date` (`c_date`), KEY `idx_year_month` (`c_year`,`c_month`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='万年历表 1970-2100';

字段设计上有几个点值得注意。c_date上建唯一索引,保证同一天不会重复插入,导入时如果 SQL 里用了INSERT IGNORE或REPLACE,重复执行也不会炸。c_week用 0 表示周日是 MySQLDAYOFWEEK()的默认约定,但很多业务习惯周一为 1,导入后要确认一下,别直接拿去和WEEKDAY()的结果比对,两者差一天。c_week_of_year如果是 ISO 周序,跨年那几天会出现「12 月 31 日属于下一年第 1 周」的情况,做周维度报表时要注意。

2.3 日期跨度与数据量估算

1970 到 2100 一共 131 年,按每年 365 或 366 天算,总行数在 47800 行左右。这个量级对 MySQL 来说非常小,单表全量导入几秒钟的事,不需要分表。导入后表大小通常在几 MB 以内,随便一台开发机都能扛。

跨度设计上,1970 这个起点不是随便选的,它对齐 Unix 时间戳,方便和FROM_UNIXTIME()互相转换。2100 这个终点要注意:2100 不是闰年(能被 100 整除但不能被 400 整除),所以 2100 年 2 月只有 28 天,如果你的业务算到 2100 年之后,这份数据就不够了,得自己补。常见做法是导入后跑一句SELECT MAX(c_date) FROM calendar;确认终点确实是 2100-12-31,别拿到一份被截断的 SQL。

3. 导入实操:建库、编码设置与批量插入验证

3.1 建库时把编码定死

摘要里特别提了一句「编码格式是 utf8,创建的时候注意一下编码格式」,这不是客套话。如果建库时用了默认的 latin1,导入含中文的节假日名称或农历描述时会变乱码,而且乱码是写进表里的,事后改库编码救不回来,只能重导。所以第一步就把字符集定死:

-- 建库时显式指定字符集和排序规则 CREATE DATABASE `calendar_db` DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci; -- 切换到这个库 USE `calendar_db`; -- 确认当前库的编码,输出应该是 utf8 SELECT DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = 'calendar_db';

这里用utf8而不是utf8mb4,是因为这份 SQL 本身是 utf8 编码的,节假日名称和农历描述都是常用汉字,utf8 三字节足够存。如果你的 MySQL 版本是 8.0 且默认字符集已经是 utf8mb4,建库时写 utf8 也能兼容,但导入前最好用file命令或编辑器确认 SQL 文件本身的编码,别出现「文件是 GBK、库是 utf8」这种错配。

3.2 两种导入方式与执行顺序

导入有两种常见做法,看 SQL 文件里建表和插入是不是分开的。如果文件里已经包含CREATE TABLE和INSERT,直接整文件导入:

# 方式一:命令行整文件导入,-u 用户名 -p 回车后输密码 mysql -u root -p calendar_db < 万年历mysql数据库.sql # 导入完成后进库验证行数和日期范围 mysql -u root -p calendar_db -e " SELECT COUNT(*) AS total_rows, MIN(c_date) AS min_date, MAX(c_date) AS max_date FROM calendar;"

如果文件里只有插入语句、建表要自己来,那就先执行 2.2 节的建表语句,再导入数据。命令行导入比用图形工具拖拽更稳,因为图形工具在处理大文件时偶尔会截断,而mysql <是流式读取,几万行不会出问题。导入时如果报ERROR 1366 (HY000): Incorrect string value,基本就是编码没对上,回到 3.1 检查库编码和文件编码。

3.3 导入后的三条验证 SQL

导入不是看到「Query OK」就完事,得验证数据真的对。我一般跑三条:

-- 验证一:抽查几个已知日期,2024-01-01 是周一,2024-02-29 存在(闰年) SELECT c_date, c_week, c_week_name, c_is_weekend FROM calendar WHERE c_date IN ('2024-01-01', '2024-02-29', '2100-02-28', '2100-03-01'); -- 验证二:确认没有重复日期,结果应为 0 SELECT COUNT(*) AS dup_count FROM ( SELECT c_date FROM calendar GROUP BY c_date HAVING COUNT(*) > 1 ) t; -- 验证三:确认总行数在合理区间,47800 左右 SELECT COUNT(*) FROM calendar;

第一条验证里,2024-02-29 能查出来说明闰年数据没漏,2100-02-28 和 2100-03-01 能查出来说明 2100 年非闰年处理正确。第二条查重复,如果结果不是 0,说明导入时唯一索引没生效或者 SQL 里有重复插入,得清表重来。第三条看总量,如果只有几千行,多半是 SQL 文件被截断或者导入中途报错没注意。

提示:导入前先在测试库跑一遍,确认行数和日期范围无误再上生产库,避免脏数据污染业务表。

4. 避坑排查:导入万年历 SQL 最容易翻车的五个地方

4.1 现象:导入报 1366 编码错误,中文变问号

原因:SQL 文件本身是 GBK 或 UTF-8 带 BOM,而目标库是 latin1 或 utf8,两边对不上。带 BOM 的文件尤其坑,MySQL 会把 BOM 头当成 SQL 语句的一部分,报语法错误或插入乱码。

解决:先用编辑器把文件另存为「UTF-8 无 BOM」,再确认库编码是 utf8。命令行下可以用file -i 万年历mysql数据库.sql看文件编码,输出charset=utf-8才正常。如果已经导入了乱码数据,别试图 UPDATE 修复,直接TRUNCATE TABLE calendar;清空重导。

4.2 现象:日期范围不对,最大日期只到 2038 年

原因:有些日历数据是用 32 位时间戳生成的,2038-01-19 是 32 位有符号整数的上限,超过就溢出。如果这份 SQL 是从某个老系统导出的,可能被截断在 2038 年。

解决:导入后立刻跑SELECT MAX(c_date) FROM calendar;,如果结果不是 2100-12-31,说明数据不完整。这份资源标题明确写了到 2100 年,正常应该完整,但拿到任何 SQL 都先验证终点,这是血泪经验。

4.3 现象:星期对不上,业务算出来差一天

原因:c_week字段的约定和业务代码不一致。MySQLDAYOFWEEK()返回 1=周日,WEEKDAY()返回 0=周一,而这份表可能用 0=周日。三种约定混用,差一天是必然的。

解决:导入后先查一个已知日期,比如 2024-01-01 是周一,看c_week返回的是 1 还是 2,确认约定后再写业务代码。别凭感觉假设,我见过太多因为星期差一天导致排班全错的案例。

4.4 现象:节假日字段全是 0,判断不出调休

原因:法定节假日和调休每年由相关部门发布,这份 SQL 生成时的节假日数据可能只覆盖到某一年,之后的年份c_is_holiday默认是 0。

解决:把节假日字段当成「基础数据 + 年度维护」来用。导入后查一下SELECT c_year, COUNT(*) FROM calendar WHERE c_is_holiday = 1 GROUP BY c_year;,看覆盖到哪一年。之后的年份需要自己按年度更新,常见做法是每年年底批量 UPDATE 下一年的节假日标记。

4.5 现象:批量插入慢,几万行跑了十几分钟

原因:SQL 文件里如果是逐条INSERT INTO ... VALUES (...);,几万条就是几万次事务提交,InnoDB 每次提交都要刷盘,慢是正常的。

解决:导入前临时关闭自动提交,或者把多条 INSERT 合并成批量插入。命令行导入时可以加参数:

# 导入时关闭自动提交并加大包大小,加快批量插入 mysql -u root -p --init-command="SET autocommit=0;" calendar_db < 万年历mysql数据库.sql

如果 SQL 文件本身是逐条插入且不方便改,导入慢就慢点,几万行十几分钟能接受,别为了快把innodb_flush_log_at_trx_commit改成 0,生产库上这么干有丢数据风险。

5. 进阶用法:把日历表接进业务查询与年度维护

导入只是第一步,真正省事的是把它接进日常查询。最常见的三个场景:算工作日天数、按周聚合、判断账期。下面这段 SQL 是算某个月的工作日天数,直接 join 日历表比用函数循环快得多:

-- 算 2024 年 2 月的工作日天数(排除周末和法定节假日) SELECT COUNT(*) AS work_days FROM calendar WHERE c_year = 2024 AND c_month = 2 AND c_is_weekend = 0 AND c_is_holiday = 0;

参数说明:c_is_weekend = 0排除周六周日,c_is_holiday = 0排除法定节假日。如果你的业务把调休上班的周末也算工作日,那条件要改成(c_is_weekend = 0 OR c_is_holiday = -1),具体看表里调休是怎么标记的,导入后先SELECT DISTINCT c_is_holiday FROM calendar;看有哪些取值。

按周聚合做报表时,用c_week_of_year分组,但要注意跨年周。ISO 周序下,每年第 1 周可能包含上一年的 12 月 29-31 日,所以分组时最好带上c_year一起,或者直接用YEARWEEK(c_date, 3)现算,和表里的周序字段交叉验证。

年度维护是个长期活。我的习惯是每年 11 月把下一年的节假日安排更新进表,用一条批量 UPDATE 搞定:

-- 示例:把 2025 年春节假期标记为节假日(日期仅为示例,以实际安排为准) UPDATE calendar SET c_is_holiday = 1, c_holiday_name = '春节' WHERE c_date BETWEEN '2025-01-28' AND '2025-02-03'; -- 更新完确认影响行数,别 UPDATE 错范围 SELECT ROW_COUNT() AS affected_rows;

执行 UPDATE 前一定先SELECT一遍确认日期范围,我吃过亏,BETWEEN的边界写错一天,把整个月都标成节假日,报表直接崩了。从那以后我每次改节假日数据都强制走一遍「先 SELECT 确认、再 UPDATE、最后 ROW_COUNT 核对」的流程,多花两分钟,省得事后回滚。这份万年历数据库把 1970 到 2100 的基础数据一次性铺好,剩下的就是按业务节奏维护年度字段,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询