为何DateTimeFormatter解析日期的结果随Java版本不同存在差异
问题根因
- 这个差异是Java 8的
DateTimeFormatter已知bug导致的:当你通过withZone()方法给格式化器设置了默认时区时,Java 8解析带时间偏移量的字符串时,会优先使用你设置的默认时区,忽略字符串中自带的偏移量信息。 - 该bug在Java 9及后续版本(包括你使用的Java 11)已经被修复,修复后的逻辑是:只要解析的字符串中存在明确的时间偏移/时区信息,就优先使用字符串自带的信息,格式化器设置的默认时区仅在字符串无时区/偏移信息时生效。
- 第二个写法能正常工作,是因为你用
appendZoneId()添加的是时区ID解析规则,该规则在Java 8中本身不存在偏移量解析的这个bug。你的第一个格式化器写法本身没有问题,只是刚好触发了Java 8的兼容缺陷。
Java 8兼容解决方案
这里提供2种无额外依赖的修复方案:
方案1:解析时优先判断是否存在明确偏移量
public static void main(String[] args) { DateTimeFormatterBuilder dfBuilder = new DateTimeFormatterBuilder() .appendPattern("uuuu-M-d") .optionalStart() .optionalStart().appendLiteral(' ').optionalEnd() .optionalStart().appendLiteral('T').optionalEnd() .appendValue(ChronoField.HOUR_OF_DAY) .optionalStart() .appendLiteral(':') .appendValue(ChronoField.MINUTE_OF_HOUR) .optionalStart() .appendLiteral(':') .appendValue(ChronoField.SECOND_OF_MINUTE) .optionalStart() .appendFraction(ChronoField.NANO_OF_SECOND, 1, 9, true) .optionalEnd() .optionalEnd() .optionalEnd() .appendPattern("[XXXXX][XXXX][XXX][XX][X]") .optionalEnd(); DateTimeFormatter df = dfBuilder.toFormatter(Locale.FRANCE).withZone(ZoneId.of("Europe/Paris")); TemporalAccessor temporalAccessor = df.parse("1970-01-01T00:00:00.00Z"); Instant instantTime; // 优先判断是否存在自带的偏移量 if (temporalAccessor.isSupported(ChronoField.OFFSET_SECONDS)) { LocalDateTime dateTime = LocalDateTime.from(temporalAccessor); ZoneOffset offset = ZoneOffset.ofTotalSeconds(temporalAccessor.get(ChronoField.OFFSET_SECONDS)); instantTime = dateTime.atOffset(offset).toInstant(); } else { // 无偏移量时使用默认时区转换 instantTime = Instant.from(temporalAccessor); } long epochTimestamp = instantTime.getEpochSecond(); System.out.println(epochTimestamp); }
方案2:移除格式化器默认时区,手动兜底补时区
如果你不需要格式化器自带默认时区的能力,可以直接移除withZone()调用,仅在解析到无偏移量的字符串时,手动补上默认时区即可:
// 构造格式化器时去掉withZone DateTimeFormatter df = dfBuilder.toFormatter(Locale.FRANCE); TemporalAccessor temporalAccessor = df.parse("1970-01-01T00:00:00.00Z"); Instant instantTime; if (temporalAccessor.isSupported(ChronoField.OFFSET_SECONDS)) { instantTime = Instant.from(temporalAccessor); } else { // 手动补充默认时区 instantTime = ZonedDateTime.of(LocalDateTime.from(temporalAccessor), ZoneId.of("Europe/Paris")).toInstant(); }
内容的提问来源于stack exchange,提问作者bloub
相关产品推荐
相关产品推荐

