Timothy Clotworthy
Timothy Clotworthy

Reputation: 2452

Java: How to Convert LocalDateTime value to Date and Preserve Time Portion

I have a need to convert from a LocalDateTime with both a date and time component to a Date without losing the time part. I have been doing something like:

LocalDateTime ldt = //some full date-time value

Date date = java.util.Date.valueOf(ldt.toLocalDate());

, but am obviously losing the time part..I see there is also a LocalDateTime.toLocalTime(), however I need both date and time together.

Thank you

Upvotes: 1

Views: 1353

Answers (3)

Basil Bourque
Basil Bourque

Reputation: 338171

tl;dr

Apparently, you inadvertently used the class java.sql.Date where you thought you were using java.util.Date. But that issue is moot: use java.time classes instead of those terrible old date-time classes.

myPreparedStatement.setObject( … , myLocalDateTime ) ;

…and…

myResultSet.getObject( … , LocalDateTime.class ) 

Check your imports

Apparently you are inadvertently using java.sql.Date which pretends to have no time of day. Unfortunately, it’s terrible class design inherits from java.util.Date which does have a time-of-day. So the sql sets its time to 00:00:00. And the documentation tells us to ignore the fact of their inheritance. Terrible design, terrible names, terrible classes all around.

Avoid legacy date-time classes

For all these reasons and more, these old classes were supplanted years ago by the java.time classes.

JDBC 4.2

As of JDBC 4.2, we can exchange java.time classes with the database. No need to ever touch java.util.Date, java.sql.Date, java.sql.Timestamp again.

Without zone

A LocalDateTime should be stored in a database column of a type akin to the SQL-standard type TIMESTAMP WITHOUT TIME ZONE. These type intentionally lack any concept of time zone or offset-from-UTC. As such, these types cannot represent a moment, cannot hold a specific point on the timeline. These are likely not what you want in most business scenarios.

myPreparedStatement.setObject( … , myLocalDateTime ) ;

Retrieval:

myLocalDateTime = myResultSet.getObject( … , LocalDateTime.class ) ;

If they cannot represent a moment, what good are these types? In what scenarios is the use of java.time.LocalDateTime class & TIMESTAMP WITHOUT TIME ZONE column appropriate? These three:

  • The zone or offset is unknown.
    This is bad. This is faulty data. Analogous to having a price/cost without knowing the currency. You should be rejecting such data, not storing it.
  • The intention is “everywhere”, as in, every time zone.
    For example, a corporate policy that states “All our factories will break for lunch at 12:30" means the factory in Delhi will break hours before the factory in Düsseldorf which breaks hours before the factory in Detroit.
  • A specific moment in the future is intended, but we are afraid of politicians redefining the time zone.
    Governments change the rules of their time zones with surprising frequency and with surprisingly little warning or even no warning at all. So if you want to book an appointment at 3 PM on a certain date, and you really mean 3 PM regardless of any crazy decision a government might make in the interim, then store a LocalDateTime. To print a report or display a calendar, dynamically apply a time zone (ZoneId) to generate a specific moment (ZonedDateTime or Instant). This must be done on-the-fly rather than storing the value.

With zone

In contrast, to store a moment, use a database column of a type akin to the SQL-standard type TIMESTAMP WITH TIME ZONE. For such a column, in Java use Instant class. This class represents a moment in UTC with a resolution of nanoseconds.

myPreparedStatement.setObject( … , myInstant ) ;

Retrieval:

myInstant = myResultSet.getObject( … , Instant.class ) ;

Applying time zone

If you are certain your LocalDateTime was meant implicitly to represent a moment in the wall-clock time used by the people of a particular region (a time zone), apply a ZoneId to get a ZonedDateTime.

Specify a proper time zone name in the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 3-4 letter abbreviation such as EST or IST as they are not true time zones, not standardized, and not even unique(!).

ZoneId z = ZoneId.of( "Pacific/Auckland" ) ;
ZonedDateTime zdt = myLocalDateTime.atZone( z ) ;

Then extract a Instant object to adjust into UTC.

Instant instant = zdt.toInstant(); 

Store in database.

myPreparedStatement.setObject( … , instant ) ;

Retrieve from database.

Instant instant = myResultSet.getObject( … , Instant.class ) ;

About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.

Where to obtain the java.time classes?

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

Upvotes: 0

Michael Gantman
Michael Gantman

Reputation: 7792

java.util.Date does hold time component if you provide it. But in the first place why would you want to work with old Date rather then keep with the new java 8 java.time package? In any case to answer your question I suggest to retrieve epoch milliseconds and then create your Date from that. So It would look something like this:

long epoch = ZonedDateTime.of(ldt, ZoneId.systemDefault()).toInstant().toEpochMilli();
Date date = new Date(epoch);

Due to request from Basil Bourque - an explanation why ZonedDateTime is needed: in order to extract epoch - number of milliseconds from "1.1.1970 00:00:00+0000" you will need to convert LocalDateTime (which is not absolute time and could mean a different instant in time in different locations) to ZonedDateTime that also holds a location and thus is absolute time. Thus from ZonedDateTime you can extract the epoch.

Upvotes: 0

Iris Hunkeler
Iris Hunkeler

Reputation: 208

That is because java.sql.Date doesn't include the time.

You could use the class java.sql.Timestamp instead, which includes both date and time.

Upvotes: 4

Related Questions