The answer by Yasmani Llanes is basically correct. I’ll expound.
LocalDateTime
!= UTC Moment
A LocalDateTime
is not a real date-time, it is not tied to the time line. It has no real meaning until you adjust it into a time zone to determine a point on the time line, a moment. Your code, LocalDateTime utcTime
, with your choice of variable name, shows you have conflated a "local" date-time with being a UTC moment. It is not. One is a vague idea, the other is real. (well, real in the Newton sense, not so much in the Einstein Relativistic sense ;-) )
So, the output of the LocalDateTime::toString
is not a fully-formed string as expected by the Instant.parse
method. Specifically, it has no data pertaining to an offset-from-UTC nor time zone. The previous paragraph explains why this is a feature not a bug.
What you want is a ZonedDateTime
which is basically an Instant
(a moment on the timeline in UTC) plus a ZoneId
(a time zone).
ZonedDateTime
= Instant
+ ZoneId
A time zone is an offset-from-UTC (hours and minutes) plus a set of rules and anomalies (such as Daylight Saving Time, DST) for past, present, and future adjustments.
ZoneId
= offset-from-UTC + adjustment-rules
You are correct to go through LocalDateTime
in the java.time framework, and this is where it gets a bit confusing. Logically, we should be able to parse directly from an input String to a ZonedDateTime
. But there is the issue of an input string without any time-zone info may not be valid for a particular zone because of adjustment-rules. For example, in the Spring when we "spring-ahead" with Daylight Saving Time, in the United States jumping an hour ahead at the stroke of 2 AM, there is no "02:38" or "20:54" on that day. The clock jumps from 01:59.59.x to 03:00:00.0.
My understanding is that the java.time framework wants to handle this adjustment via a LocalDateTime
object being passed to ZonedDateTime
rather than have ZonedDateTime
handle it directly while parsing. Two steps: (1) Parse string into LocalDateTime
, (2) Feed LocalDateTime
object and ZoneId
object to ZonedDateTime
. To correctly handle an input string with "20:54" that day, we need parse it as a LocalDateTime
, then ask ZonedDateTime
to use the specified time zone to make an adjustment (resulting in "03:54", I think -- read the class doc for details and logic used in adjustment behavior).
So we need to add to your code, calling ZonedDateTime
. Using the LocalDateTime
object you created, we need to specify a ZoneId
object for ZonedDateTime
to use in completing the transformation to a ZonedDateTime
.
Proper Time Zone Names
You said the input string is in "Eastern Time". I'm afraid to tell you there is no such thing. The "EST", "EDT", and other such 3-4 letter codes are not official, not standardized, and not unique. You need to learn to use proper time zone names. Perhaps you mean America/New_York
(note the underscore) or America/Montreal
or some such zone. I will arbitrarily go with New York.
Variable Naming
Note how I've changed your variable names. Naming variables is generally quite important for clarity and later maintenance, but even more so for date-time work.
ISO 8601
By the way, a better way to exchange data of date-time values via strings is to use the ISO 8601 formats such as 2015-10-15T13:21:09Z
. These formats include an offset-from-UTC such as the Z
(Zulu, UTC) shown in previous sentence. The java.time framework wisely extends the ISO 8601 formats by appending the name of the time zone in brackets. Passing around date-time strings with no offset or time zone info is asking for trouble.
Example code.
Here is some sample code in Java 8. First we parse the string into a LocalDateTime
object.
// Parse input string into a LocalDateTime object.
String input = "2009/10/09 11:00";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern ( "yyyy/MM/dd HH:mm" );
LocalDateTime localDateTime = LocalDateTime.parse ( input , formatter );
Transform that amorphous LocalDateTime
into an actual moment on the time line by assigning a time zone. We assume that input string represents the wall-clock time in Poughkeepsie which uses the New York time zone. So we get a ZoneId
object for New York time zone.
// Specify the time zone we expect is implied for this input string.
ZoneId zoneId = ZoneId.of ( "America/New_York" );
ZonedDateTime zdtNewYork = ZonedDateTime.of ( localDateTime , zoneId );
You can easily adjust into other time zones. I'll arbitrarily show India time as provides a contrast in two ways: ahead of UTC rather than behind, and its offset is not in whole hours (+05:30
).
ZonedDateTime zdtKolkata = zdtNewYork.withZoneSameInstant ( ZoneId.of ( "Asia/Kolkata" ) );
We can do date-time calculation like adding four hours. Because we have a ZonedDateTime
, that class handles adjustments needed for anomalies such as Daylight Saving Time.
ZonedDateTime later = zdtNewYork.plusHours ( 4 );
For a UTC time zone, you can go either of two ways.
- Assign a time zone as you would for any other time zone, but note the handy constant defined in
ZoneOffset
(a subclass of ZoneId
).
- Or, alternatively, extract the
Instant
from within the ZonedDateTime
. An Instant
is always in UTC by definition.
Either way represents the same moment on the timeline. But notice in output below how each has a different format used by default in their respective toString
implementation.
ZonedDateTime zdtUtc = zdtNewYork.withZoneSameInstant ( ZoneOffset.UTC );
Instant instant = zdtNewYork.toInstant ();
Dump to console.
System.out.println ( "input: " + input );
System.out.println ( "localDateTime: " + localDateTime );
System.out.println ( "zdtNewYork: " + zdtNewYork );
System.out.println ( "zdtKolkata: " + zdtKolkata );
System.out.println ( "zdtUtc: " + zdtUtc );
System.out.println ( "instant: " + instant );
System.out.println ( "later: " + later );
When run.
input: 2009/10/09 11:00
localDateTime: 2009-10-09T11:00
zdtNewYork: 2009-10-09T11:00-04:00[America/New_York]
zdtKolkata: 2009-10-09T20:30+05:30[Asia/Kolkata]
zdtUtc: 2009-10-09T15:00Z
instant: 2009-10-09T15:00:00Z
later: 2009-10-09T15:00-04:00[America/New_York]
Database Query
As for querying a database, search StackOverflow as that has been handled exhaustively already. Upshot: In the future JDBC should be able to use the java.time data types shown here. Until then, convert to a java.sql.Timestamp
object. Convenient conversion methods provided for you, such as java.sql.Timestamp.from( Instant instant )
.
java.sql.Timestamp ts = java.sql.Timestamp.from( zdtNewYork.toInstant () );