在Java里如何进行日期计算_Java时间类计算方式说明

LocalDateTime加减运算最安全,应使用plusDays()、minusHours()等方法;解析字符串需指定DateTimeFormatter;计算时间差要区分Duration(纳秒级)和Period(日历级);涉及时区必须用ZonedDateTime或Instant。

LocalDateTime 做加减运

算最安全

Java 8 引入的 java.time 包彻底替代了过时的 DateCalendar。日常日期加减(如“3天后”“2小时前”)直接用 LocalDateTimeplusDays()minusHours() 等方法,线程安全、不可变、语义清晰。

  • 不要用 new Date().getTime() + 3 * 24 * 60 * 60 * 1000 手动算毫秒——易出错,不处理闰秒/夏令时
  • LocalDateTime 不带时区,适合纯业务日期逻辑(如订单有效期、倒计时)
  • 若需跨时区计算(如“北京时间上午9点对应纽约时间几点”),必须升级为 ZonedDateTime
LocalDateTime now = LocalDateTime.now();
LocalDateTime threeDaysLater = now.plusDays(3);
LocalDateTime twoHoursAgo = now.minusHours(2);

解析字符串日期必须指定 DateTimeFormatter

LocalDateTime.parse() 默认只认 ISO 格式("2025-05-20T14:30:00")。遇到 "2025/05/20 14:30""2025-05-20" 这类常见格式,不传 DateTimeFormatter 会抛 DateTimeParseException

  • 预定义常量如 DateTimeFormatter.ISO_LOCAL_DATE_TIME 仅覆盖标准场景
  • 中文习惯的 "yyyy年MM月dd日 HH:mm:ss" 必须手写 pattern:DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss")
  • pattern 中大小写敏感:"mm" 是分钟,"MM" 是月份;"yyyy" 是4位年,"yy" 是2位年
String input = "2025-05-20 14:30:00";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime dt = LocalDateTime.parse(input, formatter);

DurationPeriod 别混用

计算两个时间点之间的差值时,选错类型会导致结果不符合预期:

  • Duration 用于精确到纳秒的时间跨度(适合 InstantLocalTime),返回的是“总秒数+纳秒”,不关心日历含义
  • Period 用于日期跨度(适合 LocalDateLocalDateTime),返回“几年几月几天”,会按日历规则进位(比如 1月31日 + 1个月 = 2月28/29日)
  • 错误示例:Period.between(startDate, endDate)LocalDateTime 只比较年月日,忽略时分秒——如果需要完整时间差,先转成 Instant 再用 Duration
LocalDateTime start = LocalDateTime.of(2025, 1, 31, 10, 0);
LocalDateTime end = LocalDateTime.of(2025, 2, 29, 10, 0);
Period period = Period.between(start.toLocalDate(), end.toLocalDate()); // 得到 P1M
Duration duration = Duration.between(start.atZone(ZoneId.systemDefault()).toInstant(),
                                     end.atZone(ZoneId.systemDefault()).toInstant()); // 得到 PT2505600S(29天)

时区处理不当是线上故障高发区

系统默认时区(ZoneId.systemDefault())在容器或云环境里可能不是你期望的(比如 Docker 镜像默认 UTC)。用 LocalDateTime 存储带时区含义的时间(如用户提交的“2025-05-20 14:00”),等于埋雷。

  • 入库前统一转为 Instant(UTC 时间戳),数据库字段用 TIMESTAMP WITH TIME ZONE 或等效类型
  • 前端传来的带时区时间(如 ISO 8601 格式 "2025-05-20T14:30:00+08:00"),用 ZonedDateTime.parse() 直接解析,别先切掉时区再塞进 LocalDateTime
  • 日志打印时间务必显式指定时区:LocalDateTime.now(ZoneId.of("Asia/Shanghai")),否则排查问题时容易看错时间

最隐蔽的坑:LocalDateTime.now()LocalDateTime.parse("2025-05-20") 看似都“没时区”,但前者隐含系统默认时区上下文,后者完全无上下文——混在一起做比较或计算,结果取决于服务器配置,极难复现。