Calendar.add()是日期计算主力方法,自动处理进位越界;roll()仅在当前单位内滚动不改变更大单位;set()需调用getTime()等才生效;Calendar线程不安全且已过时,但部分场景仍需谨慎使用。

Calendar.add() 是日期计算的主力方法
直接用 add() 方法增减年、月、日等字段,它会自动处理进位和越界(比如 1 月加 12 个月变成下一年 1 月,2 月 30 日会回滚到 3 月 2 日)。别手写毫秒加减再转回 Calendar —— 容易出错且不跨时区安全。
常见错误是传错字段常量:Calendar.MONTH 是从 0 开始的(0 表示 1 月),而 Calendar.DAY_OF_MONTH 和 Calendar.YEAR 是从 1 或自然年份开始的。加 1 个月 ≠ 加 30 天,尤其遇到 1 月、2 月、闰年时结果差异明显。
- 加 3 天:
calendar.add(Calendar.DAY_OF_MONTH, 3) - 减 1 年:
calendar.add(Calendar.YEAR, -1) - 加 2 个月(注意:不是固定天数):
calendar.add(Calendar.MONTH, 2)
Calendar.roll() 和 add() 的关键区别在哪
roll() 只在当前单位内“滚动”,不改变更大单位。比如对 2023-01-31 调用 roll(Calendar.MONTH, 1),结果是 2023-02-28(不是 2023-02-31,也不变成 2023-02-31 再进位到 3 月),年份不会变;而 add(Calendar.MONTH, 1) 会得到 2023-02-28,但如果是 2023-01-15,则两者结果一致。
适用场景很窄:仅当明确需要“不溢出上级单位”时才用 roll(),例如做月份选择器只在 1–12 间循环,或调试时临时调整某字段观察局部影响。
立即学习“Java免费学习笔记(深入)”;
- 想让“2023-12-15 + 1 月”变成 2024-01-15?必须用
add() - 想让“12 月”按 +1 滚成 “1 月”,但年份卡死在 2023?才考虑
roll(Calendar.MONTH, 1) -
roll()对Calendar.DAY_OF_YEAR等字段行为也受限,别默认它和add()等价
为什么 set() 后要调用 getTime() 或 computeTime() 才生效
set() 方法只是把字段值记下来,并不立即重算时间戳(time 字段),直到你调用 getTime()、get() 或 computeTime() 才真正触发计算。这导致一个经典陷阱:连续 set() 多个字段后直接 getTimeInMillis(),可能拿到未同步的旧时间戳。
更隐蔽的问题是字段冲突:比如先 set(Calendar.MONTH, 1)(2 月),再 set(Calendar.DAY_OF_MONTH, 30),这时 2 月没有 30 日,但 Calendar 不会立刻报错或修正——它等到真正计算时才按规则回滚(变成 3 月 2 日),而这个时机不可控。
- 安全做法:每次
set()后,如需立刻获取时间值,紧跟calendar.getTime() - 避免混合使用
set()和add():前者延迟计算,后者立即更新内部状态,混用易引发时序 bug - 如果只是初始化 Calendar,优先用
GregorianCalendar(int year, int month, int day)构造器,比多次set()更可靠
Calendar 在 Java 8+ 中已被弃用,但有些场景绕不开
虽然 java.time(如 LocalDateTime、ZonedDateTime)是推荐替代方案,但仍有现实约束:老系统接口返回 Calendar、某些 Android API(尤其是低于 API 26 的版本)、遗留数据库驱动(如旧版 JDBC)仍以 Calendar 传时区时间。硬切 java.time 可能引入兼容性风险或额外转换开销。
若必须用 Calendar,务必注意:它的线程不安全,不能复用实例;SimpleDateFormat 配合 Calendar 解析时,时区行为依赖 Calendar.getTimeZone(),而非格式字符串里的 Z 或 z;Android 上部分机型对 Calendar.getInstance(TimeZone) 的夏令时支持不一致。
- 多线程环境:每次用都 new 一个
GregorianCalendar(),别共享 static 实例 - 和
java.time互转:用GregorianCalendar.from(ZonedDateTime)和calendar.toInstant().atZone(ZoneId.systemDefault()),别用毫秒戳中转丢失时区信息 - Android 开发者注意:
Calendar.getActualMaximum(Calendar.DAY_OF_MONTH)在某些厂商 ROM 上对 2 月返回错误值,建议用YearMonth.of(year, month).lengthOfMonth()替代
MONTH=0)、set() 的延迟求值机制,以及跨月计算时对“月末日”的隐式处理逻辑——这些细节不写测试很难暴露,一到生产环境就出错。









