ZonedDateTime.toInstant().toEpochMilli() losing zone data

5.5k Views Asked by At

I am using TrueTime library which returns Date at the system time zone. I am having problems converting this Date to UTC Date when converted to milliseconds.

Here is what I did:

// getting true time in GMT [ex : 2020-07-13T18:00:57.192+03:00 [Europe/Istanbul]]
Date now = getTrueNowTimeInGMT();

// I like to use `LocalDateTime`, so I am converting `Date` to `LocalDateTime`:
LocalDateTime ldtOfSystem = LocalDateTime.ofInstant(now.toInstant(), ZoneId.systemDefault());

// Converting system default zone to UTC
ZonedDateTime zdtOfSystem = ldtOfSystem.atZone(ZoneId.systemDefault());
ZonedDateTime zdtOfSystemInUTC = zdtOfSystem.withZoneSameInstant(ZoneId.of("UTC"));

// Making sure that it is in the UTC. It is returning correct UTC time [ex: 2020-07-13T15:00:57.192 [UTC]]
System.out.println(zdtOfSystemInUTC); 

// converting zdtOfSystemInUTC to milliseconds - problem is occurring here!
zdtOfSystemInUTC.toInstant().toEpochMilli();

I am losing zone data and it is returning milliseconds based on the local zone (system zone). I am converting these milliseconds to Date again and the result is in GMT+3 [2020-07-13T18:00:57.192+03:00]

Am I missing something? I read in one post that toInstant() method doesn't care about the timezone. How can I get the milliseconds from the specific time zone I pointed out in ZonedDateTime?

EDIT I changed variable names (hopefully, into better ones) thanks to @MenuHochSchild for pointing that out.

Clarification. Why do I have the need for the UTC time? I cannot control the user's time. He/she can change date and time of his/her device easily and that is creating problems for us. So in order to tackle the problem, we found a library called TrueTime which gives true-time Date object like this:

Tue Jul 14 00:32:46 GMT+03:00 2020

In order to sync with our server and do some time-related operations, I need to convert this Date which is GMT to UTC and convert it to milliseconds.

2

There are 2 best solutions below

2
On BEST ANSWER

You seem to have misunderstood a thing or two.

// getting true time in GMT [ex : 2020-07-13T18:00:57.192+03:00 [Europe/Istanbul]]
Date now = getTrueNowTimeInGMT();

A Date hasn’t got any time zone or offset from UTC or GMT. So it cannot be in GMT. A Date is a point in time, nothing more, nothing less. As an aside, I would normally recommend you don’t use that class at all since it is poorly designed and long outdated; but if TrueTime cannot yet give you an Instant or other modern class, we’ll have to go with Date for now.

I am losing zone data and it is returning milliseconds based on the local zone (system zone). …

A count of milliseconds since the epoch is independent of time zone. The epoch is usually defined in UTC, but it is one point in time. Therefore the milliseconds are never based on any time zone (or you may say that they are always based on UTC, depending on how you prefer to use the words, it means the same).

… I am converting these milliseconds to Date again and the result is in GMT+3 [2020-07-13T18:00:57.192+03:00]

I didn’t understand this part. You are quoting the same date, time and UTC offset as the one you got from TrueTime, so how could it be wrong? Java regards UTC and GMT as synonymous (in the real world there’s always less than a second between UTC and GMT, so this is probably OK).

Still assuming that you cannot get but on old-fashioned Date from your date and time library, it seems to me that the easy way to get the current number of milliseconds since the epoch is:

getTrueNowTimeInGMT().getTime();

Because of what I said above this will be reliable no matter the time zone setting of the device.

3
On

The Answer by Ole V.V. is correct. I will add a few thoughts and a chart.

Never use java.util.Date

Your code:

Date now = getTrueNowTimeInGMT();

See if your library has been updated for java.time. The java.util.Date class was supplanted years ago, with the adoption of JSR 310. The java.time.Instant class should be used instead.

Instant represents a moment in UTC

Upon receiving a Date object, immediately convert from the legacy class to the modern class. Use the new conversion methods added to the old classes.

Instant instant = myJavaUtilDate.toInstant() ;

You said:

Am I missing something? I read in one post that toInstant() method doesn't care about the timezone.

The java.time.Instant does not care about time zone in that it always represents a moment in time as seen in UTC, by definition.

  • Capturing the current moment via Instant.now will always be seen in the perspective of UTC.
  • Adding/subtracting a span-of-time via Instant::plus and Instant::minus will always be in UTC, and involve the generic 24-hour days of UTC time that does not encounter the anomalies of political time such as Daylight Saving Time (DST).

Count from epoch reference

Both of these classes, legacy & modern, represent a moment in UTC. Both internally keep a count of time since the first moment of 1970 in UTC, 1970-01-01T00:00Z. Instant uses a finer resolution of nanoseconds in its count, versus milliseconds in Date.

Apparently you want a count of milliseconds since 1970-01-01T00:00Z.

long millisSinceEpoch = instant.toEpochMilli() ;

And back again.

Instant instant = Instant.ofEpochMilli( millisSinceEpoch ) ;

I do not recommend tracking time as a count. Such a count is inherently ambiguous to the human reader, and subject to error or misinterpretation. I strongly suggest instead using standard ISO 8601 strings to communicate a date-time value.

String output = instant.toString() ;

And back again.

Instant instant = Instant.parse( input ) ;

Table of all date-time types in Java, both modern and legacy

ZonedDateTime

You said:

How can I get the milliseconds from the specific time zone I pointed out in ZonedDateTime?

A ZonedDateTime object represents a moment as seen through the wall-clock time used by people of a particular region. Imagine two people talking on a long-distance phone call, one on the west coast of US, and one in Tunis Tunisia.

Capture the moment when their call started.

Instant callStartedUtc = Instant.now() ;

When the person in US glanced at a clock on their wall as they dialed their phone, what time did they see?

ZonedDateTime zdtLosAngeles = callStartedUtc.atZone( ZoneId.of( "America/Los_Angeles" ) ) ;

When the person in Tunisia glanced at a clock hanging on their wall as they picked up their phone to answer, what time did they see?

ZonedDateTime zdtTunis = callStartedUtc.atZone( ZoneId.of( "Africa/Tunis" ) ) ;

Three objects all representing the very same simultaneous moment. You can extract a Instant object from both ZonedDateTime objects.

Instant instant = zdtLosAngeles.toInstant() ;

These will all be equal. In this example code, eq will be true.

boolean eq = 
    callStartedUtc.equals( zdtLosAngeles.toInstant() ) 
    &&
    callStartedUtc.equals( zdtTunis.toInstant() )
;

All three internally have the very same count from epoch reference.

UTC as The One True Time

Tip: Learn to think in terms of UTC while on the job programming or doing sysadmin work. Think of UTC as the One True Time, with time zones being mere variations.

Do not translate back-and-forth in your head, just stick with UTC. Like learning a foreign language, constant translation in your head will drive you batty.

Exchanging date-time values between programs or systems, logging, debugging, and such should all be done in UTC. I suggest keeping a 2nd clock on your desk set to UTC.

LocalDateTime is not a moment

You said:

// I like to use LocalDateTime, so I am converting Date to LocalDateTime:

LocalDateTime ldtOfSystem = LocalDateTime.ofInstant(now.toInstant(), ZoneId.systemDefault());

You should not "like" LocalDateTime. That class cannot represent a moment. It holds a date and a time-of-day, but lacks the context of a time zone or offse-from-UTC. So it may say "noon on the 23rd of January 2021" but we have no idea if that means noon in Tokyo Japan, noon in Toulouse France, or noon in Toledo Ohio US — all very different moments, several hours apart.

Whenever you are tracking a moment, a specific point on the timeline, you cannot use LocalDateTime. Instead use Instant (for UTC), OffsetDateTime (for an offset-from-UTC, a number hours-minutes-seconds), or ZonedDateTime (for a time zone, in Continent/Region name, such as Europe/Paris).

When in doubt, do not use LocalDateTime. I find most of the time in business apps, we are tracking a specific moment. The big exception is when booking future events, such as appointments, where LocalDateTime is needed.

Search to learn more as this has been covered many many times already on Stack Overflow. For example, What's the difference between Instant and LocalDateTime?.

Time server

You said:

Clarification. Why do I have the need for the UTC time?

I assume you really meant to say, "Why do I have the need to obtain the current moment from a trusted remote time server?". If so, I ask that you edit your Question for clarity.

This need is legitimate. If the local clock cannot be trusted, and you must have a value close to the current moment, you should indeed be reaching out to a trusted time server if one is available.

The term UTC is not really relevant here. A time server is most likely to return a value in terms of UTC. But being in UTC has nothing to do with the source of time being local or remote.

GMT versus UTC

You said:

I need to convert this Date which is GMT to UTC

No, you do not, almost certainly.

The exact definitions of GMT and various kinds of UTC is long, complicated, academic, and moot for nearly all programmers.

In terms of regular business apps such as accounting and work flow, you can consider GMT and UTC to be synonyms. The difference is literally less than a second. And the default clock used by Java does indeed ignore that difference.

Unless you work with rocket telemetry, GPS/Galileo satellites, or atomic clocks, you really should not care about GMT vs UTC.

Furthermore, in your case of using a remote time server, GMT versus UTC is even less applicable, as the retrieved value loses significant time in its traversal over the network(s).