gregoriancalendar 日期计算易错因带时区、可变且隐式修正;应先 clear() 再 set(),或改用 localdate;simpledateformat 线程不安全,推荐 datetimeformatter;闰年判断须严格按格里高利历规则;农历节气需第三方库支持。

为什么 GregorianCalendar 算日期总出错?
因为它是“带时区的可变日历”,不是纯数学日期计算器。你调 add() 或 roll() 时,它会偷偷触发内部重计算(比如跨月时自动修正日期),而你没意识到当前 Calendar 实例还带着毫秒、时区、夏令时偏移这些干扰项。
- 别用
new GregorianCalendar().set(2025, 1, 30)初始化后直接算——2月没有30号,它会静默变成3月2日,但你完全看不到警告 - 每次做日期运算前,先调
cal.clear()清掉所有字段(包括毫秒、时区),再set()年月日,避免残留状态污染结果 - 如果只做年月日层面的加减(比如“2024年12月+3个月”),优先用
LocalDate(Java 8+),它不带时区、不可变、行为确定
SimpleDateFormat 格式化万年历输出时线程不安全
它内部维护一个 calendar 字段和缓冲区,多线程共用同一个 SimpleDateFormat 实例时,format() 和 parse() 会互相覆盖中间状态,导致输出乱码、日期跳变甚至 ArrayIndexOutOfBoundsException。
- 别在工具类里写
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd") - 要么每次用都新建:
new SimpleDateFormat("yyyy-MM-dd").format(date)(小项目够用) - 要么用
ThreadLocal封装:private static final ThreadLocal<simpledateformat> TL_SDF = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"))</simpledateformat> - Java 8+ 直接上
DateTimeFormatter——它是不可变且线程安全的,DateTimeFormatter.ofPattern("yyyy-MM-dd")可以放心复用
闰年判断不能只看“能被4整除”
万年历要撑到公元9999年,必须严格按格里高利历规则:能被4整除但不能被100整除,或能被400整除。写成 year % 4 == 0 会在1900、2100年出错——这些年份不是闰年,但你的代码会当成闰年,二月返回29天。
- 正确逻辑:
(year % 4 == 0 && year % 100 != 0) || (year % 400 == 0) - 别自己手写这个判断——
LocalDate.of(year, 2, 29).isLeapYear()更可靠,底层已处理所有边界 - 如果还在用
Calendar,可以用cal.getActualMaximum(Calendar.DAY_OF_MONTH)拿2月最大天数,它内部已按闰年规则计算,比手动判断更少出错
显示农历/节气?Java 标准库根本不支持
java.time 和 Calendar 都只处理公历(阳历)。所谓“万年历”里的农历、二十四节气、干支纪年,是独立于公历的另一套天文算法,标准库没实现,也没打算加。
立即学习“Java免费学习笔记(深入)”;
- 别试图用
ChineseCalendar——那是 JDK 1.1 的遗留类,JDK 9 已移除,现在根本不存在 - 需要农历转换,老实用第三方库,比如
lunardate(轻量)或jchronic(功能全但较重) - 节气计算依赖太阳黄经,得用天文算法(如 VSOP87),简单查表只能覆盖有限年份,2000–2100 够用,但标“万年历”就容易翻车
真正麻烦的不是算某一天,而是把“年月日”映射到“星期、节气、农历日期、宜忌”这整套符号系统——每层都可能有例外规则,比如闰月怎么排、冬至必须在哪天、哪些年份要置闰。越想做得全,越得承认:标准库只管公历那一半。










