java 8+ 应弃用 calendar,改用 java.time 包;因 calendar 月份从0开始、星期依赖设置、set()自动滚动、非线程安全、隐含时区/local,而 localdatetime、zoneddatetime 等更安全清晰。

Java 中 Calendar 类本身已不推荐直接使用,尤其在 Java 8+ 环境下,java.time 包(如 LocalDateTime、ZonedDateTime、Period、Duration)才是现代、线程安全、语义清晰的首选。
为什么不该再用 Calendar 的 get() 和 set()
Calendar 的字段常量(如 Calendar.YEAR、Calendar.MONTH)设计反直觉:月份从 0 开始(0 == January),星期几依赖 setFirstDayOfWeek() 且默认是 Sunday(Calendar.SUNDAY == 1),容易导致偏移错误。
-
calendar.get(Calendar.MONTH)返回0表示一月,但开发者常误当 1 处理,造成整月偏差 -
calendar.set(Calendar.DAY_OF_MONTH, 32)不报错,而是自动“滚动”到下月,行为隐蔽 - 非线程安全,多个线程共用同一
Calendar实例会相互覆盖状态 - 构造依赖
new GregorianCalendar(),隐含时区和 Locale 绑定,易引发跨环境时间不一致
Calendar.getInstance() 的替代写法(Java 8+)
获取当前时刻应优先用 Instant 或带时区的 ZonedDateTime,而非 Calendar.getInstance():
// ✅ 推荐:明确时区,不可变,语义清晰
ZonedDateTime now = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
// ✅ 若只需本地日期时间(无时区语义)
LocalDateTime localNow = LocalDateTime.now();
// ❌ 避免:隐式使用系统默认时区 + 可变对象
Calendar cal = Calendar.getInstance(); // 时区、Locale 全部黑盒
注意:LocalDateTime 不代表真实时刻(缺少时区),仅适合业务逻辑中的“日历日期”,比如排班表;真实时间戳必须用 Instant 或 ZonedDateTime。
立即学习“Java免费学习笔记(深入)”;
用 Calendar 做日期加减?改用 plusDays() 或 plusMonths()
Calendar.add(int field, int amount) 同样存在字段歧义和边界滚动问题(例如对 1 月 31 日加一个月,可能得 3 月 3 日而非 2 月 28 日)。而 java.time 提供更自然的增量操作:
-
LocalDate.plusMonths(1)默认“月末对齐”:1 月 31 日 → 2 月 28 日(闰年为 29 日) -
LocalDate.plusDays(30)是严格 30 天后,不含模糊语义 -
Period.between(start, end)直接计算“几年几个月几天”,结果可读性强
若必须兼容旧代码中 Calendar,至少用 Calendar.toInstant() 转成 Instant 再操作,避免在 Calendar 上反复 add/roll。
从 Calendar 迁移到 java.time 的关键转换点
老项目里常见 Date ↔ Calendar ↔ 字符串互转,迁移时需注意以下对应关系:
-
calendar.getTime()→calendar.toInstant().atZone(ZoneId.systemDefault()) -
date.toInstant()→ 直接调用,无需经Calendar -
SimpleDateFormat.parse(str)→ 改用DateTimeFormatter+LocalDateTime.parse(str, formatter) -
calendar.get(Calendar.WEEK_OF_YEAR)→localDate.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR)(ISO 8601 标准周)
真正棘手的是遗留系统中混用 java.util.Date、Timestamp 和数据库 JDBC 时间类型。这时建议在 DAO 层统一收口为 OffsetDateTime 或 Instant,避免在业务层反复做 Calendar 转换。









