Java 时间处理实战:LocalDateTime、Instant 与时区转换别再踩坑

📅 2026/7/21 13:41:04
Java 时间处理实战:LocalDateTime、Instant 与时区转换别再踩坑
Java 时间处理实战:LocalDateTime、Instant 与时区转换别再踩坑Java 后端里时间 bug 特别隐蔽:本地测好好的,上线到 UTC 时区的服务器就差了 8 小时;存进数据库的时间和前端显示的对不上;两个时间点算时间差算出负数。根源几乎都是「用错了时间类型」和「时区没搞清」。这篇把java.time里该用哪个类、时区怎么转讲清楚,彻底告别老旧的Date和SimpleDateFormat。一、先扔掉 Date 和 SimpleDateFormat老代码里的java.util.Date、SimpleDateFormat有两个致命问题:Date语义混乱(它其实是个时间戳却带一堆废弃的 getYear 方法),SimpleDateFormat线程不安全,在并发下会解析出错乱的时间:// 反面教材:SimpleDateFormat 做成静态共享,高并发下必炸staticfinalSimpleDateFormatSDFnewSimpleDateFormat(yyyy-MM-dd HH:mm:ss);// 多线程同时 SDF.parse() / SDF.format() 会抛异常或返回错误结果Java 8 起一律用java.time包,全部不可变、线程安全。二、三个核心类,先分清用哪个这是最关键的一步,选错类后面全乱:类含义用在哪LocalDateTime不带时区的「墙上时间」,如 2026-07-21 09:00展示层、和用户约定好时区的场景InstantUTC 时间戳(绝对时刻)存储、跨系统传输、算时间差ZonedDateTime带时区的完整时间需要明确时区换算时一句话原则:存储和传输用Instant(绝对时刻,不受时区影响),展示时再转成对应时区的LocalDateTime。importjava.time.*;// 当前绝对时刻,存库/传输就用它InstantnowInstant.now();System.out.println(now);// 2026-07-21T01:00:00Z (Z 表示 UTC)// 展示给北京用户:转成东八区的墙上时间LocalDateTimebeijingLocalDateTime.ofInstant(now,ZoneId.of(Asia/Shanghai));System.out.println(beijing);// 2026-07-21T09:00 (8 小时)三、最经典的坑:LocalDateTime 里没有时区信息LocalDateTime.now()拿的是运行服务器所在时区的墙上时间。本地开发机是东八区,拿到 09:00;部署到 UTC 服务器,同一刻拿到的是 01:00。如果你把这个LocalDateTime直接当成北京时间存库,时区一变数据就错:// 危险:依赖服务器时区,换台机器结果就变LocalDateTimebadLocalDateTime.now();// 稳妥:显式指定时区LocalDateTimegoodLocalDateTime.now(ZoneId.of(Asia/Shanghai));记住:LocalDateTime不携带时区,它只是「一串数字」。谁读它、按什么时区解释,决定了它到底是几点。别让它「裸奔」依赖服务器默认时区。四、时区转换:同一时刻在不同城市几点需求:一个东京时间 2026-07-21 10:00 的会议,北京的同事看是几点?用ZonedDateTime换算:importjava.time.*;// 东京时间 10:00ZonedDateTimetokyoZonedDateTime.of(LocalDateTime.of(2026,7,21,10,0),ZoneId.of(Asia/Tokyo));// 转成北京时间:同一绝对时刻,换个时区看ZonedDateTimebeijingtokyo.withZoneSameInstant(ZoneId.of(Asia/Shanghai));System.out.println(tokyo);// 2026-07-21T10:0009:00[Asia/Tokyo]System.out.println(beijing);// 2026-07-21T09:0008:00[Asia/Shanghai] 东京比北京早1小时关键 API 是withZoneSameInstant:保持绝对时刻不变,换一个时区来表示。别用withZoneSameLocal(那是「墙上数字不变,换时区」,含义完全不同)。五、格式化用 DateTimeFormatter,它是线程安全的替代SimpleDateFormat,而且可以放心做成静态常量共享:importjava.time.format.DateTimeFormatter;// 线程安全,可静态共享staticfinalDateTimeFormatterFMTDateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss);LocalDateTimetLocalDateTime.of(2026,7,21,9,0,0);Stringst.format(FMT);// 格式化:2026-07-21 09:00:00LocalDateTimebackLocalDateTime.parse(s,FMT);// 解析回来六、算时间差用 Duration / Period别再自己拿毫秒相减除来除去。相差多少小时用Duration(时刻差),相差多少天/月用Period(日期差):importjava.time.*;InstantstartInstant.now();Instantendstart.plusSeconds(3725);DurationdDuration.between(start,end);System.out.println(d.toHours()小时(d.toMinutes()%60)分);// 1小时2分PeriodpPeriod.between(LocalDate.of(2026,1,1),LocalDate.of(2026,7,21));System.out.println(p.getMonths()个月p.getDays()天);// 6个月20天小结彻底弃用Date/SimpleDateFormat(后者线程不安全),全用java.time。存储和传输用Instant(绝对时刻),展示时按目标时区转LocalDateTime。LocalDateTime不带时区,now()依赖服务器时区——要么显式传ZoneId,要么别拿它当固定时区存。跨时区换算用ZonedDateTime.withZoneSameInstant;格式化用线程安全的DateTimeFormatter;算差用Duration/Period。一句话记住:时刻用 Instant 存、按时区转着看;LocalDateTime 只是一串没有时区的数字,别让它依赖服务器默认时区。