วิธีการแปลง ZonedDateTime เป็น Date?


109

ฉันกำลังพยายามตั้งค่าวันเวลาที่ไม่เชื่อเรื่องพระเจ้าของเซิร์ฟเวอร์ในฐานข้อมูลของฉันและฉันเชื่อว่าแนวทางปฏิบัติที่ดีที่สุดคือการตั้งค่าวันที่เวลา UTC เซิร์ฟเวอร์ฐานข้อมูลของฉันคือ Cassandra และไดรเวอร์ db สำหรับ Java เข้าใจเฉพาะประเภท Date

ดังนั้นสมมติว่าในโค้ดของฉันฉันใช้ Java 8 ZonedDateTime ใหม่เพื่อรับ UTC ทันที ( ZonedDateTime.now(ZoneOffset.UTC)) ฉันจะแปลงอินสแตนซ์ ZonedDateTime นี้เป็นคลาส Date "ดั้งเดิม" ได้อย่างไร


มีสอง "วันที่" เรียนที่สร้างขึ้นใน Java มีและjava.util.Date java.sql.Date
Basil Bourque

คำตอบ:


178

คุณสามารถแปลง ZonedDateTime เป็นแบบทันทีซึ่งคุณสามารถใช้กับ Date ได้โดยตรง

Date.from(java.time.ZonedDateTime.now().toInstant());

32
ไม่จะเป็นวันที่ปัจจุบันบนค่าเริ่มต้นระบบโซนของคุณ
Slim Soltani Dridi

7
@MilenKovachev คำถามของคุณไม่สมเหตุสมผล - วันที่ไม่มีเขตเวลา - จะแสดงเฉพาะช่วงเวลาทันที
assylias

1
@assylias จริงๆแล้วคำพูดของคุณไม่สมเหตุสมผล รูปแบบที่จัดเก็บเวลาหมายถึงเขตเวลา วันที่ขึ้นอยู่กับ UTC น่าเสียดายที่ Java ทำสิ่งที่โง่เขลาและไม่ปฏิบัติเช่นนั้นและยิ่งไปกว่านั้นถือว่าเวลาเป็น TZ ในท้องถิ่นแทนที่จะเป็น UTC วิธีที่ Data, LocalDateTime, ZonedDateTime จัดเก็บข้อมูลหมายถึงเขตเวลา วิธีที่พวกเขาใช้ก็เหมือนกับว่าไม่มี TZs ซึ่งเป็นสิ่งที่ไม่ถูกต้อง java.util.Date มี TZ ของค่าเริ่มต้น JVM โดยปริยาย ความจริงที่ว่าผู้คนมองว่ามันเป็นอะไรที่แตกต่าง (รวมถึงเอกสาร java ด้วย!)
เดวิด

6
@David เอ่อไม่ - a Dateคือจำนวนมิลลิวินาทีตั้งแต่ยุค - ดังนั้นจึงเกี่ยวข้องกับ UTC หากคุณพิมพ์จะใช้เขตเวลาเริ่มต้น แต่คลาส Date ไม่มีความรู้เกี่ยวกับเขตเวลาของผู้ใช้ ... ดูตัวอย่างส่วน "การแมป" ในdocs.oracle.com/javase/tutorial/datetime/iso/legacy .htmlและ LocalDateTime อย่างชัดเจนโดยไม่ต้องอ้างอิงถึงเขตเวลาซึ่งอาจทำให้สับสนได้ ...
assylias

3
ZonedDateTimeคำตอบของคุณไม่จำเป็นต้องเกี่ยวข้องกับ java.time.Instantระดับเป็นแทนโดยตรงjava.util.Dateทั้งที่เป็นตัวแทนของช่วงเวลาใน UTC แม้ว่าInstantการใช้ความละเอียดปลีกย่อยของนาโนวินาทีแทนมิลลิวินาที Date.from( Instant.now() )น่าจะเป็นทางออกของคุณ หรือสำหรับเรื่องนั้นnew Date()ซึ่งมีผลเช่นเดียวกันการจับภาพช่วงเวลาปัจจุบันใน UTC
Basil Bourque

72

tl; dr

java.util.Date.from(  // Transfer the moment in UTC, truncating any microseconds or nanoseconds to milliseconds.
    Instant.now() ;   // Capture current moment in UTC, with resolution as fine as nanoseconds.
)

แม้ว่าจะไม่มีจุดใดในรหัสข้างต้น ทั้งสองjava.util.DateและInstantแสดงช่วงเวลาใน UTC เสมอใน UTC รหัสด้านบนมีผลเช่นเดียวกับ:

new java.util.Date()  // Capture current moment in UTC.

ZonedDateTimeไม่มีประโยชน์ที่นี่เพื่อใช้ หากคุณมีอยู่แล้วให้ZonedDateTimeปรับเป็น UTC โดยแยกไฟล์Instant.

java.util.Date.from(             // Truncates any micros/nanos.
    myZonedDateTime.toInstant()  // Adjust to UTC. Same moment, same point on the timeline, different wall-clock time.
)

คำตอบอื่น ๆ ถูกต้อง

คำตอบโดยssoltanidอย่างถูกต้องอยู่คำถามของคุณโดยเฉพาะวิธีการแปลง java.time วัตถุใหม่โรงเรียน ( ZonedDateTime) เพื่อโรงเรียนเก่าjava.util.Dateวัตถุ แยกInstantจาก ZonedDateTime java.util.Date.from()และส่งผ่านไปยัง

ข้อมูลสูญหาย

โปรดทราบว่าคุณจะต้องสูญเสียข้อมูลเนื่องจากInstantติดตามนาโนวินาทีตั้งแต่ยุคขณะที่java.util.Dateติดตามมิลลิวินาทีนับตั้งแต่ยุค

แผนภาพเปรียบเทียบความละเอียดของมิลลิวินาทีไมโครวินาทีและนาโนวินาที

คำถามและความคิดเห็นของคุณทำให้เกิดปัญหาอื่น ๆ

เก็บเซิร์ฟเวอร์ใน UTC

เซิร์ฟเวอร์ของคุณควรตั้งค่าโฮสต์ OS เป็น UTC เป็นแนวปฏิบัติที่ดีที่สุดโดยทั่วไป JVM รับการตั้งค่าโฮสต์ OS นี้เป็นโซนเวลาเริ่มต้นในการใช้งาน Java ที่ฉันทราบ

ระบุโซนเวลา

แต่คุณไม่ควรพึ่งพาเขตเวลาเริ่มต้นปัจจุบันของ JVM แทนที่จะรับการตั้งค่าโฮสต์แฟล็กที่ส่งผ่านเมื่อเรียกใช้ JVM สามารถตั้งค่าเขตเวลาอื่น ที่แย่ไปกว่านั้น: โค้ดใด ๆ ในเธรดของแอพใดก็ได้ตลอดเวลาสามารถโทรjava.util.TimeZone::setDefaultเพื่อเปลี่ยนค่าเริ่มต้นที่รันไทม์ได้!

Timestampประเภทคาสซานดรา

ใด ๆฐานข้อมูลที่ดีและคนขับรถโดยอัตโนมัติควรจะจัดการกับการปรับผ่านวันเวลาที่จะ UTC สำหรับการจัดเก็บ ฉันไม่ได้ใช้ Cassandra แต่ดูเหมือนว่าจะมีการรองรับพื้นฐานสำหรับวันที่ - เวลา เอกสารระบุว่าTimestampประเภทของมันคือจำนวนมิลลิวินาทีจากยุคเดียวกัน (ช่วงเวลาแรกของปี 1970 ใน UTC)

ISO 8601

นอกจากนี้ Cassandra ยังยอมรับอินพุตสตริงในรูปแบบมาตรฐานISO 8601 โชคดีที่ java.time ใช้รูปแบบ ISO 8601 เป็นค่าเริ่มต้นสำหรับการแยกวิเคราะห์ / สร้างสตริง การใช้งานInstantชั้นเรียนtoStringจะทำได้ดี

ความแม่นยำ: มิลลิวินาทีเทียบกับนาโนคอร์ด

แต่ก่อนอื่นเราต้องลดความแม่นยำระดับนาโนวินาทีของ ZonedDateTime เป็นมิลลิวินาที วิธีหนึ่งคือสร้าง Instant ใหม่โดยใช้มิลลิวินาที โชคดีที่ java.time มีวิธีการที่สะดวกในการแปลงค่าเป็นมิลลิวินาที

ตัวอย่างรหัส

นี่คือตัวอย่างโค้ดบางส่วนใน Java 8 Update 60

ZonedDateTime zdt = ZonedDateTime.now( ZoneId.of( "America/Montreal" ) );
…
Instant instant = zdt.toInstant();
Instant instantTruncatedToMilliseconds = Instant.ofEpochMilli( instant.toEpochMilli() );
String fodderForCassandra = instantTruncatedToMilliseconds.toString();  // Example: 2015-08-18T06:36:40.321Z

หรือตามเอกสารไดรเวอร์ Cassandra Javaนี้คุณสามารถส่งผ่านjava.util.Dateอินสแตนซ์ (เพื่อไม่ให้สับสนกับjava.sqlDate) คุณสามารถสร้าง juDate ได้จากinstantTruncatedToMillisecondsโค้ดด้านบน

java.util.Date dateForCassandra = java.util.Date.from( instantTruncatedToMilliseconds );

หากทำเช่นนี้บ่อยๆคุณสามารถสร้างซับใน

java.util.Date dateForCassandra = java.util.Date.from( zdt.toInstant() );

แต่มันจะดีกว่าที่จะสร้างวิธียูทิลิตี้เล็กน้อย

static public java.util.Date toJavaUtilDateFromZonedDateTime ( ZonedDateTime zdt ) {
    Instant instant = zdt.toInstant();
    // Data-loss, going from nanosecond resolution to milliseconds.
    java.util.Date utilDate = java.util.Date.from( instant ) ;
    return utilDate;
}

สังเกตความแตกต่างของรหัสทั้งหมดนี้มากกว่าในคำถาม รหัสของคำถามพยายามปรับโซนเวลาของอินสแตนซ์ ZonedDateTime เป็น UTC แต่นั่นไม่จำเป็น ตามแนวคิด:

ZonedDateTime = ทันที + ZoneId

เราเพียงแค่แยกส่วนทันทีซึ่งมีอยู่แล้วใน UTC (โดยทั่วไปใน UTC อ่านเอกสารคลาสเพื่อดูรายละเอียดที่แม่นยำ)


ตารางประเภทวันที่ - เวลาใน Java ทั้งแบบสมัยใหม่และแบบเดิม


เกี่ยวกับjava.time

java.timeกรอบถูกสร้างขึ้นใน Java 8 และต่อมา ชั้นเรียนเหล่านี้แย่งลำบากเก่ามรดกเรียนวันที่เวลาเช่นjava.util.Date, และCalendarSimpleDateFormat

โครงการJoda-Timeขณะนี้อยู่ในโหมดการบำรุงรักษาแนะนำให้ย้ายไปยังคลาสjava.time

ต้องการเรียนรู้เพิ่มเติมโปรดดูที่ออราเคิลกวดวิชา และค้นหา Stack Overflow สำหรับตัวอย่างและคำอธิบายมากมาย สเปกJSR 310

คุณสามารถแลกเปลี่ยนวัตถุjava.timeโดยตรงกับฐานข้อมูลของคุณ ใช้ไดรเวอร์ JDBC ที่สอดคล้องกับJDBC 4.2หรือใหม่กว่า ไม่จำเป็นต้องมีสตริงไม่จำเป็นต้องมีjava.sql.*คลาส

จะหาคลาส java.time ได้ที่ไหน?

  • Java SE 8 , Java SE 9 , Java SE 10และใหม่กว่า
    • ในตัว.
    • ส่วนหนึ่งของ Java API มาตรฐานพร้อมการใช้งานแบบรวม
    • Java 9 เพิ่มคุณสมบัติและการแก้ไขเล็กน้อย
  • Java SE 6และ Java SE 7
    • มากของการทำงาน java.time จะกลับรังเพลิง Java 6 และ 7 ในThreeTen-ย้ายกลับ
  • Android

ตารางที่ไลบรารี java.time ที่จะใช้กับ Java หรือ Android เวอร์ชันใด

โครงการThreeTen-Extraขยาย java.time ด้วยคลาสเพิ่มเติม โปรเจ็กต์นี้เป็นพื้นที่พิสูจน์สำหรับการเพิ่ม java.time ในอนาคต คุณอาจพบว่าการเรียนที่มีประโยชน์บางอย่างที่นี่เช่นInterval, YearWeek, YearQuarterและอื่น ๆ อีกมากมาย


คำอธิบายที่ยอดเยี่ยมมีรายละเอียดดี ขอบคุณมาก!
Gianmarco F.

5

หากคุณใช้ThreeTen backportสำหรับ Android และไม่สามารถใช้รุ่นใหม่กว่าได้Date.from(Instant instant)(ซึ่งต้องใช้ API ขั้นต่ำ 26) คุณสามารถใช้:

ZonedDateTime zdt = ZonedDateTime.now();
Date date = new Date(zdt.toInstant().toEpochMilli());

หรือ:

Date date = DateTimeUtils.toDate(zdt.toInstant());

โปรดอ่านคำแนะนำในคำตอบของ Basil Bourque


1
ThreeTen Backport (และ ThreeTenABP) รวมDateTimeUtilsคลาสที่มีวิธีการแปลงดังนั้นฉันจึงใช้ Date date = DateTimeUtils.toDate (zdt.toInstant ()); ". มันไม่ได้ต่ำมาก
Ole VV

4

นี่คือตัวอย่างการแปลงเวลาของระบบปัจจุบันเป็น UTC มันเกี่ยวข้องกับการจัดรูปแบบ ZonedDateTime เป็น String จากนั้นวัตถุ String จะถูกแยกวิเคราะห์เป็นวัตถุวันที่โดยใช้ java.text DateFormat

    ZonedDateTime zdt = ZonedDateTime.now(ZoneOffset.UTC);
    final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm:ss");
    final DateFormat FORMATTER_YYYYMMDD_HH_MM_SS = new SimpleDateFormat("yyyyMMdd HH:mm:ss");
    String dateStr = zdt.format(DATETIME_FORMATTER);

    Date utcDate = null;
    try {
        utcDate = FORMATTER_YYYYMMDD_HH_MM_SS.parse(dateStr);
    }catch (ParseException ex){
        ex.printStackTrace();
    }

2

คำตอบที่ยอมรับไม่ได้ผลสำหรับฉัน วันที่ที่ส่งคืนจะเป็นวันที่ภายในเครื่องเสมอและไม่ใช่วันที่สำหรับเขตเวลาเดิม ฉันอาศัยอยู่ใน UTC + 2

//This did not work for me
Date.from(java.time.ZonedDateTime.now().toInstant()); 

ฉันมีทางเลือกสองวิธีในการรับวันที่ที่ถูกต้องจาก ZonedDateTime

สมมติว่าคุณมี ZonedDateTime สำหรับฮาวาย

LocalDateTime ldt = LocalDateTime.now();
ZonedDateTime zdt = ldt.atZone(ZoneId.of("US/Hawaii"); // UTC-10

หรือสำหรับ UTC ตามที่ถามในตอนแรก

Instant zulu = Instant.now(); // GMT, UTC+0
ZonedDateTime zdt = zulu.atZone(ZoneId.of("UTC"));

ทางเลือก 1

เราสามารถใช้ java.sql.Timestamp มันง่าย แต่มันอาจจะทำให้ความสมบูรณ์ในการเขียนโปรแกรมของคุณแย่ลงด้วย

Date date1 = Timestamp.valueOf(zdt.toLocalDateTime());

ทางเลือกที่ 2

เราสร้างวันที่จากมิลลิวินาที (ตอบไว้ที่นี่ก่อนหน้านี้) โปรดทราบว่า ZoneOffset ในเครื่องเป็นสิ่งจำเป็น

ZoneOffset localOffset = ZoneOffset.systemDefault().getRules().getOffset(LocalDateTime.now());
long zonedMillis = 1000L * zdt.toLocalDateTime().toEpochSecond(localOffset) + zdt.toLocalDateTime().getNano() / 1000000L;
Date date2 = new Date(zonedMillis);

1

คุณสามารถทำได้โดยใช้คลาสjava.time ที่สร้างใน Java 8 ขึ้นไป

ZonedDateTime temporal = ...
long epochSecond = temporal.getLong(INSTANT_SECONDS);
int nanoOfSecond = temporal.get(NANO_OF_SECOND);
Date date = new Date(epochSecond * 1000 + nanoOfSecond / 1000000);

ขออภัยค่าคงที่ชั่วคราวและค่าคงที่ทั้งหมดนี้มาจากไหน
Milen Kovachev

@MilenKovachev A ZonedDateTime เป็นตัวอย่างของ Temporal
Peter Lawrey

ขอบคุณ แต่คุณควรแจ้งว่าคุณแก้ไขคำตอบของคุณเพื่อให้ความคิดเห็นของฉันดูไม่งี่เง่า
Milen Kovachev

2
@MilenKovachev คุณสามารถลบความคิดเห็นได้ แต่ไม่ใช่คำถามโง่ ๆ
Peter Lawrey

1
ฉันไม่เข้าใจว่าทำไมคำตอบนี้จึงถูกลดลง Instantวินาทีติดตามและนาโนวินาที Dateวินาทีติดตาม การแปลงจากที่หนึ่งไปเป็นอีกแบบหนึ่งถูกต้อง
scottb

1

ฉันใช้สิ่งนี้

public class TimeTools {

    public static Date getTaipeiNowDate() {
        Instant now = Instant.now();
        ZoneId zoneId = ZoneId.of("Asia/Taipei");
        ZonedDateTime dateAndTimeInTai = ZonedDateTime.ofInstant(now, zoneId);
        try {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(dateAndTimeInTai.toString().substring(0, 19).replace("T", " "));
        } catch (ParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }
}

เพราะ Date.from(java.time.ZonedDateTime.ofInstant(now, zoneId).toInstant()); มันไม่เวิร์ค !!! หากคุณเรียกใช้แอปพลิเคชันของคุณในคอมพิวเตอร์ก็ไม่มีปัญหา แต่ถ้าคุณทำงานในภูมิภาคใด ๆ ของ AWS หรือ Docker หรือ GCP ก็จะสร้างปัญหา เนื่องจากคอมพิวเตอร์ไม่ใช่เขตเวลาของคุณบนคลาวด์ คุณควรกำหนดเขตเวลาที่ถูกต้องใน Code ตัวอย่างเช่นเอเชีย / ไทเป จากนั้นจะแก้ไขใน AWS หรือ Docker หรือ GCP

public class App {
    public static void main(String[] args) {
        Instant now = Instant.now();
        ZoneId zoneId = ZoneId.of("Australia/Sydney");
        ZonedDateTime dateAndTimeInLA = ZonedDateTime.ofInstant(now, zoneId);
        try {
            Date ans = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(dateAndTimeInLA.toString().substring(0, 19).replace("T", " "));
            System.out.println("ans="+ans);
        } catch (ParseException e) {
        }
        Date wrongAns = Date.from(java.time.ZonedDateTime.ofInstant(now, zoneId).toInstant());
        System.out.println("wrongAns="+wrongAns);
    }
}

1
นี่ดูเหมือนจะเป็นวิธีที่ซับซ้อนที่สุดวิธีหนึ่งใน 7 คำตอบ มันมีข้อดีหรือไม่? ฉันจะคิดว่าไม่ ไม่จำเป็นต้องผ่านการจัดรูปแบบและการแยกวิเคราะห์
Ole VV

ขอบคุณที่คุณถาม เนื่องจาก Date wrongAns = Date.from (java.time.ZonedDateTime.ofInstant (now, zoneId) .toInstant ()) ;; ไม่เวิร์ค !!!!!
beehuang

ฉันเรียกใช้ตัวอย่างข้อมูลของคุณก่อนเวลา 17:08 น. ในเขตเวลาของฉันและได้รับans=Mon Jun 18 01:07:56 CEST 2018ซึ่งไม่ถูกต้องจากนั้นwrongAns=Sun Jun 17 17:07:56 CEST 2018ซึ่งถูกต้อง
Ole VV

ฉันไม่มีปัญหาใด ๆ เมื่อเรียกใช้แอพในคอมพิวเตอร์ของฉัน แต่เมื่อใช้นักเทียบท่ามันมีปัญหา เนื่องจากเขตเวลาในนักเทียบท่าไม่ใช่เขตเวลาของคุณในคอมพิวเตอร์ คุณควรกำหนดเขตเวลาที่ถูกต้องใน Code ตัวอย่างเช่นเอเชีย / ไทเป จากนั้นจะแก้ไขใน AWS, Docker, GCP หรือคอมพิวเตอร์เครื่องใดก็ได้
beehuang

@ OleV.V. เข้าใจมั้ย ?? หรือคำถามใด ๆ ?
beehuang

0

สำหรับแอปพลิเคชันนักเทียบท่าเช่น beehuang แสดงความคิดเห็นว่าคุณควรกำหนดเขตเวลาของคุณ

หรือคุณสามารถใช้withZoneSameLocal ตัวอย่างเช่น:

2014-07-01T00: 00 + 02: 00 [GMT + 02: 00] แปลงโดย

Date.from(zonedDateTime.withZoneSameLocal(ZoneId.systemDefault()).toInstant())

ถึงอังคาร 01 ก.ค. 00:00:00 CEST 2014และภายในวันที่

Date.from(zonedDateTime.toInstant())

ถึงจันทร์ 30 มิ.ย. 22:00:00 UTC 2014


-1

หากคุณสนใจในตอนนี้เท่านั้นให้ใช้:

Date d = new Date();

ตอนนี้ฉันสนใจ แต่ตอนนี้ UTC
Milen Kovachev

2
ใช่ว่าจะเป็น UTC แล้ววันที่ไม่รู้จะดีไปกว่านี้แล้ว
Jacob Eckel

3
ไม่มันจะไม่ ตอนนี้จะอยู่ในเขตเวลาของระบบท้องถิ่น วันที่ไม่ได้จัดเก็บข้อมูลเขตเวลาใด ๆ แต่ใช้เวลาของระบบปัจจุบันและละเว้นเขตเวลาของระบบปัจจุบันดังนั้นจึงไม่มีการแปลงกลับเป็น UTC
David

2
@David Date จะรักษาเวลาให้สัมพันธ์กับ UTC epoch ดังนั้นหากคุณใช้ Date () ใหม่จากระบบในสหรัฐอเมริกาและวัตถุอื่นในคอมพิวเตอร์ในญี่ปุ่นข้อมูลเหล่านั้นจะเหมือนกัน (ดูระยะเวลาที่ทั้งสองเก็บไว้ภายใน)
Jacob Eckel

1
@JacobEckel - ใช่ แต่ถ้าคุณใส่เวลา 9.00 น. ในวันที่ในขณะที่ tz ของคุณเป็น US tz แล้วเปลี่ยนเป็น JP tz และสร้างวันที่ใหม่ด้วยเวลา 9.00 น. ค่าภายในในวันที่จะแตกต่างกัน ทันทีที่คุณโยน DST ลงในส่วนผสมคุณจะไม่สามารถใช้วันที่ได้อย่างน่าเชื่อถือเว้นแต่แอปของคุณจะทำงานใน UTC โดยเฉพาะซึ่งอาจเปลี่ยนแปลงได้ด้วยเหตุผลหลายประการที่ส่วนใหญ่วนเวียนอยู่กับรหัสที่ไม่ดี
เดวิด
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.