Reputation: 29
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:
source: 2018-07-11 15:45:13.0
instant: 2018-07-11T19:45:13Z
response: 2018-07-11T19:45:13Z
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
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 thejava.util.Date
class mentioned above, it is recommended that code not viewTimestamp
values generically as an instance ofjava.util.Date
. The inheritance relationship betweenTimestamp
andjava.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
Reputation: 338496
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.
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
.
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 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() ;
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
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