Java 8 has a completely new API for date and time. One of the most useful classes in this API is LocalDateTime
, for holding a timezone-independent date-with-time value.
There are probably millions of lines of code using the legacy class java.util.Date
for this purpose. As such, when interfacing old and new code there will be a need for converting between the two. As there seems to be no direct methods for accomplishing this, how can it be done?
Short answer:
Date in = new Date();
LocalDateTime ldt = LocalDateTime.ofInstant(in.toInstant(), ZoneId.systemDefault());
Date out = Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant());
Explanation: (based on this question about LocalDate
)
Despite its name, java.util.Date
represents an instant on the time-line, not a "date". The actual data stored within the object is a long
count of milliseconds since 1970-01-01T00:00Z (midnight at the start of 1970 GMT/UTC).
The equivalent class to java.util.Date
in JSR-310 is Instant
, thus there are convenient methods to provide the conversion to and fro:
Date input = new Date();
Instant instant = input.toInstant();
Date output = Date.from(instant);
A java.util.Date
instance has no concept of time-zone. This might seem strange if you call toString()
on a java.util.Date
, because the toString
is relative to a time-zone. However that method actually uses Java's default time-zone on the fly to provide the string. The time-zone is not part of the actual state of java.util.Date
.
An Instant
also does not contain any information about the time-zone. Thus, to convert from an Instant
to a local date-time it is necessary to specify a time-zone. This might be the default zone - ZoneId.systemDefault()
- or it might be a time-zone that your application controls, such as a time-zone from user preferences. LocalDateTime
has a convenient factory method that takes both the instant and time-zone:
Date in = new Date();
LocalDateTime ldt = LocalDateTime.ofInstant(in.toInstant(), ZoneId.systemDefault());
In reverse, the LocalDateTime
the time-zone is specified by calling the atZone(ZoneId)
method. The ZonedDateTime
can then be converted directly to an Instant
:
LocalDateTime ldt = ...
ZonedDateTime zdt = ldt.atZone(ZoneId.systemDefault());
Date output = Date.from(zdt.toInstant());
Note that the conversion from LocalDateTime
to ZonedDateTime
has the potential to introduce unexpected behaviour. This is because not every local date-time exists due to Daylight Saving Time. In autumn/fall, there is an overlap in the local time-line where the same local date-time occurs twice. In spring, there is a gap, where an hour disappears. See the Javadoc of atZone(ZoneId)
for more the definition of what the conversion will do.
Summary, if you round-trip a java.util.Date
to a LocalDateTime
and back to a java.util.Date
you may end up with a different instant due to Daylight Saving Time.
Additional info: There is another difference that will affect very old dates. java.util.Date
uses a calendar that changes at October 15, 1582, with dates before that using the Julian calendar instead of the Gregorian one. By contrast, java.time.*
uses the ISO calendar system (equivalent to the Gregorian) for all time. In most use cases, the ISO calendar system is what you want, but you may see odd effects when comparing dates before year 1582.
Here is what I came up with ( and like all Date Time conundrums it is probably going to be disproved based on some weird timezone-leapyear-daylight adjustment :D )
Round-tripping: Date <<->> LocalDateTime
Given: Date date = [some date]
(1) LocalDateTime
<< Instant
<< Date
Instant instant = Instant.ofEpochMilli(date.getTime());
LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);
(2) Date
<< Instant
<< LocalDateTime
Instant instant = ldt.toInstant(ZoneOffset.UTC);
Date date = Date.from(instant);
Example:
Given:
Date date = new Date();
System.out.println(date + " long: " + date.getTime());
(1) LocalDateTime << Instant<< Date:
Create Instant
from Date
:
Instant instant = Instant.ofEpochMilli(date.getTime());
System.out.println("Instant from Date:\n" + instant);
Create Date
from Instant
(not necessary,but for illustration):
date = Date.from(instant);
System.out.println("Date from Instant:\n" + date + " long: " + date.getTime());
Create LocalDateTime
from Instant
LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);
System.out.println("LocalDateTime from Instant:\n" + ldt);
(2) Date << Instant << LocalDateTime
Create Instant
from LocalDateTime
:
instant = ldt.toInstant(ZoneOffset.UTC);
System.out.println("Instant from LocalDateTime:\n" + instant);
Create Date
from Instant
:
date = Date.from(instant);
System.out.println("Date from Instant:\n" + date + " long: " + date.getTime());
The output is:
Fri Nov 01 07:13:04 PDT 2013 long: 1383315184574
Instant from Date:
2013-11-01T14:13:04.574Z
Date from Instant:
Fri Nov 01 07:13:04 PDT 2013 long: 1383315184574
LocalDateTime from Instant:
2013-11-01T14:13:04.574
Instant from LocalDateTime:
2013-11-01T14:13:04.574Z
Date from Instant:
Fri Nov 01 07:13:04 PDT 2013 long: 1383315184574
Instant.ofEpochMilli(date.getTime())
do date.toInstant()
toInstant()
looks nice, except it fails for java.sql.Date
, arggggh! So it's finally easier to use Instant.ofEpochMilli(date.getTime())
.
Much more convenient way if you are sure you need a default timezone :
Date d = java.sql.Timestamp.valueOf( myLocalDateTime );
The fastest way for LocalDateTime
-> Date
is:
Date.from(ldt.toInstant(ZoneOffset.UTC))
Everything is here : http://blog.progs.be/542/date-to-java-time
The answer with "round-tripping" is not exact : when you do
LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);
if your system timezone is not UTC/GMT, you change the time !
the following seems to work when converting from new API LocalDateTime into java.util.date:
Date.from(ZonedDateTime.of({time as LocalDateTime}, ZoneId.systemDefault()).toInstant());
the reverse conversion can be (hopefully) achieved similar way...
hope it helps...
If you are on android and using threetenbp you can use DateTimeUtils
instead.
ex:
Date date = DateTimeUtils.toDate(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
you can't use Date.from
since it's only supported on api 26+
I'm not sure if this is the simplest or best way, or if there are any pitfalls, but it works:
static public LocalDateTime toLdt(Date date) {
GregorianCalendar cal = new GregorianCalendar();
cal.setTime(date);
ZonedDateTime zdt = cal.toZonedDateTime();
return zdt.toLocalDateTime();
}
static public Date fromLdt(LocalDateTime ldt) {
ZonedDateTime zdt = ZonedDateTime.of(ldt, ZoneId.systemDefault());
GregorianCalendar cal = GregorianCalendar.from(zdt);
return cal.getTime();
}
LocalDateTime
to Date
. At daylight saving transitions, a LocalDateTime
can be non-existent, or occur twice. You need to work out what you want to happen in each case.
GregorianCalendar
belongs old awkward API that new java.time
API aims to replace
I think below approach will solve the conversion without taking time-zone into consideration. Please comment if it has any pitfalls.
LocalDateTime datetime //input
public static final DateTimeFormatter yyyyMMddHHmmss_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatDateTime = datetime.format(yyyyMMddHHmmss_DATE_FORMAT);
Date outputDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(formatDateTime); //output
Success story sharing
java.util.Date
does not contains timezone, but print it duringtoString()
. Event official documentation does not tell this clearly as you post.LocalDateTime.ofInstant(date.toInstant()...
does not behave as one would naively expect. For examplenew Date(1111-1900,11-1,11,0,0,0);
will become1111-11-17 23:53:28
using this approach. Take a look at the implementation ofjava.sql.Timestamp#toLocalDateTime()
if you needed the result to be1111-11-11 00:00:00
in the previous example.java.sql.Date#toInstant
throws anUnsupportedOperationException
. So don't usetoInstant
in a RowMapper onjava.sql.ResultSet#getDate
.