การแปลงระหว่าง java.time.LocalDateTime และ java.util.Date


484

Java 8 มี API ใหม่อย่างสมบูรณ์สำหรับวันที่และเวลา หนึ่งในคลาสที่มีประโยชน์ที่สุดใน API นี้คือLocalDateTimeสำหรับเก็บค่าวันที่แบบไม่ขึ้นกับเขตเวลา

อาจมีหลายล้านบรรทัดของรหัสโดยใช้คลาสดั้งเดิมjava.util.Dateสำหรับวัตถุประสงค์นี้ เช่นนี้เมื่อทำการเชื่อมต่อโค้ดเก่าและใหม่จะต้องมีการแปลงระหว่างทั้งสอง ดูเหมือนจะไม่มีวิธีการโดยตรงในการทำสิ่งนี้ให้สำเร็จจึงจะทำได้อย่างไร




สำเนาซ้ำที่เป็นไปได้ของConvert java.util.Date เป็น java.time.LocalDate
Peter Perháč

คำตอบ:


706

คำตอบสั้น ๆ :

Date in = new Date();
LocalDateTime ldt = LocalDateTime.ofInstant(in.toInstant(), ZoneId.systemDefault());
Date out = Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant());

คำอธิบาย: (ตามคำถามนี้เกี่ยวกับLocalDate)

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

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

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

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

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

Date in = new Date();
LocalDateTime ldt = LocalDateTime.ofInstant(in.toInstant(), ZoneId.systemDefault());

ในทางกลับกันเขตLocalDateTimeเวลาจะถูกระบุโดยเรียกatZone(ZoneId)วิธีการ ZonedDateTimeแล้วสามารถแปลงโดยตรงไปยังInstant:

LocalDateTime ldt = ...
ZonedDateTime zdt = ldt.atZone(ZoneId.systemDefault());
Date output = Date.from(zdt.toInstant());

โปรดทราบว่าการแปลงจากLocalDateTimeถึงZonedDateTimeมีแนวโน้มที่จะนำเสนอพฤติกรรมที่ไม่คาดคิด นี่เป็นเพราะไม่มีวันที่เวลาท้องถิ่นเนื่องจากการปรับเวลาตามฤดูกาล ในฤดูใบไม้ร่วง / ฤดูใบไม้ร่วงจะมีการทับซ้อนกันในเส้นบอกเวลาท้องถิ่นซึ่งมีวันที่และเวลาท้องถิ่นซ้ำกันสองครั้ง ในฤดูใบไม้ผลิมีช่องว่างซึ่งเวลาหนึ่งชั่วโมงจะหายไป ดู Javadoc ของatZone(ZoneId)สำหรับคำนิยามเพิ่มเติมของการแปลงที่จะทำ

สรุปถ้าคุณjava.util.Dateไป a LocalDateTimeและกลับไป a java.util.Dateคุณอาจจบลงด้วยการเปลี่ยนทันทีเนื่องจากเวลาออมแสง

ข้อมูลเพิ่มเติม: มีความแตกต่างอื่นที่จะส่งผลต่อวันที่เก่ามาก java.util.Dateใช้ปฏิทินที่เปลี่ยนแปลงเมื่อวันที่ 15 ตุลาคม 1582 โดยมีวันที่ก่อนหน้านั้นโดยใช้ปฏิทินจูเลียนแทนที่จะเป็นปฏิทินเกรกอเรียน ในทางตรงกันข้ามjava.time.*ใช้ระบบปฏิทิน ISO (เทียบเท่ากับ Gregorian) ตลอดเวลา ในกรณีที่ใช้มากที่สุดระบบปฏิทิน ISO เป็นสิ่งที่คุณต้องการ แต่คุณอาจเห็นผลกระทบแปลก ๆ เมื่อเปรียบเทียบวันที่ก่อนปี 1582


5
ขอบคุณมากสำหรับคำอธิบายที่ชัดเจน โดยเฉพาะอย่างยิ่งสำหรับเหตุผลที่java.util.Dateไม่ได้มีเขตเวลา toString()แต่พิมพ์ในช่วง เอกสารทางการของเหตุการณ์ไม่ได้บอกสิ่งนี้อย่างชัดเจนในขณะที่คุณโพสต์
Cherry

คำเตือน: LocalDateTime.ofInstant(date.toInstant()...ไม่ทำงานตามที่คาดหวังไว้อย่างไร้เดียงสา ตัวอย่างเช่นnew Date(1111-1900,11-1,11,0,0,0);จะ1111-11-17 23:53:28ใช้วิธีนี้ ลองดูที่การนำไปใช้java.sql.Timestamp#toLocalDateTime()ถ้าคุณต้องการผลลัพธ์ที่จะเป็น1111-11-11 00:00:00ในตัวอย่างก่อนหน้า
สุนัข

2
ฉันได้เพิ่มหัวข้อที่เกี่ยวกับวันที่เก่ามาก (ก่อนอายุ 1582) FWIW การแก้ไขที่แนะนำของคุณอาจผิดเพราะ 1111-11-11 ใน java.util.Date เป็นวันที่เกิดขึ้นจริงในประวัติศาสตร์เดียวกันกับ 1111-11-18 ใน java.time เนื่องจากระบบปฏิทินที่แตกต่างกัน (ความต่าง 6.5 นาที เกิดขึ้นกับเขตเวลาหลายแห่งก่อนปี 1900)
JodaStephen

2
นอกจากนี้มูลค่าการที่จะทราบว่าพ่นjava.sql.Date#toInstant UnsupportedOperationExceptionจึงไม่ใช้toInstantใน RowMapper java.sql.ResultSet#getDateบน
LazerBass

132

นี่คือสิ่งที่ฉันคิดขึ้นมา (และเช่นเดียวกับปัญหาเรื่องวันที่และเวลาอื่น ๆ มันอาจจะถูกหักล้างโดยอิงจากการปรับไทม์โซน - leapyear-daylight: D)

การปัดเศษ: Date<<->>LocalDateTime

ได้รับ: 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);

ตัวอย่าง:

ได้รับ:

Date date = new Date();
System.out.println(date + " long: " + date.getTime());

(1) LocalDateTime<< Instant<< Date:

สร้างInstantจากDate:

Instant instant = Instant.ofEpochMilli(date.getTime());
System.out.println("Instant from Date:\n" + instant);

สร้างDateจากInstant(ไม่จำเป็น แต่เป็นภาพประกอบ):

date = Date.from(instant);
System.out.println("Date from Instant:\n" + date + " long: " + date.getTime());

สร้างLocalDateTimeจากInstant

LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);
System.out.println("LocalDateTime from Instant:\n" + ldt);

(2) Date<< Instant<<LocalDateTime

สร้างInstantจากLocalDateTime:

instant = ldt.toInstant(ZoneOffset.UTC);
System.out.println("Instant from LocalDateTime:\n" + instant);

สร้างDateจากInstant:

date = Date.from(instant);
System.out.println("Date from Instant:\n" + date + " long: " + date.getTime());

ผลลัพธ์คือ:

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

2
@scottb คุณพูดกับฉันหรือคนที่โพสต์คำถาม? สำหรับความคิดของฉันก็เป็นเรื่องดีที่มีการระบุการแปลงไว้อย่างชัดเจนใน jdk 8 API - อย่างน้อยพวกเขาก็สามารถทำได้ ไม่ว่าในกรณีใดมีห้องสมุดจำนวนมากที่จะได้รับการพิจารณาใหม่เพื่อรวมคุณลักษณะ Java-8 ใหม่และเพื่อทราบว่าจะต้องทำอย่างไรจึงมีความสำคัญ
ผู้ประสานงาน

4
@scottb เหตุใดเคสของบุคคลที่สามจึงผิดปกติ มันธรรมดาทั่วไป ตัวอย่างหนึ่ง: JDBC 4 และน้อยกว่า (หวังว่าจะไม่ 5)
Raman

5
ตามกฎทั่วไปของ JSR-310 ไม่จำเป็นต้องแปลงระหว่างชนิดโดยใช้ epoch-millis มีทางเลือกที่ดีกว่าในการใช้วัตถุดูคำตอบทั้งหมดของฉันด้านล่าง คำตอบข้างต้นจะใช้ได้อย่างสมบูรณ์หากใช้การชดเชยแบบโซนเช่น UTC - บางส่วนของคำตอบจะไม่ทำงานสำหรับเขตเวลาแบบเต็มเช่น America / New_York
JodaStephen

2
แทนที่จะInstant.ofEpochMilli(date.getTime())ทำdate.toInstant()
แพะ

1
@goat toInstant()ดูดียกเว้นมันล้มเหลวสำหรับjava.sql.Datearggggh! Instant.ofEpochMilli(date.getTime())ดังนั้นจึงเป็นที่สุดง่ายต่อการใช้งาน
vadipp

22

วิธีที่สะดวกกว่านี้มากถ้าคุณแน่ใจว่าคุณต้องการเขตเวลาเริ่มต้น:

Date d = java.sql.Timestamp.valueOf( myLocalDateTime );

25
แน่นอนว่ามันง่ายกว่า แต่ฉันไม่ชอบ mix jdbc ที่เกี่ยวข้องกับการจัดการ Date ง่ายๆอย่างน้อย IMHO
Enrico Giurin

9
ฉันขอโทษนี่เป็นวิธีแก้ปัญหาที่แย่มากสำหรับปัญหาง่ายๆ มันเหมือนกับการนิยาม 5 ให้เท่ากับจำนวนเสียงเรียกเข้าในโลโก้โอลิมปิก
Madbreaks

7

ต่อไปนี้ดูเหมือนว่าจะทำงานเมื่อแปลงจาก API LocalDateTime ใหม่เป็น java.util.date:

Date.from(ZonedDateTime.of({time as LocalDateTime}, ZoneId.systemDefault()).toInstant());

การแปลงกลับสามารถทำได้ (หวังว่า) จะประสบความสำเร็จในลักษณะเดียวกัน ...

หวังว่ามันจะช่วย ...


5

ทุกอย่างอยู่ที่นี่: http://blog.progs.be/542/date-to-java-time

คำตอบด้วย "ปัดเศษ" ไม่แน่นอน: เมื่อคุณทำ

LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);

ถ้าเขตเวลาระบบของคุณไม่ใช่ UTC / GMT คุณเปลี่ยนเวลา!


เฉพาะในกรณีที่คุณทำในช่วง 1 ชั่วโมงต่อปีในฤดูใบไม้ร่วงเมื่อสองครั้งจาก LocalDateTime ทับซ้อนกัน สปริงเลื่อนไปข้างหน้าไม่ทำให้เกิดปัญหา เวลาส่วนใหญ่จะแปลงทั้งสองทิศทางอย่างเหมาะสม
เดวิด


3

ฉันไม่แน่ใจว่านี่เป็นวิธีที่ง่ายที่สุดหรือดีที่สุดหรือมีข้อผิดพลาดใด ๆ แต่ใช้งานได้:

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

3
มีแน่นอนอันตรายที่เกิดจากการLocalDateTime Dateที่การเปลี่ยนการปรับเวลาตามฤดูกาล a LocalDateTimeอาจไม่มีอยู่จริงหรือเกิดขึ้นสองครั้ง คุณต้องคิดออกสิ่งที่คุณต้องการจะเกิดขึ้นในแต่ละกรณี
Jon Skeet

1
BTW GregorianCalendarเป็น API เก่าที่น่าอึดอัดใจที่ API ใหม่java.timeมีจุดมุ่งหมายเพื่อแทนที่
Vadzim

3

หากคุณใช้ Android และใช้threetenbpคุณสามารถใช้DateTimeUtilsแทนได้

อดีต:

Date date = DateTimeUtils.toDate(localDateTime.atZone(ZoneId.systemDefault()).toInstant());

คุณไม่สามารถใช้ได้Date.fromเนื่องจาก api 26+ รองรับเท่านั้น

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