แปลง java.util.Date เป็น java.time.LocalDate


494

วิธีที่ดีที่สุดในการแปลงjava.util.Dateวัตถุเป็น JDK 8 / JSR-310 ใหม่java.time.LocalDateคืออะไร?

Date input = new Date();
LocalDate date = ???

คำตอบ:


828

คำตอบสั้น ๆ

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

คำอธิบาย

แม้จะมีชื่อjava.util.Dateแสดงถึงการโต้ตอบแบบทันทีในเส้นเวลาไม่ใช่ "วันที่" ข้อมูลจริงที่จัดเก็บภายในวัตถุนั้นlongนับเป็นมิลลิวินาทีตั้งแต่ 1970-01-01T00: 00Z (เที่ยงคืนเมื่อเริ่มต้น 1970 GMT / UTC)

คลาสเทียบเท่ากับjava.util.Dateใน JSR-310 Instantจึงมีวิธีที่สะดวกในการtoInstant()ให้การแปลง:

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

java.util.Dateเช่นมีแนวคิดของโซนเวลาไม่มี นี่อาจดูแปลกถ้าคุณโทรหาtoString()a java.util.DateเพราะtoStringมันสัมพันธ์กับเขตเวลา อย่างไรก็ตามวิธีการนั้นใช้เขตเวลาเริ่มต้นของ Java ในการบินเพื่อจัดเตรียมสตริง java.util.DateThe-โซนเวลาไม่ได้เป็นส่วนหนึ่งของรัฐที่แท้จริงของ

Instantยังไม่ได้มีข้อมูลใด ๆ เกี่ยวกับโซนเวลา ดังนั้นการแปลงจากInstantวันที่ในท้องถิ่นเป็นสิ่งจำเป็นในการระบุเขตเวลา นี่อาจเป็นเขตเริ่มต้น - ZoneId.systemDefault()- หรืออาจเป็นเขตเวลาที่แอปพลิเคชันของคุณควบคุมเช่นเขตเวลาจากการตั้งค่าของผู้ใช้ ใช้atZone()วิธีการเพื่อใช้เขตเวลา:

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

A ZonedDateTimeประกอบด้วยรัฐประกอบด้วยวันที่และเวลาท้องถิ่น, เขตเวลาและออฟเซ็ตจาก GMT / UTC เช่นวันที่ -LocalDate - สามารถสกัดได้ง่ายโดยใช้toLocalDate():

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

Java 9 คำตอบ

ใน Java SE 9 เป็นวิธีการใหม่มีการเพิ่มที่ลดความซับซ้อนของงานนี้:

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

ทางเลือกใหม่นี้โดยตรงมากขึ้นสร้างขยะน้อยลงและควรทำงานได้ดีขึ้น


14
ฉันLocalDate.from(Instant.ofEpochMilli(date.getTime()))คิดว่ามันเทียบเท่ากับของคุณ แต่ตรงกว่า
Marko Topolnik

9
@MarkoTopolnik ที่รวบรวม แต่ไม่ได้ทำงาน การค้นหาทันใจไม่มีเขตเวลาดังนั้นจึงไม่มีวิธีรับ LocalDate
JodaStephen

11
@assylias เพียงใช้ sqlDate.toLocalDate ()!
JodaStephen

25
@JodaStephen Dateไม่มีแนวคิดเกี่ยวกับเขตเวลาและInstantไม่มีข้อมูลเกี่ยวกับเขตเวลา LocalDateAPI บอกว่า "วันที่ไม่มีโซนเวลา" แล้วทำไมการแปลงจากDateเพื่อInstantที่จะLocalDateตอบสนองความต้องการatZone(ZoneId.systemDefault())?
Gustavo

8
@Gustavo: LocalDateและLocalDateTimeห้าม "จัดเก็บหรือเป็นตัวแทนของเวลาหรือเขตเวลา" (อ้างอิง: javadocs) แม้ว่าพวกเขาจะไม่เก็บมัน แต่คลาสจะเป็นตัวแทนของLocalวันที่และ / หรือเวลาดังนั้นการแปลงเป็นวันที่ / เวลาท้องถิ่นหมายถึงเขตเวลา
Cuga

158

วิธีที่ดีกว่าคือ:

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

ข้อดีของรุ่นนี้:

  • ทำงานไม่ว่าอินพุตจะเป็นอินสแตนซ์ของjava.util.Dateหรือเป็นคลาสย่อยของjava.sql.Date(ต่างจากวิธีของ @ JodaStephen) นี่เป็นเรื่องปกติกับข้อมูลที่มาจาก JDBC java.sql.Date.toInstant()ส่งข้อยกเว้นเสมอ

  • มันเหมือนกันสำหรับ JDK8 และ JDK7 ที่มี backport JSR-310

ฉันใช้คลาสยูทิลิตี้ส่วนตัว (แต่ไม่เข้ากันได้กับพอร์ท):

/**
 * 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);
    }

}

asLocalDate()วิธีการที่นี่เป็นโมฆะปลอดภัยการใช้งานtoLocalDate(), ถ้าใส่เป็นjava.sql.Date(มันอาจจะถูกแทนที่โดยไดรเวอร์ JDBC ปัญหาหลีกเลี่ยงเขตเวลาหรือการคำนวณที่ไม่จำเป็น) มิฉะนั้นใช้วิธีการดังกล่าวข้างต้น


12
หากนี่เป็นวิธีที่ดีกว่ามันน่าเกลียดมากและ verbose เจ็บปวดมาก
ceklock

3
มันจะดีกว่าเมื่อเทียบกับคำตอบที่ยอมรับฉันอธิบายว่าทำไม ใช่มันเป็นความน่าเกลียด DateConvertUtilsแต่ที่ว่าทำไมเขียน
Oliv

ฉันไม่เข้าใจว่าทำไมพวกเขาไม่ใช้คลาสการแปลงใน API ใหม่
ceklock

@ceklock พวกเขาไม่ใช้ไม่ได้เป็นระดับการแปลง Date.toInstant()แต่คู่ของวิธีการแปลงเช่น
Ole VV

2
มีห้องสมุด Kotlin ที่ทำหน้าที่เป็นส่วนขยายหรือไม่ วันที่ x = ... ; x.asLocalDate ();
Mark Ashworth

20
LocalDate localDate = LocalDate.parse( new SimpleDateFormat("yyyy-MM-dd").format(date) );

7
ที่นี่SimpleDateFormatอินสแตนซ์ถูกจำกัด ไว้ที่เธรดปัจจุบัน มันถูกใช้ในวิธีที่ปลอดภัยต่อเธรด ตอนนี้ได้SimpleDateFormatรับการยกย่องว่าเป็น 'แพงในการสร้างอินสแตนซ์' (เนื่องจากโครงสร้างข้อมูลภายในทั้งหมดที่ต้องการ) แต่คุณไม่สามารถแชร์เป็น 'ซิงเกิลตัน' (โดยไม่ต้องซิงโครไนซ์การเข้าถึง) เพราะมันไม่ใช่เธรด ปลอดภัย ( ThreadLocalวิธีการแก้ปัญหาสามารถทำงานได้ถ้ารหัส 'มลพิษ' Threadในนี้เป็นผู้รับผิดชอบวงจรชีวิตของด้าย ... แต่ที่ไม่ค่อยเกิดขึ้น) อึดอัด หลีกเลี่ยงการSimpleDateFormatอยู่ในjavax.timeเหตุผลสำหรับการใช้
David Bullock

6
วิธีนี้มีค่าใช้จ่ายมากมาย: 'แพง' SimpleDateFormat (ซึ่งถูกโยนทิ้งไป) สตริงกลาง (ซึ่งถูกโยนทิ้งไป) และค่าใช้จ่ายในการแยกวิเคราะห์ มันเป็นวิธีแก้ปัญหา แต่ไม่แนะนำ
David Bullock

2
@ceklock แต่คุณได้รับปัญหาว่าไม่ปลอดภัยสำหรับเธรดอีกต่อไป
flup

4
@ceklock SimpleDateFormat สามารถดำเนินการได้ครั้งละหนึ่งวันเท่านั้นหากคุณใช้พร้อมกันมันจะให้ผลลัพธ์ที่สับสน ใช่มันเป็นสิ่งสำคัญ อย่าสร้างอินสแตนซ์เดียวของ SimpleDateFormat ในตัวแปรส่วนกลาง
flup

19

หากคุณใช้ Java 8 คำตอบของ @ JodaStephen นั้นดีที่สุด อย่างไรก็ตามหากคุณทำงานกับbackport JSR-310คุณต้องทำสิ่งนี้:

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));

4
ไม่เป็นความจริงคำตอบของ @ JodaStephen ยังคงใช้ได้ คุณเพียงต้องการวิธีอื่นในการแปลง java.util.Date เป็น Instant สำหรับสิ่งนี้คุณสามารถใช้ org.threeten.bp.DateTimeUtils.toInstant: threeten.org/threetenbp/apidocs/org/threeten/bp/…
Christian Ciach

3
DateTimeUtils ไม่พร้อมใช้งานเมื่อฉันใช้ backport แต่คุณถูกต้องว่าทุกคนที่ใช้ ThreeTen Backport 1.0 หรือใหม่กว่านั้น ขอบคุณที่ชี้นำ
dhalsim2

14
LocalDate ld = new java.sql.Date( new java.util.Date().getTime() ).toLocalDate();

1
และการรับรู้ที่ง่ายที่สุด: LocalDate.of (getYear () + 1900, getMonth () + 1, getDate ())
Grigory Kislin

บรรทัดที่บอกว่าเลิกใช้แล้ว: |
TheRealChx101

8

คุณสามารถแปลงเป็นหนึ่งบรรทัด:

public static LocalDate getLocalDateFromDate(Date date){
   return LocalDate.from(Instant.ofEpochMilli(date.getTime()).atZone(ZoneId.systemDefault()));
}

ฉันได้รับข้อผิดพลาดเช่นนี้โดยใช้วิธีนี้: java.time.DateTimeException: ไม่สามารถรับ LocalDate จาก TemporalAccessor: 2018-01-31T11: 54: 27.964Z ประเภท java.time.Instant
Luigi Rubino

@LuigiRubino ขอบคุณที่ชี้ให้เห็น โปรดดูคำตอบที่อัพเดต ฉันลืมเพิ่มโซนก่อนหน้า
Sahil Chhabra

8

ก่อนอื่นมันง่ายที่จะแปลง Date เป็น Instant

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

จากนั้นคุณสามารถแปลง Instant เป็น api วันที่ใด ๆ ใน jdk 8 โดยใช้เมธอด ofInstant ():

LocalDateTime date = LocalDateTime.ofInstant(timestamp, ZoneId.systemDefault()); 

รวม localDate, localDateTime, localTime
Shedom Wei

การระบุ ZoneId ที่นี่หมายความว่าอะไร ถ้าฉันได้รับวันที่ -> ทันทีจาก API และฉันคิดได้ว่ามันเป็นมิลลิวินาทีจาก 1970 ใน UTC เมื่อฉันต้องการรับ LocalDate ที่สอดคล้องกันเพียงแค่ (yyyy-mm-dd) จากมุมมองของ api ที่ไม่ได้แปลงเป็นเขตเวลาใด ๆ ฉันไม่ควรใช้ ZoneOffset.UTC เพื่อไม่ให้มีการชดเชยทันทีกับเขตเวลาท้องถิ่นของฉัน ไม่ใช่ตัวอย่างของคุณที่มี ZoneId.systemDefault () กำลังเปลี่ยนเซิร์ฟเวอร์แบบฟอร์มวันที่ อดีต ทันทีแสดงถึง 2018-10-20 0:00 จากนั้นเปลี่ยนจาก UTC เป็นอเมริกา / Los_Angeles ฉันได้รับในวันที่ท้องถิ่น 2018-10-19 แทน 2018-10-20?
Michał Ziobro

มันจะแตกถ้าคุณเกิดขึ้นกับimport java.sql.Dateไฟล์ของคุณ: toInstant()วิธีการjava.sql.Dateพ่นเสมอ
Oliv

4
Date input = new Date();
LocalDateTime  conv=LocalDateTime.ofInstant(input.toInstant(), ZoneId.systemDefault());
LocalDate convDate=conv.toLocalDate();

Dateเช่นจะมีเวลาพร้อมเกินไปกับวันในขณะที่LocalDateไม่ได้ ดังนั้นท่านแรกสามารถแปลงเป็นLocalDateTimeใช้วิธีการของมันofInstant()แล้วถ้าคุณต้องการได้โดยไม่ต้องเวลาLocalDateแล้วแปลงเช่นลง


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

รูปแบบนี้มาจาก 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();
    }

0

ผมมีปัญหาเกี่ยวกับการดำเนินงาน @ JodaStephen บน JBoss EAP 6. ดังนั้นผมเขียนแปลงดังต่อไปนี้ของออราเคิล Java สอนใน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();

-2

เกิดอะไรขึ้นกับ 1 บรรทัดง่ายๆนี้

new LocalDateTime(new Date().getTime()).toLocalDate();

Java บ่นว่าไม่สามารถเรียกใช้ toLocalDate () กับชนิดดั้งเดิมที่มีความยาว ฉันใช้วัตถุวันที่จริง อาจจะมีการถู ??
TheGeeko61

-8

ฉันแก้ไขคำถามนี้ด้วยวิธีแก้ปัญหาด้านล่าง

  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);

ในกรณีนี้ localDate พิมพ์วันที่ของคุณในรูปแบบนี้ "yyyy-MM-dd"


1
คำถามกำลังมองหาวิธีแก้ปัญหาโดยใช้java.timeคลาสใน JDK8 ไม่ใช่กับ Joda Time
pioto
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.