Manoj Selvaraj
Manoj Selvaraj

Reputation: 29

How to convert UTC date to UTC OffsetDateTime in java 8?

How can I convert the date object which is already in UTC to an OffsetDateTime Object in UTC itself in Java? This logic should be written on a microservice where the timezone can be entirely different. So .now() and other things are ruled out, I guess. Also, I don't want to pass Timezone as params anywhere.

Sample code:

public OffsetDateTime convertFrom(Date source) {
    LOGGER.info("source: " + source.toString());
    LOGGER.info("instant: " + source.toInstant().toString());
    LOGGER.info("response: " + source.toInstant().atOffset(ZoneOffset.UTC).toString());
    return source.toInstant().atOffset(ZoneOffset.UTC);
}

and the output I get is:

I want my output return to be 2018-07-11 15:45:13Z for input 2018-07-11 15:45:13.0

Upvotes: 2

Views: 10271

Answers (3)

Anonymous
Anonymous

Reputation: 86262

public static OffsetDateTime convertFrom(Date source) {
    if (source instanceof Timestamp) {
        return ((Timestamp) source).toLocalDateTime()
                .atOffset(ZoneOffset.UTC);
    }
    return source.toInstant().atOffset(ZoneOffset.UTC);
}

The object that was passed to your method was a java.sql.Timestamp, not a Date. We can see this fact from the way it was printed: 2018-07-11 15:45:13.0 is the return value from Timestamp.toString(). The Timestamp class is implemented as a subclass of Date, but this doesn’t mean that we can nor should handle it as a Date. The documentation warns us:

Due to the differences between the Timestamp class and the java.util.Date class mentioned above, it is recommended that code not view Timestamp values generically as an instance of java.util.Date. The inheritance relationship between Timestamp and java.util.Date really denotes implementation inheritance, and not type inheritance.

In the implementation above I have assumed that you cannot mitigate the possibility of getting a Timestamp argument, so I am handling the possibility the best I can. The code is still fragile, though, because sometimes a Timestamp denotes a point in time (I should say that this is the point), at other times it denotes a date and hour of day. Granted that the Timestamp does not hold a time zone in it, the two are not the same. I understand that your sample Timestamp denotes a date and time of 2018-07-11 15:45:13.0, and you want this interpreted in UTC. My code does that (your code in the question, on the other hand, correctly handles the situation where the Timestamp denotes a point in time). Also, even though no time zone is passed in my code, its behaviour still depends on the time zone setting of your JVM.

When I pass a Timestamp of 2018-07-11 15:45:13.0 to my method above, it returns an OffsetDateTime of 2018-07-11T15:45:13Z.

The double nature of Timestamp is unfortunate and confusing, and the only real solution would be if you could avoid that class completely. The Date class too is poorly designed, and both are outdated and replaced by java.time, the modern Java date and time API. If you cannot avoid the old classes in your code, I certainly understand your desire to convert to the modern OffsetDateTime first thing. If on the other hand I understand correctly that the date and time comes through JSON, you may be able to parse it on your side without any of the old date and time classes, which would be a good solution to your problem. And under all circumstances, if your real goal is to represent the point in time in a time zone neutral way, I agree with Basil Bourque in preferring an Instant over an OffsetDateTime in UTC.

Link: Documentation of java.sql.Timestamp

Upvotes: 0

Basil Bourque
Basil Bourque

Reputation: 338496

tl;dr

A java.util.Date and a Instant both represent a moment in UTC. Other time zones and offsets are irrelevant.

Instant instant = myJavaUtilDate.toInstant() 

How can I convert the date object which is already in UTC to an OffsetDateTime Object in UTC itself in Java?

You don’t need OffsetDateTime. Use Instant as shown above.

Use ZonedDateTime, not OffsetDateTime

You do not need OffsetDateTime. An offset-from-UTC is merely a number of hours and minutes. Nothing more, nothing less. In contrast, a time zone is a history of the past, present, and future changes to the offset used by the people of a particular region. So a time zone, if known, is always preferable to a mere offset. So use ZonedDateTime rather than OffsetDateTime wherever possible.

Use OffsetDateTime only when given an offset-from-UTC, such as +02:00, without the context of a specific time zone, such as Europe/Paris.

Convert Date to Instant

If given a java.util.Date, concert to the modern class (Instant) that replaced that troublesome old class. Both represent a moment in UTC as a count from the same epoch reference of first moment of 1970 in UTC. The modern class resolves to nanoseconds rather than milliseconds. To convert, call new methods added to the old class.

Instant instant = myJavaUtilDate.toInstant() ;

Remember that both java.util.Date and Instant always represent a moment in UTC.

Capture current moment, “now”

Capture the current moment in UTC.

Instant instant = Instant.now() ;

now() and other things are ruled out, I guess.

No, you can always capture the current moment by calling Instant.now() on any machine at any time. The JVM’s current default time zone is irrelevant as Instant is always in UTC.

Adjust from UTC into another time zone. Same moment, same point on the timeline, different wall-clock time. <— That is the most important concept to comprehend in this discussion!

ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = instant.atZone() ;

As a shortcut, you can skip the Instant when capturing current moment.

ZonedDateTime zdt = ZonedDateTime.now( z ) ;

Move back to UTC by extracting a Instant object.

Instant instant = zdt.toInstant() ; 

Tip: Focus on UTC

Usually best to have most of your work in UTC. When storing, logging, debugging, or exchanging moments, use UTC. Forget about your own parochial time zone while on the job as a programmer or sysadmin; learn to think in UTC. Keep a second click in your office set to UTC.

Avoid flipping between time zones all the time. Stick with UTC. Adjust to a time zone only when presenting to the user or when business logic demands.

Upvotes: 3

Kiskae
Kiskae

Reputation: 25573

It is already working as intended, the problem is that Date.toString is "helpfully" converting the internal timestamp to your local timezone. Using Date.toGMTString would result in the exact same timestamp for each of the values.

If the resulting timestamp is wrong then the problem lies in the creation of the Date instance. Using the constructor like new Date(2018, 7, 11, 15, 45, 11) would result in that date being calculated for the system timezone, not UTC. To create it for UTC there is Date.UTC but all these APIs have been deprecated since Java 1.1 because they are so confusing.

Upvotes: 0

Related Questions