Q: What's the difference between java 8 ZonedDateTime and OffsetDateTime?
The javadocs say this:
"OffsetDateTime
, ZonedDateTime
and Instant
all store an instant on the time-line to nanosecond precision. Instant
is the simplest, simply representing the instant. OffsetDateTime
adds to the instant the offset from UTC/Greenwich, which allows the local date-time to be obtained. ZonedDateTime
adds full time-zone rules."
Source: https://docs.oracle.com/javase/8/docs/api/java/time/OffsetDateTime.html
Thus the difference between OffsetDateTime
and ZonedDateTime
is that the latter includes the rules that cover daylight saving time adjustments and various other anomalies.
Stated simply:
Time Zone = ( Offset-From-UTC + Rules-For-Anomalies )
Q: According to documentation OffsetDateTime
should be used when writing date to database, but I don't get why.
Dates with local time offsets always represent the same instants in time, and therefore have a stable ordering. By contrast, the meaning of dates with full timezone information is unstable in the face of adjustments to the rules for the respective timezones. (And these do happen; e.g. for date-time values in the future.) So if you store and then retrieve a ZonedDateTime
the implementation has a problem:
It can store the computed offset ... and the retrieved object may then have an offset that is inconsistent with the current rules for the zone-id.
It can discard the computed offset ... and the retrieved object then represents a different point in the absolute / universal timeline than the one that was stored.
If you use Java object serialization, the Java 9 implementation takes the first approach. This is arguably the "more correct" way to handle this, but this doesn't appear to be documented. (JDBC drivers and ORM bindings are presumably making similar decisions, and are hopefully getting it right.)
But if you are writing an application that manually stores date/time values, or that rely on java.sql.DateTime
, then dealing with the complications of a zone-id is ... probably something to be avoided. Hence the advice.
Note that dates whose meaning / ordering is unstable over time may be problematic for an application. And since changes to zone rules are an edge case, the problems are liable to emerge at unexpected times.
A (possible) second reason for the advice is that the construction of a ZonedDateTime
is ambiguous at the certain points. For example in the period in time when you are "putting the clocks back", combining a local time and a zone-id can give you two different offsets. The ZonedDateTime
will consistently pick one over the other ... but this isn't always the correct choice.
Now, this could be a problem for any applications that construct ZonedDateTime
values that way. But from the perspective of someone building an enterprise application is a bigger problem when the (possibly incorrect) ZonedDateTime
values are persistent and used later.
Both returns the JVM's default timezone (in the end, Clock
calls TimeZone.getDefault()
, as explained in @Kiskae's answer), but it's not guaranteed that all calls will always return the same value everytime.
That's because the default timezone can be changed:
- the system where the JVM is running can change its configuration. In Windows machines, for example, this information is read from the registry, while in Linux it gets from
/etc/localtime
(usually a link to a specific file in /usr/share/zoneinfo
) or another similar folder (it can vary in each version/distribution), or by setting the TZ
environment variable. If this system configuration changes it and the JVM is restarted, suddenly your code starts returning different values
- the JVM can be configured to use a different timezone, regardless of OS's config. One example is when the maintanance/infrastructure team changes this configuration (on purpose or by accident, and usually without telling the developers...) and then your code doesn't return the same values anymore (and everything that depends on the timezone will suddenly break)
your application (or another application running the same JVM) calls TimeZone.setDefault()
method. This will affect all the applications running in the same JVM, at runtime, so if you run this code:
TimeZone.setDefault(TimeZone.getTimeZone("Europe/London"));
System.out.println(ZoneId.systemDefault());
TimeZone.setDefault(TimeZone.getTimeZone("America/New_York"));
System.out.println(ZoneId.systemDefault());
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
System.out.println(ZoneId.systemDefault());
The output will be:
Europe/London
America/New_York
UTC
Note how easily the default timezone is changed at runtime, and all subsequent calls to get it are affected. The same will happen if you call Clock.systemDefaultZone().getZone()
or TimeZone.getDefault().toZoneId()
, because both uses the default timezone.
As this changes the JVM's default timezone, all applications running in the same JVM will be affected by it. This might lead to unexpected errors that are hard to debug.
Although the methods that use the default timezone are convenient, you must check how your code depends on it and how it can be affected if the zone changes.
If you don't want to depend on the default, the ideal is to use a specific timezone, such as ZoneId.of("Europe/Paris")
. Always prefer IANA timezones names (always in the format Region/City
, like America/New_York
or Europe/Paris
).
Avoid using the short abbreviations (like CET
or CEST
) because they are ambiguous and not standard.
You can get a list of available timezones (and choose the one that fits best your system) by calling ZoneId.getAvailableZoneIds()
.
Best Answer
The answer comes from the javadoc of
ZoneId
(emphasis mine) ...... and from the javadoc of
ZoneId#of
(emphasis mine):The argument id is specified as
"UTC"
, therefore it will return aZoneId
with an offset, which also presented in the string form:Outputs:
As you use the
equals
method for comparison, you check for object equivalence. Because of the described difference, the result of the evaluation isfalse
.When the
normalized()
method is used as proposed in the documentation, the comparison usingequals
will returntrue
, asnormalized()
will return the correspondingZoneOffset
:As the documentation states, if you use
"Z"
or"+0"
as input id,of
will return theZoneOffset
directly and there is no need to callnormalized()
:To check if they store the same date time, you can use the
isEqual
method instead:Sample
Output: