Both System.currentTimeMillis()
and Instant.toEpochMilli()
return the number of milliseconds since the Unix epoch. That isn't "in" any particular time zone, although the Unix epoch is normally expressed as "midnight on January 1st 1970, UTC". But an instant is just an instant in time, and is the same whichever time zone you're in - but it will reflect a different local time.
The output of LocalDateTime.atZone(UTC)
differs because you're saying "Take the local date and time, and convert it to an instant as if it were in the UTC time zone" - even though when you created that LocalDateTime
you did so implicitly in the UTC+3 time zone... that's why it's "wrong".
LocalDateTime.now()
takes the local date and time in the system default time zone. So if your time zone is UTC+3, the current instant in time is 2015-10-06T16:57:00Z, then LocalDateTime.now()
will return .2015-10-06T19:57:00
. Let's call that localNow
...
So localNow.atZone(ZoneOffset.of("+3"))
will return a ZonedDateTime
representing 2015-10-06T19:57:00+03 - in other words, the same local date/time, but "knowing" that it's 3 hours ahead of UTC... so toInstant()
will return an Instant
representing 2015-10-06T16:57:00Z. Great - we still have the current date/time.
But localNow.atZone(ZoneOffset.UTC)
will return a ZonedDateTime
representing 2015-10-06T19:57:00Z - in other words, the same local date/time, but "thinking" that it's already in UTC... so toInstant()
will return an Instant
representing 2015-10-06T19:57:00Z.. which isn't the current time at all (it's in three hours).
The answer comes from the javadoc of ZoneId
(emphasis mine) ...
A ZoneId is used to identify the rules used to convert between an
Instant and a LocalDateTime. There are two distinct types of ID:
- Fixed offsets - a fully resolved offset from UTC/Greenwich, that uses the same offset for all local date-times
- Geographical regions - an area where a specific set of rules for finding the offset from UTC/Greenwich apply
Most fixed offsets are represented by ZoneOffset. Calling normalized()
on any ZoneId will ensure that a fixed offset ID will be represented
as a ZoneOffset.
... and from the javadoc of ZoneId#of
(emphasis mine):
This method parses the ID producing a ZoneId or ZoneOffset. A
ZoneOffset is returned if the ID is 'Z', or starts with '+' or '-'.
The argument id is specified as "UTC"
, therefore it will return a ZoneId
with an offset, which also presented in the string form:
System.out.println(now.withZoneSameInstant(ZoneOffset.UTC));
System.out.println(now.withZoneSameInstant(ZoneId.of("UTC")));
Outputs:
2017-03-10T08:06:28.045Z
2017-03-10T08:06:28.045Z[UTC]
As you use the equals
method for comparison, you check for object equivalence. Because of the described difference, the result of the evaluation is false
.
When the normalized()
method is used as proposed in the documentation, the comparison using equals
will return true
, as normalized()
will return the corresponding ZoneOffset
:
Normalizes the time-zone ID, returning a ZoneOffset where possible.
now.withZoneSameInstant(ZoneOffset.UTC)
.equals(now.withZoneSameInstant(ZoneId.of("UTC").normalized())); // true
As the documentation states, if you use "Z"
or "+0"
as input id, of
will return the ZoneOffset
directly and there is no need to call normalized()
:
now.withZoneSameInstant(ZoneOffset.UTC).equals(now.withZoneSameInstant(ZoneId.of("Z"))); //true
now.withZoneSameInstant(ZoneOffset.UTC).equals(now.withZoneSameInstant(ZoneId.of("+0"))); //true
To check if they store the same date time, you can use the isEqual
method instead:
now.withZoneSameInstant(ZoneOffset.UTC)
.isEqual(now.withZoneSameInstant(ZoneId.of("UTC"))); // true
Sample
System.out.println("equals - ZoneId.of(\"UTC\"): " + nowZoneOffset
.equals(now.withZoneSameInstant(ZoneId.of("UTC"))));
System.out.println("equals - ZoneId.of(\"UTC\").normalized(): " + nowZoneOffset
.equals(now.withZoneSameInstant(ZoneId.of("UTC").normalized())));
System.out.println("equals - ZoneId.of(\"Z\"): " + nowZoneOffset
.equals(now.withZoneSameInstant(ZoneId.of("Z"))));
System.out.println("equals - ZoneId.of(\"+0\"): " + nowZoneOffset
.equals(now.withZoneSameInstant(ZoneId.of("+0"))));
System.out.println("isEqual - ZoneId.of(\"UTC\"): "+ nowZoneOffset
.isEqual(now.withZoneSameInstant(ZoneId.of("UTC"))));
Output:
equals - ZoneId.of("UTC"): false
equals - ZoneId.of("UTC").normalized(): true
equals - ZoneId.of("Z"): true
equals - ZoneId.of("+0"): true
isEqual - ZoneId.of("UTC"): true
Best Answer
Both returns the JVM's default timezone (in the end,
Clock
callsTimeZone.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:
/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 theTZ
environment variable. If this system configuration changes it and the JVM is restarted, suddenly your code starts returning different valuesyour 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:The output will be:
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()
orTimeZone.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 formatRegion/City
, likeAmerica/New_York
orEurope/Paris
). Avoid using the short abbreviations (likeCET
orCEST
) 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()
.