531

What is the best way to convert a java.util.Date object to the new JDK 8/JSR-310 java.time.LocalDate?

Date input = new Date();
LocalDate date = ???
JodaStephen
  • 53,561
  • 13
  • 87
  • 108

13 Answers13

876

Short answer

Date input = new Date();
LocalDate date = input.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();

Explanation

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 is a convenient method toInstant() to provide the conversion:

Date input = new Date();
Instant instant = input.toInstant();

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 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. Use the atZone() method to apply the time-zone:

Date input = new Date();
Instant instant = input.toInstant();
ZonedDateTime zdt = instant.atZone(ZoneId.systemDefault());

A ZonedDateTime contains state consisting of the local date and time, time-zone and the offset from GMT/UTC. As such the date - LocalDate - can be easily extracted using toLocalDate():

Date input = new Date();
Instant instant = input.toInstant();
ZonedDateTime zdt = instant.atZone(ZoneId.systemDefault());
LocalDate date = zdt.toLocalDate();

Java 9 answer

In Java SE 9, a new method has been added that slightly simplifies this task:

Date input = new Date();
LocalDate date = LocalDate.ofInstant(input.toInstant(), ZoneId.systemDefault());

This new alternative is more direct, creating less garbage, and thus should perform better.

JodaStephen
  • 53,561
  • 13
  • 87
  • 108
  • 15
    I had `LocalDate.from(Instant.ofEpochMilli(date.getTime()))` I think it's equivalent to yours, but more direct. – Marko Topolnik Jan 20 '14 at 19:12
  • 10
    @MarkoTopolnik that compiles but does not run. An Instant does not contain a time-zone thus there is no way to get the LocalDate. – JodaStephen Jan 20 '14 at 19:17
  • @assylias I don't understand your 2nd statement. – Rohit Jain Jan 20 '14 at 19:18
  • I have just compiled and run it, and it works. Using ThreeTen implementation. – Marko Topolnik Jan 20 '14 at 19:23
  • 2
    @JodaStephen How about the method used in java.sql.Date: `return LocalDate.of(getYear() + 1900, getMonth() + 1, getDate())`? – assylias Jan 20 '14 at 19:23
  • 3
    To confirm, I have also run it against Java 8 and there it fails. So apparently the ThreeTen implementation diverges from Java 8. – Marko Topolnik Jan 20 '14 at 19:26
  • 12
    @assylias Just use sqlDate.toLocalDate() ! – JodaStephen Jan 20 '14 at 19:49
  • @JodaStephen There is some confusion here because both assylias and I were struggling to make this work on Java 7 + ThreeTen. Of course, you can't count on any instance methods of `java.*l.Date`. assylias extracted the source code from Java 8 `java.sql.Date#toLocalDate()` and I drilled a route through the ThreeTen API, which turns out not to be compatible with Java 8. – Marko Topolnik Jan 21 '14 at 08:48
  • 2
    The kind of quality (posts) this guy produces is incredible! Would +10 if I could – avalancha Feb 06 '14 at 08:46
  • 28
    @JodaStephen `Date` has no concept of time-zone, `Instant` also does not contain information about time-zone. The `LocalDate` API says "A date without a time-zone". Then why converting from `Date` to `Instant` to `LocalDate` needs `atZone(ZoneId.systemDefault())`? – Gustavo May 22 '15 at 13:28
  • 9
    @Gustavo: `LocalDate` and `LocalDateTime` do not "store or represent a time or time-zone" (ref: javadocs). While they don't store it- the classes do represent a *`Local`* date and/or time, hence the conversion to *local* date/time implies a timezone. – Cuga Aug 19 '15 at 15:58
  • 3
    @avalancha well, he does have a slight advantage in respect of the `java.time`, being the primary author of JodaTime, and a member of the JSR 310 expert group :-) He's thought more deeply about this problem domain than you or I are likely to need to! – David Bullock Nov 06 '15 at 02:04
  • 1
    I found `timestamp.toLocalDateTime().toLocalDate()` (a `java.sql.timestamp`-Object) in some code of us. Is it equivalent (enough) or is the code given above the better option? Are there differences in using it with `java.sql.timestamp` rather than `java.util.Date`? – julianwki Feb 25 '16 at 10:50
  • @SnakeDoc - I think you're mistaken... I see no such method in the Oracle documentation of `java.util.Date`: https://docs.oracle.com/javase/8/docs/api/java/util/Date.html – ArtOfWarfare Feb 23 '17 at 20:21
  • @ArtOfWarfare Sorry, I meant for `java.sql.Date`: https://docs.oracle.com/javase/8/docs/api/java/sql/Date.html#toLocalDate-- – SnakeDoc Feb 24 '17 at 15:59
  • 2
    @Gustavo what date is new `Date(0)`? that depends on the time zone. In Hawaii its `1969-12-31` but in Australia its `1970-01-01`. That is why you need to define a time zone to make a meaningful conversion. So `ZoneId.systemDefault()` may not be correct for you as that is the time zone the system (JVM) has. – Jens Jun 06 '18 at 09:19
  • In some input cases `myDate.toInstant()` gives ab Unsupported operation exception: http://i.imgur.com/HEJbpFo.png Therefore Oliv's answer is more correct. – Zon Jan 23 '20 at 05:12
  • LocalDate requires APİ 26 but my minSdk is 19. Thus i cannot use LocalDate. Is there a solution or workaround for that ? – oiyio May 06 '21 at 08:35
170

Better way is:

Date date = ...;
Instant.ofEpochMilli(date.getTime()).atZone(ZoneId.systemDefault()).toLocalDate()

Advantages of this version:

  • works regardless the input is an instance of java.util.Date or it's a subclass of java.sql.Date (unlike @JodaStephen's way). This is common with JDBC-originated data. java.sql.Date.toInstant() always throws an exception.

  • it's the same for JDK8 and JDK7 with JSR-310 backport

I personally use an utility class (but it is not backport-compatible):

/**
 * Utilities for conversion between the old and new JDK date types 
 * (between {@code java.util.Date} and {@code java.time.*}).
 * 
 * <p>
 * All methods are null-safe.
 */
public class DateConvertUtils {

    /**
     * Calls {@link #asLocalDate(Date, ZoneId)} with the system default time zone.
     */
    public static LocalDate asLocalDate(java.util.Date date) {
        return asLocalDate(date, ZoneId.systemDefault());
    }

    /**
     * Creates {@link LocalDate} from {@code java.util.Date} or it's subclasses. Null-safe.
     */
    public static LocalDate asLocalDate(java.util.Date date, ZoneId zone) {
        if (date == null)
            return null;

        if (date instanceof java.sql.Date)
            return ((java.sql.Date) date).toLocalDate();
        else
            return Instant.ofEpochMilli(date.getTime()).atZone(zone).toLocalDate();
    }

    /**
     * Calls {@link #asLocalDateTime(Date, ZoneId)} with the system default time zone.
     */
    public static LocalDateTime asLocalDateTime(java.util.Date date) {
        return asLocalDateTime(date, ZoneId.systemDefault());
    }

    /**
     * Creates {@link LocalDateTime} from {@code java.util.Date} or it's subclasses. Null-safe.
     */
    public static LocalDateTime asLocalDateTime(java.util.Date date, ZoneId zone) {
        if (date == null)
            return null;

        if (date instanceof java.sql.Timestamp)
            return ((java.sql.Timestamp) date).toLocalDateTime();
        else
            return Instant.ofEpochMilli(date.getTime()).atZone(zone).toLocalDateTime();
    }

    /**
     * Calls {@link #asUtilDate(Object, ZoneId)} with the system default time zone.
     */
    public static java.util.Date asUtilDate(Object date) {
        return asUtilDate(date, ZoneId.systemDefault());
    }

    /**
     * Creates a {@link java.util.Date} from various date objects. Is null-safe. Currently supports:<ul>
     * <li>{@link java.util.Date}
     * <li>{@link java.sql.Date}
     * <li>{@link java.sql.Timestamp}
     * <li>{@link java.time.LocalDate}
     * <li>{@link java.time.LocalDateTime}
     * <li>{@link java.time.ZonedDateTime}
     * <li>{@link java.time.Instant}
     * </ul>
     * 
     * @param zone Time zone, used only if the input object is LocalDate or LocalDateTime.
     * 
     * @return {@link java.util.Date} (exactly this class, not a subclass, such as java.sql.Date)
     */
    public static java.util.Date asUtilDate(Object date, ZoneId zone) {
        if (date == null)
            return null;

        if (date instanceof java.sql.Date || date instanceof java.sql.Timestamp)
            return new java.util.Date(((java.util.Date) date).getTime());
        if (date instanceof java.util.Date)
            return (java.util.Date) date;
        if (date instanceof LocalDate)
            return java.util.Date.from(((LocalDate) date).atStartOfDay(zone).toInstant());
        if (date instanceof LocalDateTime)
            return java.util.Date.from(((LocalDateTime) date).atZone(zone).toInstant());
        if (date instanceof ZonedDateTime)
            return java.util.Date.from(((ZonedDateTime) date).toInstant());
        if (date instanceof Instant)
            return java.util.Date.from((Instant) date);

        throw new UnsupportedOperationException("Don't know hot to convert " + date.getClass().getName() + " to java.util.Date");
    }

    /**
     * Creates an {@link Instant} from {@code java.util.Date} or it's subclasses. Null-safe.
     */
    public static Instant asInstant(Date date) {
        if (date == null)
            return null;
        else
            return Instant.ofEpochMilli(date.getTime());
    }

    /**
     * Calls {@link #asZonedDateTime(Date, ZoneId)} with the system default time zone.
     */
    public static ZonedDateTime asZonedDateTime(Date date) {
        return asZonedDateTime(date, ZoneId.systemDefault());
    }

    /**
     * Creates {@link ZonedDateTime} from {@code java.util.Date} or it's subclasses. Null-safe.
     */
    public static ZonedDateTime asZonedDateTime(Date date, ZoneId zone) {
        if (date == null)
            return null;
        else
            return asInstant(date).atZone(zone);
    }

}

The asLocalDate() method here is null-safe, uses toLocalDate(), if input is java.sql.Date (it may be overriden by the JDBC driver to avoid timezone problems or unnecessary calculations), otherwise uses the abovementioned method.

Oliv
  • 8,930
  • 2
  • 43
  • 70
  • 12
    If this is the better way, it is very ugly and verbose. So painful. – ceklock Nov 06 '15 at 13:58
  • 5
    It is better in comparison to the accepted answer, I explain why. Yes, it is ugly, but that's why wrote `DateConvertUtils`. – Oliv Nov 11 '15 at 11:07
  • I don't understand why they didn't implement a conversion class in the new API. – ceklock Jan 15 '17 at 01:39
  • @ceklock, they *did* implement, not a conversion class, but a couple of conversion methods, like `Date.toInstant()`. – Ole V.V. Mar 26 '17 at 09:29
  • 2
    Is there a Kotlin library that packages these as extension functions so that a developer can do the following. Date x = ...; x.asLocalDate(); – Mark Ashworth Feb 08 '18 at 12:22
  • @MarkAshworth I don't know of any – Oliv Feb 08 '18 at 14:50
  • @Oliv Its old but is not a bad practice return a null, can you explain to me why it would be a good decision? or there are some restrictions where you can do it, thanks – Jose Jun 24 '19 at 01:16
  • @Jose What's wrong with returning null? If you pass in a null, what would you expect to return? To throw?? – Oliv Jun 25 '19 at 07:19
  • The need for specifying the timezone it's what irks me given that it is said the date is an instance in time, not if a time – TheRealChx101 Apr 29 '20 at 04:38
22
LocalDate localDate = LocalDate.parse( new SimpleDateFormat("yyyy-MM-dd").format(date) );
ceklock
  • 5,596
  • 10
  • 52
  • 75
  • 9
    Here, the `SimpleDateFormat` instance is *confined* to the current thread. It is *used* in a thread-safe way. Now, `SimpleDateFormat` is reputed to be 'expensive to instantiate' (on account of all the internal data structures it needs) but you can't **share** one as a 'singleton' (without synchronizing access to it), because *it* is indeed not thread-safe. (A `ThreadLocal` solution can work *if* the code 'polluting' the `Thread` in this was is responsible for the thread's lifecycle ... but that rarely happens). Awkward. Avoiding `SimpleDateFormat` is *the* reason for using `javax.time`. – David Bullock Nov 06 '15 at 01:53
  • 7
    This approach has a lot of overhead: the 'expensive' `SimpleDateFormat` (which is thrown away), the intermediate string (which is thrown away), and the cost of parsing. It's *a* solution, but not recommended. – David Bullock Nov 06 '15 at 02:02
  • 2
    @ceklock but then you get the problem that it's no longer thread-safe – flup Dec 13 '16 at 11:20
  • 4
    @ceklock the SimpleDateFormat can only process one date at a time, if you use it concurrently, it'll yield mangled results. So yes, it is important. Don't create a single instance of SimpleDateFormat in a global variable. – flup Dec 20 '16 at 17:40
20

If you're using Java 8, @JodaStephen's answer is obviously the best. However, if you're working with the JSR-310 backport, you unfortunately have to do something like this:

Date input = new Date();
Calendar cal = Calendar.getInstance();
cal.setTime(input);
LocalDate date = LocalDate.of(cal.get(Calendar.YEAR),
        cal.get(Calendar.MONTH) + 1,
        cal.get(Calendar.DAY_OF_MONTH));
dhalsim2
  • 875
  • 2
  • 11
  • 34
  • 6
    Not true, @JodaStephen's answer still works. You just need another way to convert java.util.Date to an Instant. For this you can use org.threeten.bp.DateTimeUtils.toInstant: http://www.threeten.org/threetenbp/apidocs/org/threeten/bp/DateTimeUtils.html – Christian Ciach Mar 18 '16 at 09:38
  • 3
    DateTimeUtils wasn't available when I was using the backport, but you are correct that it is available to anyone using ThreeTen Backport 1.0 or later. Thanks for pointing it out. – dhalsim2 Mar 18 '16 at 16:15
  • @ChristianCiach You are correct. I spelled out what you are saying in [this answer](https://stackoverflow.com/a/63068341/5772882). – Ole V.V. Jul 24 '20 at 06:56
15
LocalDate ld = new java.sql.Date( new java.util.Date().getTime() ).toLocalDate();
Magnilex
  • 10,219
  • 8
  • 49
  • 72
Gustavo
  • 1,087
  • 1
  • 13
  • 28
10

You can convert in one line :

public static LocalDate getLocalDateFromDate(Date date){
   return LocalDate.from(Instant.ofEpochMilli(date.getTime()).atZone(ZoneId.systemDefault()));
}
Sahil Chhabra
  • 7,481
  • 4
  • 53
  • 53
  • I get an error like this using this method: java.time.DateTimeException: Unable to obtain LocalDate from TemporalAccessor: 2018-01-31T11:54:27.964Z of type java.time.Instant – Luigi Rubino Jan 31 '18 at 11:55
  • @LuigiRubino thanks for pointing out. Please see the updated answer. I forgot to add Zone earlier. – Sahil Chhabra Jan 31 '18 at 16:59
8

first, it's easy to convert a Date to an Instant

Instant timestamp = new Date().toInstant(); 

Then, you can convert the Instant to any date api in jdk 8 using ofInstant() method:

LocalDateTime date = LocalDateTime.ofInstant(timestamp, ZoneId.systemDefault()); 
Shedom Wei
  • 889
  • 12
  • 25
  • includes localDate, localDateTime, localTime – Shedom Wei Feb 24 '18 at 02:40
  • What does it mean to specify ZoneId here. If I get Date -> Instant from API and I can think about it as milliseconds from 1970 in UTC. When I want to get just corresponding LocalDate (yyyy-mm-dd) from perspective of api not converted to any timezone, shouldn't I use ZoneOffset.UTC to not have this instant offset to my local timezone. Isn't your example with ZoneId.systemDefault() is shifting date form server. Ex. instant represents 2018-10-20 0:00 and then shifted from UTC to America/Los_Angeles I get in Local Date 2018-10-19 instead of 2018-10-20? – Michał Ziobro Nov 16 '18 at 10:26
  • It breaks if you happen to have `import java.sql.Date` in your file: the `toInstant()` method of `java.sql.Date` always throws. – Oliv Dec 23 '19 at 09:24
4
Date input = new Date();
LocalDateTime  conv=LocalDateTime.ofInstant(input.toInstant(), ZoneId.systemDefault());
LocalDate convDate=conv.toLocalDate();

The Date instance does contain time too along with the date while LocalDate doesn't. So you can firstly convert it into LocalDateTime using its method ofInstant() then if you want it without time then convert the instance into LocalDate.

Arun Raaj
  • 1,520
  • 1
  • 15
  • 19
1
public static LocalDate Date2LocalDate(Date date) {
        return LocalDate.parse(date.toString(), DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss zzz yyyy"))

this format is from Date#tostring

    public String toString() {
        // "EEE MMM dd HH:mm:ss zzz yyyy";
        BaseCalendar.Date date = normalize();
        StringBuilder sb = new StringBuilder(28);
        int index = date.getDayOfWeek();
        if (index == BaseCalendar.SUNDAY) {
            index = 8;
        }
        convertToAbbr(sb, wtb[index]).append(' ');                        // EEE
        convertToAbbr(sb, wtb[date.getMonth() - 1 + 2 + 7]).append(' ');  // MMM
        CalendarUtils.sprintf0d(sb, date.getDayOfMonth(), 2).append(' '); // dd

        CalendarUtils.sprintf0d(sb, date.getHours(), 2).append(':');   // HH
        CalendarUtils.sprintf0d(sb, date.getMinutes(), 2).append(':'); // mm
        CalendarUtils.sprintf0d(sb, date.getSeconds(), 2).append(' '); // ss
        TimeZone zi = date.getZone();
        if (zi != null) {
            sb.append(zi.getDisplayName(date.isDaylightTime(), TimeZone.SHORT, Locale.US)); // zzz
        } else {
            sb.append("GMT");
        }
        sb.append(' ').append(date.getYear());  // yyyy
        return sb.toString();
    }
宏杰李
  • 10,658
  • 2
  • 21
  • 32
0

I have had problems with @JodaStephen's implementation on JBoss EAP 6. So, I rewrote the conversion following Oracle's Java Tutorial in http://docs.oracle.com/javase/tutorial/datetime/iso/legacy.html.

    Date input = new Date();
    GregorianCalendar gregorianCalendar = (GregorianCalendar) Calendar.getInstance();
    gregorianCalendar.setTime(input);
    ZonedDateTime zonedDateTime = gregorianCalendar.toZonedDateTime();
    zonedDateTime.toLocalDate();
rogerio_gentil
  • 319
  • 1
  • 3
  • 15
0

If you are using ThreeTen Backport including ThreeTenABP

    Date input = new Date(); // Imagine your Date here
    LocalDate date = DateTimeUtils.toInstant(input)
            .atZone(ZoneId.systemDefault())
            .toLocalDate();

If you are using the backport of JSR 310, either you haven’t got a Date.toInstant() method or it won’t give you the org.threeten.bp.Instant that you need for you further conversion. Instead you need to use the DateTimeUtils class that comes as part of the backport. The remainder of the conversion is the same, so so is the explanation.

Ole V.V.
  • 65,573
  • 11
  • 96
  • 117
-1

What's wrong with this 1 simple line?

new LocalDateTime(new Date().getTime()).toLocalDate();
Lawrence
  • 989
  • 12
  • 7
  • Java complains that it cannot invoke toLocalDate() on the primitive type long. I used an actual Date object; perhaps there's the rub?? – TheGeeko61 Mar 02 '18 at 08:36
-8

I solved this question with solution below

  import org.joda.time.LocalDate;
  Date myDate = new Date();
  LocalDate localDate = LocalDate.fromDateFields(myDate);
  System.out.println("My date using Date" Nov 18 11:23:33 BRST 2016);
  System.out.println("My date using joda.time LocalTime" 2016-11-18);

In this case localDate print your date in this format "yyyy-MM-dd"

estevamdf
  • 25
  • 4
  • 1
    The question is looking for a solution using the `java.time` classes in JDK8, not with Joda Time. – pioto Dec 21 '16 at 19:06