Zone parsing for GMT0
Zone parsing for GMT0
I have GMT0 as the default timezone in a system and it causes problem when I'm serializing it and deserializing it just after that.
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));
The first System.out.println
prints GMT0
while the second throws the following problem.
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)
is it an expected behavior? Is there a way to do that in a safe manner?
The funny thing is I cannot reproduce your problem (only noticed now). On my Java 10.0.1 your code prints
GMT0
and then ,ISO,GMT0
.– Ole V.V.
Aug 23 at 9:58
GMT0
,ISO,GMT0
I have reproduced. On Java 1.8.0_131 I get the same exception as you report. On Java 9 I don’t.
– Ole V.V.
Aug 23 at 10:20
Indeed it's a bug in the JVM bugs.java.com/bugdatabase/view_bug.do?bug_id=8138664, seems not fixed in 8
– Nicolas Henneaux
Aug 23 at 10:21
Workarounds include: (1) Move to Java 9 or later (well, that’s not always a walk in the park, though). (2) Set default to
GMT
or UTC
(without the 0
). There are probably more…– Ole V.V.
Aug 23 at 10:28
GMT
UTC
0
2 Answers
2
As you noticed in the comments, that's a bug in JDK 8, fixed only in versions >= 9.
If you're using JDK 8 and can't/won't upgrade it, there's a workaround. You can treat the "GMT" part as a literal (the text "GMT"
itself) and consider the 0 as the offset seconds, using the respective ChronoField
:
"GMT"
ChronoField
DateTimeFormatter zoneParser = new DateTimeFormatterBuilder()
// text "GMT"
.appendLiteral("GMT")
// offset seconds
.appendValue(ChronoField.OFFSET_SECONDS)
.toFormatter();
System.out.println(zoneParser.parse("GMT0"));
Keep in mind that this works only for offset zero. For any other values (such as "GMT2" or "GMT-2") this won't work, because it'll consider the values "2" and "-2" as seconds, but they actually mean "hours".
Well, JDK 8 also can't handle one-digit offsets, and it always requires a signal, either +
or -
. So "GMT2" and "GMT-2" won't work with the current API.
+
-
There's a harder alternative, though: create your own TemporalField
, representing "offset hours". All the details about how to do it are in the documentation, and I'm not sure if all methods are correctly implemented - I'm just sure about isSupportedBy
, getFrom
and adjustInto
, the others maybe need some improvement/adjustment:
TemporalField
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 extends Temporal> R adjustInto(R temporal, long newValue)
return (R) temporal.with(ChronoField.OFFSET_SECONDS, newValue * 3600);
Now you create an instance of this field and use it in your parser:
// the new field
OffsetHours offsetHoursField = new OffsetHours();
DateTimeFormatter zoneParser = new DateTimeFormatterBuilder()
// text "GMT"
.appendLiteral("GMT")
// offset hours
.appendValue(offsetHoursField)
.toFormatter();
I also recommend creating a TemporalQuery
to convert the parsed result to a ZoneOffset
:
TemporalQuery
ZoneOffset
// get hours and create offset from hours value
TemporalQuery<ZoneOffset> getOffsetFromHours = temporal ->
return ZoneOffset.ofHours((int) temporal.getLong(offsetHoursField));
;
Now you can parse it:
ZoneOffset offsetZero = zoneParser.parse("GMT0", getOffsetFromHours);
ZoneOffset offsetTwo = zoneParser.parse("GMT2", getOffsetFromHours);
ZoneOffset offsetMinusTwo = zoneParser.parse("GMT-2", getOffsetFromHours);
You can improve it letting the OffsetHours
field to be a static instance (or maybe an enum
), so you don't need to create it all the time.
OffsetHours
enum
I believe that the
0
in GMT0
refers to an offset in hours, not seconds. As long as we’re sure it’s 0, it doesn’t matter, of course, but then we absolutely need to check this assumption explicitly.– Ole V.V.
Aug 24 at 6:40
0
GMT0
Since Java 8, the ZoneId is responsible for handling timezone names and aliases according to IANA Time Zone Database (often referred as TZ DB, zdata). In particular, ZoneId.html#of is used for that conversion.
Different TZ DB versions are shipped by Oracle with different JREs: see tzdata versions and overview of how to update tzdata
The list of TZ DB identifiers is available on wikipedia (TZ column), as for release release 2017c, GMT0
is already listed as deprecated in there, and canonical name for this time zone is Etc/GMT
GMT0
Etc/GMT
Despite there is no explicit instructions to use only canonical TZ names, it can be a best practice however, as deprecated aliases may be removed from further tzdata distributions (and thus from Zone.of support) without any special notice
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
thanks @OleV.V. However I would prefer to avoid changing that since it has impacts on other applications.
– Nicolas Henneaux
Aug 23 at 9:51