Bagaimana cara mengonversi ZonedDateTime ke Date?

103

Saya mencoba menyetel waktu tanggal agnostik server dalam database saya dan saya yakin praktik terbaik untuk melakukannya adalah dengan menyetel UTC DateTime. Server db saya adalah Cassandra dan driver db untuk Java hanya memahami tipe Tanggal.

Jadi dengan asumsi bahwa dalam kode saya, saya menggunakan Java 8 ZonedDateTime baru untuk mendapatkan UTC sekarang ( ZonedDateTime.now(ZoneOffset.UTC)), bagaimana saya dapat mengonversi instance ZonedDateTime ini ke kelas Tanggal "warisan"?

Milen Kovachev
sumber
Ada dua kelas "Tanggal" yang dibangun ke dalam Java, java.util.Datedan java.sql.Date.
Basil Bourque

Jawaban:

163

Anda dapat mengubah ZonedDateTime menjadi instan, yang dapat Anda gunakan secara langsung dengan Tanggal.

Date.from(java.time.ZonedDateTime.now().toInstant());
Slim Soltani Dridi
sumber
27
Tidak, ini akan menjadi Tanggal saat ini di default sistem zona Anda.
Slim Soltani Dridi
6
@MilenKovachev Pertanyaan Anda tidak masuk akal - Tanggal tidak memiliki zona waktu - ini hanya mewakili sekejap dalam waktu.
assylias
1
@assylias Sebenarnya, pernyataan Anda tidak masuk akal. Format penyimpanan waktu menunjukkan zona waktu. Tanggal didasarkan pada UTC, sayangnya, Java melakukan beberapa hal bodoh dan tidak memperlakukannya seperti itu, dan di atas itu, menganggap waktu sebagai TZ lokal, bukan UTC. Cara Data, LocalDateTime, ZonedDateTime menyimpan data menyiratkan zona waktu. Cara mereka digunakan adalah seolah-olah TZ tidak ada, yang sebenarnya salah. java.util.Date memiliki TZ default JVM, secara implisit. Fakta bahwa orang memperlakukannya sebagai sesuatu yang berbeda (termasuk dokumentasi java untuk itu!) Hanyalah orang jahat.
David
5
@David uh no - a Dateadalah jumlah milidetik sejak epoch - jadi ini terkait dengan UTC. Jika Anda mencetaknya , zona waktu default akan digunakan, tetapi kelas Tanggal tidak memiliki pengetahuan tentang zona waktu pengguna ... Lihat misalnya bagian "pemetaan" di docs.oracle.com/javase/tutorial/datetime/iso/legacy .html Dan LocalDateTime secara eksplisit tanpa mengacu pada zona waktu - yang dapat dianggap membingungkan ...
assylias
1
Jawaban Anda tidak perlu melibatkan ZonedDateTime. The java.time.Instantkelas pengganti langsung untuk java.util.Date, baik mewakili momen di UTC meskipun Instantmenggunakan resolusi yang lebih halus nanodetik bukan milidetik. Date.from( Instant.now() )seharusnya menjadi solusi Anda. Atau dalam hal ini, new Date()yang memiliki efek yang sama, menangkap momen saat ini dalam UTC.
Basil Bourque
66

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

Padahal tidak ada gunanya kode di atas. Keduanya java.util.Datedan Instantmewakili momen dalam UTC, selalu dalam UTC. Kode di atas memiliki efek yang sama seperti:

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

Tidak ada manfaat di sini untuk digunakan ZonedDateTime. Jika Anda sudah memiliki ZonedDateTime, sesuaikan ke UTC dengan mengekstrak file 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.
)

Jawaban Lain Benar

The Answer oleh ssoltanid benar alamat pertanyaan spesifik Anda, bagaimana mengkonversi objek java.time baru-sekolah ( ZonedDateTime) untuk sekolah tua java.util.Dateobjek. Ekstrak Instantdari ZonedDateTime dan teruskan ke java.util.Date.from().

Data hilang

Perhatikan bahwa Anda akan mengalami kehilangan data , karena Instantmelacak nanodetik sejak epoch sementara java.util.Datemelacak milidetik sejak epoch.

diagram yang membandingkan resolusi milidetik, mikrodetik, dan nanodetik

Pertanyaan dan komentar Anda mengangkat masalah lain.

Pertahankan Server Dalam UTC

Server Anda harus mengatur OS host mereka ke UTC sebagai praktik terbaik secara umum. JVM mengambil pengaturan OS host ini sebagai zona waktu default-nya, dalam implementasi Java yang saya ketahui.

Tentukan Zona Waktu

Tetapi Anda tidak boleh bergantung pada zona waktu default JVM saat ini. Daripada mengambil pengaturan host, bendera yang diteruskan saat meluncurkan JVM dapat mengatur zona waktu lain. Lebih buruk lagi: Kode apa pun di utas apa pun dari aplikasi apa pun kapan saja dapat melakukan panggilan ke java.util.TimeZone::setDefaultuntuk mengubah default itu pada waktu proses!

TimestampJenis Cassandra

Setiap basis data dan driver yang layak harus secara otomatis menangani penyesuaian waktu tanggal yang dilewati ke UTC untuk penyimpanan. Saya tidak menggunakan Cassandra, tetapi tampaknya memiliki beberapa dukungan dasar untuk tanggal-waktu. Dokumentasi mengatakan bahwa Timestamptipenya adalah hitungan milidetik dari periode yang sama (momen pertama tahun 1970 di UTC).

ISO 8601

Selanjutnya, Cassandra menerima input string dalam format standar ISO 8601 . Untungnya, java.time menggunakan format ISO 8601 sebagai default untuk parsing / menghasilkan string. The Instantkelas toStringimplementasi akan melakukan dengan baik.

Presisi: Milidetik vs Nano

Tapi pertama-tama kita perlu mengurangi presisi nanodetik dari ZonedDateTime ke milidetik. Salah satu caranya adalah membuat Instan baru menggunakan milidetik. Untungnya, java.time memiliki beberapa metode praktis untuk mengonversi ke dan dari milidetik.

Kode Contoh

Berikut beberapa contoh kode di 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

Atau menurut dokumen driver Cassandra Java ini , Anda dapat memberikan java.util.Datecontoh (jangan bingung dengan java.sqlDate). Jadi Anda bisa membuat juDate dari itu instantTruncatedToMillisecondspada kode di atas.

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

Jika sering melakukan ini, Anda bisa membuat satu baris.

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

Tetapi akan lebih baik jika membuat sedikit metode utilitas.

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

Perhatikan perbedaan di semua kode ini daripada di Pertanyaan. Kode Pertanyaan mencoba menyesuaikan zona waktu contoh ZonedDateTime ke UTC. Tapi itu tidak perlu. Secara konseptual:

ZonedDateTime = Instan + ZoneId

Kami hanya mengekstrak bagian Instan, yang sudah dalam UTC (pada dasarnya dalam UTC, baca dokumen kelas untuk detail tepatnya).


Tabel tipe tanggal-waktu di Jawa, baik modern maupun lawas


Tentang java.time

The java.time kerangka dibangun ke Jawa 8 dan kemudian. Kelas-kelas ini menggantikan tua merepotkan warisan kelas tanggal-waktu seperti java.util.Date, Calendar, & SimpleDateFormat.

Proyek Joda-Time , sekarang dalam mode pemeliharaan , menyarankan migrasi ke kelas java.time .

Untuk mempelajari lebih lanjut, lihat Tutorial Oracle . Dan cari Stack Overflow untuk banyak contoh dan penjelasan. Spesifikasinya adalah JSR 310 .

Anda dapat bertukar objek java.time langsung dengan database Anda. Gunakan driver JDBC yang sesuai dengan JDBC 4.2 atau yang lebih baru. Tidak perlu string, tidak perlu java.sql.*kelas.

Dimana mendapatkan kelas java.time?

Tabel library java.time mana yang akan digunakan dengan versi Java atau Android apa

Proyek ThreeTen-Extra memperluas java.time dengan kelas tambahan. Proyek ini adalah tempat pembuktian untuk kemungkinan penambahan java.time di masa mendatang. Anda mungkin menemukan beberapa kelas berguna di sini seperti Interval, YearWeek, YearQuarter, dan lebih .

Basil Bourque
sumber
Penjelasan yang bagus, sangat rinci. Terima kasih banyak!
Gianmarco F.
5

Jika Anda menggunakan backport ThreeTen untuk Android dan tidak dapat menggunakan yang lebih baru Date.from(Instant instant)(yang membutuhkan minimal API 26), Anda dapat menggunakan:

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

atau:

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

Harap baca juga saran dalam jawaban Basil Bourque

David Rawson
sumber
1
ThreeTen Backport (dan ThreeTenABP) menyertakan DateTimeUtilskelas dengan metode konversi, jadi saya akan menggunakan Date date = DateTimeUtils.toDate (zdt.toInstant ()); `. Itu tidak terlalu rendah.
Ole VV
4

Berikut adalah contoh yang mengubah waktu sistem saat ini ke UTC. Ini melibatkan pemformatan ZonedDateTime sebagai String dan kemudian objek String akan diuraikan menjadi objek tanggal menggunakan 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();
    }
Asanka Siriwardena
sumber
1

Anda dapat melakukan ini menggunakan kelas java.time yang dibangun pada Java 8 dan yang lebih baru.

ZonedDateTime temporal = ...
long epochSecond = temporal.getLong(INSTANT_SECONDS);
int nanoOfSecond = temporal.get(NANO_OF_SECOND);
Date date = new Date(epochSecond * 1000 + nanoOfSecond / 1000000);
Peter Lawrey
sumber
Maaf, dari manakah temporal dan semua konstanta ini berasal?
Milen Kovachev
@MilenKovachev A ZonedDateTime adalah turunan dari Temporal
Peter Lawrey
Terima kasih, tetapi Anda seharusnya menyebutkan bahwa Anda mengedit jawaban Anda sehingga komentar saya tidak tampak konyol.
Milen Kovachev
2
@MilenKovachev Anda dapat menghapus komentar, tetapi ini bukan pertanyaan yang konyol.
Peter Lawrey
1
Saya tidak dapat memahami mengapa jawaban ini tidak disukai. Instants trek detik dan nanodetik. DateMelacak milidetik. Konversi dari satu ke yang lain ini benar.
scottb
1

Saya menggunakan ini.

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

Karena Date.from(java.time.ZonedDateTime.ofInstant(now, zoneId).toInstant()); itu tidak berhasil !!! Jika Anda menjalankan aplikasi Anda di komputer, itu tidak masalah. Tetapi jika Anda menjalankannya di wilayah AWS atau Docker atau GCP mana pun, itu akan menimbulkan masalah. Karena komputer bukan zona waktu Anda di Cloud. Anda harus menyetel zona waktu dengan benar di Kode. Misalnya, Asia / Taipei. Kemudian akan diperbaiki di AWS atau Docker atau 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);
    }
}
beehuang
sumber
1
Ini tampaknya menjadi salah satu cara paling kompleks di salah satu dari 7 jawaban. Apakah ada keuntungannya? Saya tidak akan berpikir. Tidak perlu melalui pemformatan dan penguraian.
Ole VV
Terima kasih, Anda bertanya. Karena Date wrongAns = Date.from (java.time.ZonedDateTime.ofInstant (sekarang, zoneId) .toInstant ()) ;; Ini tidak berhasil !!!!!
beehuang
Saya menjalankan cuplikan detik sebelum pukul 17:08 di zona waktu saya dan mendapatkan ans=Mon Jun 18 01:07:56 CEST 2018, mana yang salah, dan kemudian wrongAns=Sun Jun 17 17:07:56 CEST 2018, mana yang benar.
Ole VV
Saya tidak punya masalah saat menjalankan aplikasi di komputer saya. Tapi saat menggunakan buruh pelabuhan, itu bermasalah. Karena zona waktu di buruh pelabuhan bukan zona waktu Anda di komputer. Anda harus menyetel zona waktu dengan benar di Kode. Misalnya, Asia / Taipei. Kemudian akan diperbaiki di AWS, Docker, GCP atau komputer mana pun.
beehuang
@ OleV.V. Bisakah kamu mengerti ?? atau ada pertanyaan?
beehuang
1

Jawaban yang diterima tidak berhasil untuk saya. Tanggal yang dikembalikan selalu Tanggal lokal dan bukan Tanggal untuk Zona Waktu asli. Saya tinggal di UTC + 2.

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

Saya telah menemukan dua cara alternatif untuk mendapatkan Tanggal yang benar dari ZonedDateTime.

Katakanlah Anda memiliki ZonedDateTime untuk Hawaii ini

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

atau untuk UTC seperti yang awalnya diminta

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

Alternatif 1

Kita bisa menggunakan java.sql.Timestamp. Ini sederhana tetapi mungkin juga akan mengurangi integritas pemrograman Anda

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

Alternatif 2

Kami membuat Tanggal dari milis (dijawab di sini sebelumnya). Perhatikan bahwa ZoneOffset lokal adalah suatu keharusan.

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

Untuk aplikasi buruh pelabuhan seperti beehuang komentar Anda harus mengatur zona waktu Anda.

Atau Anda dapat menggunakan withZoneSameLocal . Sebagai contoh:

2014-07-01T00: 00 + 02: 00 [GMT + 02: 00] dikonversi oleh

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

sampai Selasa 01 Jul 00:00:00 CEST 2014 dan pada

Date.from(zonedDateTime.toInstant())

hingga Sen 30 Jun 22:00:00 UTC 2014

dwe
sumber
-1

Jika Anda hanya tertarik pada saat ini, cukup gunakan:

Date d = new Date();
Jacob Eckel
sumber
Saya tertarik sekarang tapi UTC sekarang.
Milen Kovachev
2
Ya, itu akan menjadi UTC sekarang, Tanggal tidak tahu yang lebih baik.
Jacob Eckel
3
Tidak, tidak akan. Sekarang akan berada di zona waktu sistem lokal. Tanggal tidak menyimpan informasi zona waktu apa pun, tetapi menggunakan waktu sistem saat ini dan mengabaikan zona waktu sistem saat ini, sehingga tidak pernah mengubahnya kembali ke UTC
David
2
@David Date menjaga waktu relatif terhadap periode UTC, jadi jika Anda mengambil Date () baru dari sistem di AS dan objek lain dari komputer di Jepang, mereka akan identik (lihat berapa lama keduanya disimpan secara internal).
Jacob Eckel
1
@JacobEckel - Ya, tetapi jika Anda memasukkan 9am ke dalam tanggal sementara tz Anda adalah US tz dan kemudian mengubah ke JP tz dan membuat tanggal baru dengan 9am, nilai internal di Date akan berbeda. Segera setelah Anda memasukkan DST ke dalam campuran, Anda tidak dapat menggunakan Date dengan andal kecuali aplikasi Anda SELALU berjalan di UTC secara khusus, yang dapat berubah karena sejumlah alasan yang sebagian besar berkisar pada kode yang buruk.
David