Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Converting Java Date to OffsetDateTime

I have an eta value that is an OffsetDateTime and I have a scheduledDate that is a Date type. If the eta isn't set I want to fall back to the Date.

An example of the date is Tue Jul 21 10:32:28 PDT 2020. To convert this, I tried doing: OffsetDateTime.ofInstant(dto.getScheduledTime().ToInstant(), ZoneOffset.UTC) It feels like the utc offset is wrong since the Date has PDT in it already, but at the same time that's also not a timezoneId like "America/Los_Angeles".

I'm a little confused on how to handle this.

like image 665
user6728767 Avatar asked Oct 03 '18 17:10

user6728767


1 Answers

tl;dr

OffsetDateTime target, eta;    // Modern java.time class.
java.util.Date scheduledDate;  // Terrible legacy class.


if(Objects.isNull(eta)) {   // If no eta, fall back to scheduledDate.
    target = scheduledDate.toInstant().atOffset( ZoneOffset.UTC ); // Never use legacy class `java.util.Date` -- when encountered, immediately convert to modern `java.time.Instant`. 
} else {  // Else not null.
    target = eta;
}
return target;

Better to completely avoid java.util.Date. When encountered, immediately convert to modern class Instant, and forget all about the Date object.

OffsetDateTime target, eta, scheduled;
scheduled = incomingJavaUtilDate.toInstant().atOffset(ZoneOffset.UTC);

target = Objects.isNull(eta) ? scheduledDate : eta;  // Ternary operator. Short way of saying: If eta is null, go with scheduledDate, otherwise go with eta. 
return target;

Date::toString lies to you

Firstly, understand that java.util.Date represents a moment in UTC, always in UTC. However, its toString method has the well-intentioned by terribly confusing anti-feature of dynamically applying the JVM’s current default time zone. This creates the false impression that Date has a time zone when in fact it does not.

Avoid legacy date-time classes

Secondly, you are needlessly and tragically mixing the very good modern java.time classes (OffsetDateTime) with the terribly awful legacy date-time classes (Date). Do not do this. Avoid the legacy classes entirely. They were obsoleted by the adoption of JSR 310 back in 2014.

table of date-time classes in Java, both legacy and modern

Use java.time

If handed a java.util.Date object, immediately convert to java.time. Call the new conversion methods added to the old classes. The Instant class directly replaces Date, as a moment in UTC, but with finer resolution of nanoseconds versus milliseconds.

Instant instant = myJavaUtilDate.toInstant();  // Convert from legacy class to modern class.

You should generally be tracking moments in UTC. You can do this as an Instant or as a OffsetDateTime with its offset set to ZoneOffset.UTC constant.

OffsetDateTime odt = instant.atOffset(ZoneOffset.UTC);  // Same moment, no change in meaning whatsoever. 

For UTC specifically, there is no difference between the instant and the odt in our code here. They both represent a moment in UTC. The difference is that OffsetDateTime (a) could carry an alternate offset-from-UTC value (hours-minutes-seconds), and (b) is more flexible, such as generating text in formats other than standard ISO 8601.

Understand that an offset-from-UTC is merely a number of hours, minutes, and seconds. Nothing more. A time zone, in contrast, is much more. A time zone is a history of past, present, and future changes to the offset used by the people of a particular region. For example, the people in region using America/Los_Angeles time zone change their offset-from-UTC twice a year in a silly practice known as Daylight Saving Time (DST) going from -08:00 to -07:00 and back again.

So generally a time zone is preferable over a mere offset. For example, to see your Date we turned into a Instant through the wall-clock time used by most people on the west coast of US, apply the time zone America/Los_Angeles (ZoneId) to the Instant to get a ZonedDateTime.

ZoneId z = ZoneId.of("America/Los_Angeles");
ZonedDateTime zdt = instant.atZone(z);

You can get back to UTC by extracting a Instant.

Instant instant = zdt.toInstant();

And from there get back to a java.util.Date (if you must, otherwise avoid).

java.util.Date d = java.util.Date.from(instant);

Actually, the java.time.Date class does have a time zone buried deep inside. Lacking any accessor (get/set) methods, it is unreachable. Its behaviors do not relate to our discussion here. Confusing? Yes. Yet another of many reasons to avoid the terrible legacy date-time classes.

like image 139
Basil Bourque Avatar answered Sep 20 '22 04:09

Basil Bourque