我将GMT0作为系统中的默认时区,当我将其序列化并在此之后反序列化时会导致问题.
System.setProperty("user.timezone","GMT0");
DateTimeFormatter zoneFormatter = new DateTimeFormatterBuilder()
.appendZoneOrOffsetId()
.toFormatter();
String formatted = zoneFormatter.format(ZonedDateTime.now());
System.out.println(formatted);
System.out.println(zoneFormatter.parse(formatted));
第一个System.out.println打印GMT0,而第二个抛出以下问题.
Exception in thread "main" java.time.format.DateTimeParseException: Text 'GMT0' could not be parsed, unparsed text found at index 3
at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1952)
at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1777)
这是一种预期的行为吗?有没有办法以安全的方式做到这一点?
最佳答案 正如您在评论中注意到的那样,这是一个
bug in JDK 8,仅在版本> = 9中修复.
如果您正在使用JDK 8而不能/不会升级它,那么就有一种解决方法.您可以将“GMT”部分视为文字(文本“GMT”本身),并使用相应的ChronoField将0视为偏移秒:
DateTimeFormatter zoneParser = new DateTimeFormatterBuilder()
// text "GMT"
.appendLiteral("GMT")
// offset seconds
.appendValue(ChronoField.OFFSET_SECONDS)
.toFormatter();
System.out.println(zoneParser.parse("GMT0"));
请记住,这仅适用于零偏移.对于任何其他值(例如“GMT2”或“GMT-2”),这将不起作用,因为它将值“2”和“-2”视为秒,但它们实际上意味着“小时”.
如果您需要以此格式解析所有偏移值(“GMTn”)
那么,JDK 8也是can’t handle one-digit offsets,它总是需要一个信号,或者 – .因此“GMT2”和“GMT-2”不适用于当前的API.
但是,有一个更难的选择:创建自己的TemporalField,表示“偏移小时”.有关如何操作的所有细节都在documentation中,我不确定是否所有方法都正确实现 – 我只是确定isSupportedBy,getFrom和adjustInto,其他人可能需要一些改进/调整:
public class OffsetHours implements TemporalField {
@Override
public TemporalUnit getBaseUnit() {
return ChronoUnit.HOURS;
}
@Override
public TemporalUnit getRangeUnit() {
return ChronoUnit.FOREVER;
}
@Override
public ValueRange range() {
return ValueRange.of(ZoneOffset.MIN.getTotalSeconds() / 3600, ZoneOffset.MAX.getTotalSeconds() / 3600);
}
@Override
public boolean isDateBased() {
return false;
}
@Override
public boolean isTimeBased() {
return true;
}
@Override
public boolean isSupportedBy(TemporalAccessor temporal) {
return temporal.isSupported(ChronoField.OFFSET_SECONDS);
}
@Override
public ValueRange rangeRefinedBy(TemporalAccessor temporal) {
ValueRange rangeInSecs = temporal.range(ChronoField.OFFSET_SECONDS);
return ValueRange.of(rangeInSecs.getMinimum() / 3600, rangeInSecs.getMaximum() / 3600);
}
@Override
public long getFrom(TemporalAccessor temporal) {
return temporal.getLong(ChronoField.OFFSET_SECONDS) / 3600;
}
@Override
public R adjustInto(R temporal, long newValue) {
return (R) temporal.with(ChronoField.OFFSET_SECONDS, newValue * 3600);
}
}
现在,您创建此字段的实例并在解析器中使用它:
// the new field
OffsetHours offsetHoursField = new OffsetHours();
DateTimeFormatter zoneParser = new DateTimeFormatterBuilder()
// text "GMT"
.appendLiteral("GMT")
// offset hours
.appendValue(offsetHoursField)
.toFormatter();
我还建议创建一个TemporalQuery来将解析后的结果转换为ZoneOffset:
// get hours and create offset from hours value
TemporalQuery getOffsetFromHours = temporal -> {
return ZoneOffset.ofHours((int) temporal.getLong(offsetHoursField));
};
现在你可以解析它:
ZoneOffset offsetZero = zoneParser.parse("GMT0", getOffsetFromHours);
ZoneOffset offsetTwo = zoneParser.parse("GMT2", getOffsetFromHours);
ZoneOffset offsetMinusTwo = zoneParser.parse("GMT-2", getOffsetFromHours);
您可以改进它,使OffsetHours字段成为静态实例(或者可能是枚举),因此您无需一直创建它.