ZoneId.of("UTC")
instead of ZoneOffset.UTC
?We know the difference between the two as provided in What is the difference between ZoneOffset.UTC and ZoneId.of("UTC")?.
<tl;dr> version:
ZoneOffset.UTC
returns a mere ZoneOffset
with ID "Z", offset of 0 and default zone rules.ZoneId.of("UTC")
returns a ZoneRegion
with ID "UTC" and ZoneOffset.UTC
included.</tl;dr>
For this question, I assume UTC-usage merely for easier date and time handling and not, because something might actually be located in the UTC region or some other business reason to have this as the actual ZoneRegion.
For example, when dealing with ZonedDateTime
. The only difference I could find was that it prints differently.
2021-06-10T15:28:25.000000111Z
2021-06-10T15:28:25.000000111Z[UTC]
We are having code review discussions back and forth about this, so I guess this conflict is not uncommon.
ZoneOffset.UTC
ZoneId.of("UTC")
ZoneId.of("UTC")
is just a region wrap around ZoneOffset.UTC
and I could not find any benefit so far. As far as I know, in UTC no region-relevant data exists apart from it's inherited ZoneOffset.UTC
rules.Unless you really need the UTC time zone/region for some (e.g. business) reasons, you should prefer ZoneOffset.UTC
. It has a (minor) advantage in its performance footprint and the UTC ZoneRegion does not seem provide any benefit I can see or think of.
However, because of the complexity of the Java Date Time API it is hard to tell if I am missing something in this discussion.
Is there any reason why someone should ever use ZoneId.of("UTC")
?
I can think of one situation where ZoneId.of("UTC")
might be preferable over ZoneOffset.UTC
. If you use jackson-modules-java8 to deserialize ZonedDataTime
from JSON, you get dates with ZoneId
.
For example, let's say we have this POJO (getters/setters omitted for brevity):
class Example {
private ZonedDateTime date = ZonedDateTime.now(ZoneOffset.UTC).truncatedTo(ChronoUnit.DAYS);
}
If you print an instance of the class you get:
Example example1 = new Example();
System.out.println(example1);
// output:
// Example(date=2022-06-17T00:00Z) - as expected
What happens when you deserialize JSON containing the above string 2022-06-17T00:00Z
?
String json = "{\"date\":\"2022-06-17T00:00Z\"}";
Example example2 = mapper.readValue(json, Example.class);
System.out.println(example2);
// output:
// Example(date=2022-06-17T00:00Z[UTC]) // now with ZoneId(!)
So now we have two variants, one with ZoneOffset
and one with ZoneId
:
2022-06-17T00:00Z
2022-06-17T00:00Z[UTC]
And these are not equal to each other. For example:
Instant instant = Instant.now();
ZonedDateTime z1 = ZonedDateTime.ofInstant(instant, ZoneOffset.UTC);
ZonedDateTime z2 = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC"));
System.out.println(z1.equals(z2)); // => false
As a result example1.equals(example2)
will also be false
. This could be a source of subtle bugs.
Note: you get same result if your serialize new Example()
to a JSON string and deseralize the string back to an object. You will get a date with ZoneId
.
Tested with Jackson 2.13.1
.
Edit: I ended up filing a bug for this issue: https://github.com/FasterXML/jackson-modules-java8/issues/244