I'm playing around with the new java.time package in Java 8. I have a legacy database that gives me java.util.Date
, which I convert to Instant
.
What I am trying to do is add a period of time that is based off of another database flag. I could be adding days, weeks, months, or years. I don't want to have to care what I am adding, and I would like to be able to add more options in the future.
My first thought was Instant.plus()
, but that gives me an UnsupportedTemporalTypeException
for values greater than a day. Instant apparently does not support operations on large units of time. Fine, whatever, LocalDateTime
does.
So that gives me this code:
private Date adjustDate(Date myDate, TemporalUnit unit){ Instant instant = myDate.toInstant(); LocalDateTime dateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); dateTime = dateTime.plus(1, unit); Instant updatedInstant = dateTime.atZone(ZoneId.systemDefault()).toInstant(); return new Date(dueInstant.toEpochMilli()); }
Now, this is my first time using the new time API, so I may have missed something here. But it seems clunky to me that I have to go:
Date --> Instant --> LocalDateTime --> do stuff--> Instant --> Date.
Even if I did not have to use the Date part, I would still think it was a bit awkward. So my question is this, am I doing this completely wrong and what is the best way to do this?
Edit: Expanding on the discussion in the comments.
I think I have a better idea now about how LocalDateTime and Instant are playing with java.util.Date and java.sql.Timestamp. Thanks everyone.
Now, a more practical consideration. Let's say a user sends me a date from wherever they are in the world, arbitrary time zone. They send me 2014-04-16T13:00:00
which I can parse into a LocalDateTime. I then convert this directly to a java.sql.Timestamp and persist in my database.
Now, without doing anything else, I pull my java.sql.timestamp from my database, convert to LocalDateTime
using timestamp.toLocalDateTime()
. All good. Then I return this value to my user using the ISO_DATE_TIME formatting. The result is 2014-04-16T09:00:00
.
I assume this difference is because of some type of implicit conversion to/from UTC. I think my default time zone may be getting applied to the value (EDT, UTC-4) which would explain why the number is off by 4 hours.
New question(s). Where is the implicit conversion from local time to UTC happening here? What is the better way to preserve time zones. Should I not be going directly from Local time as a string (2014-04-16T13:00:00) to LocalDateTime
? Should I be expecting a time zone from the user input?
Instant and LocalDateTime are two entirely different animals: One represents a moment, the other does not. Instant represents a moment, a specific point in the timeline. LocalDateTime represents a date and a time-of-day. But lacking a time zone or offset-from-UTC, this class cannot represent a moment.
getDays() + " days " + time[0] + " hours " + time[1] + " minutes " + time[2] + " seconds.");
The Java Date Time API was added from Java version 8. instant() method of Clock class returns a current instant of Clock object as Instant Class Object. Instant generates a timestamp to represent machine time. So this method generates a timestamp for clock object.
A Datetime is instead a physical concept (an instant of time), which in addition has a Timezone, and hence, it can be expressed in day/month/year. a LocalDateTime is just a bunch of numbers (day, month, year, hour, minute...) that represent a civil (not a physic) concept.
I will go ahead and post an answer based on my final solution and a sort of summary of the very long comment chain.
To start, the whole conversion chain of:
Date --> Instant --> LocalDateTime --> Do stuff --> Instant --> Date
Is necessary to preserve the time zone information and still do operations on a Date like object that is aware of a Calendar and all of the context therein. Otherwise we run the risk of implicitly converting to the local time zone, and if we try to put it into a human readable date format, the times may have changed because of this.
For example, the toLocalDateTime()
method on the java.sql.Timestamp
class implicitly converts to the default time zone. This was undesirable for my purposes, but is not necessarily bad behavior. It is important, however, to be aware of it. That is the issue with converting directly from a legacy java date object into a LocalDateTime
object. Since legacy objects are generally assumed to be UTC, the conversion uses the local timezone offset.
Now, lets say our program takes the input of 2014-04-16T13:00:00
and save to a database as a java.sql.Timestamp
.
//Parse string into local date. LocalDateTime has no timezone component LocalDateTime time = LocalDateTime.parse("2014-04-16T13:00:00"); //Convert to Instant with no time zone offset Instant instant = time.atZone(ZoneOffset.ofHours(0)).toInstant(); //Easy conversion from Instant to the java.sql.Timestamp object Timestamp timestamp = Timestamp.from(instant);
Now we take a timestamp and add some number of days to it:
Timestamp timestamp = ... //Convert to LocalDateTime. Use no offset for timezone LocalDateTime time = LocalDateTime.ofInstant(timestamp.toInstant(), ZoneOffset.ofHours(0)); //Add time. In this case, add one day. time = time.plus(1, ChronoUnit.DAYS); //Convert back to instant, again, no time zone offset. Instant output = time.atZone(ZoneOffset.ofHours(0)).toInstant(); Timestamp savedTimestamp = Timestamp.from(output);
Now we just need to output as a human readable String in the format of ISO_LOCAL_DATE_TIME
.
Timestamp timestamp = .... LocalDateTime time = LocalDateTime.ofInstant(timestamp.toInstant(), ZoneOffset.ofHours(0)); String formatted = DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(time);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With