Parsing string dengan tanggal dan waktu ke titik waktu tertentu (Java menyebutnya " Instant
") cukup rumit. Java telah menangani ini dalam beberapa iterasi. Yang terbaru, java.time
dan java.time.chrono
, mencakup hampir semua kebutuhan (kecuali Pelebaran Waktu :)).
Namun, kerumitan itu membawa banyak kebingungan.
Kunci untuk memahami penguraian tanggal adalah:
Mengapa Java memiliki banyak cara untuk menguraikan tanggal
- Ada beberapa sistem untuk mengukur waktu. Misalnya, kalender Jepang historis berasal dari rentang waktu masa pemerintahan kaisar atau dinasti masing-masing. Lalu ada misalnya cap waktu UNIX. Untungnya, seluruh dunia (bisnis) berhasil menggunakan hal yang sama.
- Secara historis, sistem sedang beralih dari / ke, karena berbagai alasan . Misalnya dari kalender Julian ke kalender Gregorian pada 1582. Jadi tanggal 'barat' sebelum itu perlu diperlakukan berbeda.
- Dan tentu saja perubahan itu tidak terjadi sekaligus. Karena kalender berasal dari kantor pusat beberapa agama dan bagian lain Eropa percaya pada diet lain, misalnya Jerman tidak beralih sampai tahun 1700.
... dan mengapa LocalDateTime
, ZonedDateTime
et al. sangat rumit
Ada zona waktu . Zona waktu pada dasarnya adalah "garis" * [1] dari permukaan bumi yang otoritasnya mengikuti aturan yang sama tentang kapan zona waktu itu diimbangi. Ini termasuk aturan waktu musim panas.
Zona waktu berubah dari waktu ke waktu untuk berbagai area, sebagian besar didasarkan pada siapa yang menaklukkan siapa. Dan aturan satu zona waktu juga berubah seiring waktu .
Ada offset waktu. Itu tidak sama dengan zona waktu, karena zona waktu mungkin misalnya "Praha", tetapi memiliki offset waktu musim panas dan waktu musim dingin.
Jika Anda mendapatkan stempel waktu dengan zona waktu, offset dapat bervariasi, tergantung pada bagian tahun apa yang digunakan. Selama jam kabisat, stempel waktu dapat berarti 2 waktu yang berbeda, jadi tanpa informasi tambahan, itu tidak dapat diandalkan. dikonversi.
Catatan: Dengan timestamp yang saya maksud "string yang berisi tanggal dan / atau waktu, opsional dengan zona waktu dan / atau offset waktu."
Beberapa zona waktu dapat berbagi offset waktu yang sama untuk periode tertentu. Misalnya, zona waktu GMT / UTC sama dengan zona waktu "London" ketika offset waktu musim panas tidak berlaku.
Untuk membuatnya sedikit lebih rumit (tapi itu tidak terlalu penting untuk kasus penggunaan Anda):
- Para ilmuwan mengamati dinamika Bumi, yang berubah seiring waktu; berdasarkan itu, mereka menambahkan detik pada akhir tahun individual. (Jadi
2040-12-31 24:00:00
mungkin tanggal-waktu yang valid.) Ini membutuhkan pembaruan rutin metadata yang digunakan sistem agar konversi tanggal tepat. Misalnya di Linux, Anda mendapatkan pembaruan rutin ke paket Java termasuk data baru ini.
Pembaruan tidak selalu menjaga perilaku sebelumnya untuk cap waktu historis dan masa depan. Jadi mungkin terjadi penguraian dua stempel waktu di sekitar perubahan zona waktu yang membandingkannya mungkin memberikan hasil yang berbeda saat dijalankan pada versi perangkat lunak yang berbeda. Itu juga berlaku untuk membandingkan antara zona waktu yang terpengaruh dan zona waktu lainnya.
Jika ini menyebabkan bug pada perangkat lunak Anda, pertimbangkan untuk menggunakan beberapa stempel waktu yang tidak memiliki aturan yang rumit, seperti stempel waktu UNIX .
Karena 7, untuk tanggal mendatang, kami tidak dapat mengonversi tanggal dengan pasti. Jadi, misalnya, penguraian saat ini 8524-02-17 12:00:00
mungkin tidak aktif beberapa detik dari penguraian masa depan.
API JDK untuk ini berkembang dengan kebutuhan kontemporer
- Rilis Java awal baru saja
java.util.Date
memiliki pendekatan yang agak naif, dengan asumsi bahwa hanya ada tahun, bulan, hari, dan waktu. Ini dengan cepat tidak cukup.
- Juga, kebutuhan database berbeda, jadi cukup awal,
java.sql.Date
diperkenalkan, dengan keterbatasan sendiri.
- Karena tidak mencakup kalender dan zona waktu yang berbeda dengan baik,
Calendar
API diperkenalkan.
- Ini masih belum mencakup kompleksitas zona waktu. Namun, campuran API di atas benar-benar menyebalkan. Jadi ketika pengembang Java mulai bekerja pada aplikasi web global, perpustakaan yang menargetkan sebagian besar kasus penggunaan, seperti JodaTime, menjadi cepat populer. JodaTime adalah standar de-facto selama sekitar satu dekade.
- Tetapi JDK tidak berintegrasi dengan JodaTime, jadi bekerja dengannya agak rumit. Jadi, setelah diskusi yang sangat panjang tentang bagaimana mendekati masalah ini, JSR-310 dibuat terutama berdasarkan JodaTime .
Bagaimana menghadapinya di Jawa java.time
Tentukan jenis yang akan diurai timestamp untuk
Saat Anda menggunakan string cap waktu, Anda perlu mengetahui informasi apa yang dikandungnya. Ini adalah poin krusial. Jika Anda tidak melakukan ini dengan benar, Anda berakhir dengan pengecualian samar seperti "Tidak dapat membuat Instan" atau "Zona offset hilang" atau "id zona tidak dikenal" dll.
Apakah ini berisi tanggal dan waktu?
Apakah ada offset waktu?
Offset waktu adalah +hh:mm
bagiannya. Kadang-kadang, +00:00
dapat diganti dengan Z
'Zulu time', UTC
sebagai Universal Time Coordinated, atau GMT
sebagai Greenwich Mean Time. Ini juga mengatur zona waktu.
Untuk cap waktu ini, Anda gunakan OffsetDateTime
.
Apakah ada zona waktu?
Untuk cap waktu ini, Anda gunakan ZonedDateTime
.
Zona ditentukan baik oleh
- nama ("Praha", "Waktu Standar Pasifik", "PST"), atau
- "ID zona" ("Amerika / Los_Angeles", "Eropa / London"), diwakili oleh java.time.ZoneId .
Daftar zona waktu dikompilasi oleh "database TZ" , didukung oleh ICAAN.
Menurut ZoneId
javadoc, id zona juga dapat ditentukan Z
dan diimbangi. Saya tidak yakin bagaimana ini memetakan ke zona nyata. Jika cap waktu, yang hanya memiliki TZ, jatuh ke dalam lompatan waktu dari perubahan offset, maka itu ambigu, dan interpretasinya adalah subjek ResolverStyle
, lihat di bawah.
Jika tidak ada , maka konteks yang hilang diasumsikan atau diabaikan. Dan konsumen harus memutuskan. Jadi itu perlu diurai LocalDateTime
dan dikonversi OffsetDateTime
dengan menambahkan info yang hilang:
- Anda dapat berasumsi bahwa ini adalah waktu UTC. Tambahkan offset UTC 0 jam.
- Anda dapat berasumsi bahwa ini adalah waktu di mana konversi terjadi. Konversikan dengan menambahkan zona waktu sistem.
- Anda dapat mengabaikan dan hanya menggunakannya apa adanya. Itu berguna misalnya untuk membandingkan atau mengurangi dua kali (lihat
Duration
), atau ketika Anda tidak tahu dan itu tidak masalah (misalnya jadwal bus lokal).
Informasi waktu parsial
- Berdasarkan apa timestamp mengandung, Anda dapat mengambil
LocalDate
, LocalTime
, OffsetTime
, MonthDay
, Year
, atau YearMonth
keluar dari itu.
Jika Anda memiliki informasi lengkap, Anda bisa mendapatkan java.time.Instant
. Ini juga digunakan secara internal untuk mengkonversi antara OffsetDateTime
dan ZonedDateTime
.
Cari tahu bagaimana cara menguraikannya
Ada dokumentasi yang luas DateTimeFormatter
yang dapat mengurai string stempel waktu dan memformat ke string.
The pre-diciptakan DateTimeFormatter
s harus mencakup moreless semua format timestamp standar. Misalnya ISO_INSTANT
bisa parse 2011-12-03T10:15:30.123457Z
.
Jika Anda memiliki beberapa format khusus, maka Anda dapat membuat DateTimeFormatter Anda sendiri (yang juga merupakan parser).
private static final DateTimeFormatter TIMESTAMP_PARSER = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.append(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SX"))
.toFormatter();
Saya merekomendasikan untuk melihat kode sumber DateTimeFormatter
dan mendapatkan inspirasi tentang cara membangunnya menggunakan DateTimeFormatterBuilder
. Saat Anda berada di sana, lihat juga ResolverStyle
yang mengontrol apakah parser itu LENIENT, SMART, atau STRICT untuk format dan informasi yang ambigu.
TemporalAccessor
Sekarang, kesalahan yang sering terjadi adalah masuk ke kompleksitas TemporalAccessor
. Ini berasal dari bagaimana para pengembang dulu bekerja dengan SimpleDateFormatter.parse(String)
. Benar, DateTimeFormatter.parse("...")
memberimu TemporalAccessor
.
// No need for this!
TemporalAccessor ta = TIMESTAMP_PARSER.parse("2011-... etc");
Tetapi, dilengkapi dengan pengetahuan dari bagian sebelumnya, Anda dapat dengan mudah menguraikan ke dalam jenis yang Anda butuhkan:
OffsetDateTime myTimestamp = OffsetDateTime.parse("2011-12-03T10:15:30.123457Z", TIMESTAMP_PARSER);
Anda sebenarnya tidak perlu ke DateTimeFormatter
keduanya. Tipe yang ingin Anda parse memiliki parse(String)
metode.
OffsetDateTime myTimestamp = OffsetDateTime.parse("2011-12-03T10:15:30.123457Z");
Mengenai TemporalAccessor
, Anda dapat menggunakannya jika Anda memiliki gagasan yang tidak jelas tentang informasi apa yang ada di string, dan ingin memutuskan saat runtime.
Saya harap saya memberi sedikit pengertian pada jiwa Anda :)
Catatan: Ada backport java.time
ke Java 6 dan 7: ThreeTen-Backport . Untuk Android memiliki ThreeTenABP .
[1] Tidak hanya itu bukan garis-garis, tetapi ada juga beberapa ekstrem aneh. Misalnya, beberapa pulau pasifik yang berdekatan memiliki zona waktu +14: 00 dan -11: 00. Itu berarti, bahwa sementara di satu pulau, ada 1 Mei 3 sore, di pulau lain tidak begitu jauh, itu masih 30 April 12 PM (jika saya menghitung dengan benar :))
ZonedDateTime
lebih daripadaLocalDateTime
. Namanya kontra-intuitif; yangLocal
berarti setiap wilayah secara umum daripada zona waktu tertentu. Dengan demikian, suatuLocalDateTime
objek tidak terikat dengan garis waktu. Untuk memiliki makna, untuk mendapatkan momen tertentu pada garis waktu, Anda harus menerapkan zona waktu.