ZonedDateTime — apa itu, bekerja dengan zona waktu dan waktu

Penulis: IT Sectr Diterbitkan: 2026-07-13 Waktu membaca: 10 mnt

ZonedDateTime — kelas immutable dari paket java.time yang menyimpan tanggal dan waktu bersama dengan informasi zona waktu (ZoneId). Berbeda dengan LocalDateTime, ZonedDateTime secara unik mengidentifikasi momen pada garis waktu. Menurut spesifikasi Oracle Java 17 (2024), kelas ini menangani transisi ke waktu musim panas (DST) dengan benar melalui aturan zona dari basis data IANA Time Zone Database.

Poin Utama

  • ZonedDateTime — kelas immutable yang menggabungkan tanggal, waktu, dan zona waktu (ZoneId) dalam satu objek.
  • Berbeda dengan LocalDateTime, ZonedDateTime secara unik menentukan momen pada garis waktu dan cocok untuk sistem global.
  • Kelas secara otomatis menangani transisi ke waktu musim panas (DST) sesuai dengan aturan IANA Time Zone Database.
  • Untuk konversi antar zona waktu digunakan metode withZoneSameInstant(ZoneId).
  • Penyimpanan ZonedDateTime di basis data direkomendasikan melalui OffsetDateTime atau TIMESTAMP WITH TIME ZONE.

Apa itu ZonedDateTime?

ZonedDateTime — salah satu kelas kunci dari paket java.time, yang mewakili tanggal dan waktu dengan informasi lengkap tentang zona waktu. Ini menggabungkan tiga komponen: LocalDateTime (tanggal dan waktu), ZoneId (identifikasi zona), dan ZoneOffset (offset relatif terhadap UTC).

Berbeda dengan LocalDateTime, yang hanya menyimpan waktu dinding (wall-clock time) tanpa kaitan dengan zona, ZonedDateTime secara unik mengidentifikasi momen. Dua LocalDateTime yang sama di zona waktu yang berbeda mewakili momen waktu yang berbeda. Dua ZonedDateTime yang sama — momen yang sama.

Kelas ini sepenuhnya immutable dan thread-safe. Semua operasi aritmatika mengembalikan objek baru. ZonedDateTime mengimplementasikan antarmuka ChronoZonedDateTime dan dapat digunakan di mana pun diperlukan bekerja dengan waktu zona di Java.

Menurut spesifikasi Oracle Java 17, ZonedDateTime mendukung bekerja dengan zona apa pun dari IANA Time Zone Database, yang mencakup lebih dari 600 zona waktu.

ZonedDateTime vs LocalDateTime: apa perbedaannya?

Perbedaan utama — ZonedDateTime berisi zona waktu, sedangkan LocalDateTime tidak. Perbedaan mendasar ini menentukan ruang lingkup penerapan setiap kelas.

LocalDateTime digunakan untuk acara lokal: waktu konser, jadwal pelajaran, tanggal lahir. Jika acara berlangsung di Moskow pukul 15:00, LocalDateTime akan mencatat 15:00 tanpa kaitan. Jika Anda memindahkan server ke New York, waktu tetap 15:00 — tetapi ini sudah merupakan momen fisik yang berbeda.

ZonedDateTime diterapkan untuk data global: log server, stempel waktu di API, pertemuan internasional. Jika pertemuan dijadwalkan pada pukul 15:00 MSK, ZonedDateTime akan menyimpan waktu dan zona. Di New York akan ditampilkan dengan benar sebagai 8:00 EST. Menurut Baeldung (2024), pilihan antara LocalDateTime dan ZonedDateTime adalah keputusan arsitektur paling umum saat bekerja dengan tanggal.

Aturan praktis: jika data disimpan untuk satu wilayah — gunakan LocalDateTime. Jika data melintasi batas zona waktu — gunakan ZonedDateTime. Jika Anda perlu mengirimkan momen absolut — gunakan Instant.

Bagaimana cara kerja zona waktu di java.time?

Zona waktu di java.time diwakili oleh kelas ZoneId. ZoneId adalah pengidentifikasi zona dalam format “continent/region”, misalnya “Europe/Moscow”, “America/New_York”, “Asia/Tokyo”. ZoneId diperoleh melalui metode statis of(String zoneId) atau melalui zona waktu sistem default.

ZoneId dibagi menjadi dua tipe: fixed offset (offset tetap, misalnya “+03:00”) dan region-based (zona regional, misalnya “Europe/London”). Zona regional berisi aturan transisi ke waktu musim panas dan perubahan historis. Fixed offset — hanya offset tetap.

Untuk mendapatkan offset saat ini dari ZoneId pada momen tertentu, digunakan metode getRules(), yang mengembalikan ZoneRules. ZoneRules berisi semua transisi dan offset untuk zona tersebut. Ini adalah mekanisme kunci untuk penanganan DST yang benar.

Semua zona waktu disediakan dengan JDK melalui file tzdata (IANA Time Zone Database) dan diperbarui secara teratur. Di Android, versi tzdata tergantung pada pembaruan sistem melalui Google Play Services.

Membuat ZonedDateTime

Ada beberapa cara untuk membuat ZonedDateTime. Yang paling sederhana — now(), yang mengembalikan waktu saat ini di zona waktu sistem. Varian now(ZoneId) memungkinkan mendapatkan waktu saat ini di zona yang ditentukan.

Metode of(LocalDateTime, ZoneId) membuat ZonedDateTime dari waktu lokal dan zona. Varian of(int year, int month, int dayOfMonth, int hour, int minute, int second, int nanoOfSecond, ZoneId zone) — dari komponen.

LocalDateTime dapat dikonversi ke ZonedDateTime melalui metode atZone(ZoneId). Instant — melalui Instant.atZone(ZoneId). Date — melalui Date.toInstant().atZone(ZoneId).

kotlin
val moscowZone = ZoneId.of("Europe/Moscow")

val nowInMoscow = ZonedDateTime.now(moscowZone)

val fromComponents = ZonedDateTime.of(
    2026, 7, 21, 15, 30, 0, 0, moscowZone
)

val fromLocal = LocalDateTime.now().atZone(moscowZone)

val fromInstant = Instant.now().atZone(moscowZone)

Konversi antar zona waktu

Metode konversi utama — withZoneSameInstant(ZoneId). Mengonversi ZonedDateTime ke zona waktu lain, mempertahankan momen waktu yang sama. Misalnya, 15:00 MSK → 8:00 EST. Metode withZoneSameLocal(ZoneId) mengubah zona, mempertahankan waktu lokal — ini memberikan momen yang berbeda.

Untuk mendapatkan offset relatif terhadap UTC, digunakan metode getOffset(), yang mengembalikan ZoneOffset. ZoneOffset adalah turunan dari ZoneId yang mewakili offset tetap dalam format “+HH:mm” atau “-HH:mm”.

Konversi ke Instant dilakukan melalui metode toInstant(). Instant adalah momen waktu absolut, tidak tergantung pada zona waktu. Konversi terbalik — Instant.atZone(ZoneId).

kotlin
val moscow = ZonedDateTime.of(
    2026, 7, 21, 15, 0, 0, 0,
    ZoneId.of("Europe/Moscow")
)

val newYork = moscow.withZoneSameInstant(
    ZoneId.of("America/New_York")
)

val utcInstant = moscow.toInstant()
val backToMoscow = utcInstant.atZone(ZoneId.of("Europe/Moscow"))

Bekerja dengan waktu musim panas (DST)

Transisi ke waktu musim panas menciptakan dua masalah: celah (gap) dan tumpang tindih (overlap). Celah terjadi di musim semi ketika jam dimajukan — waktu tertentu tidak ada. Tumpang tindih — di musim gugur ketika waktu dimundurkan — waktu yang sama ada dua kali.

ZonedDateTime menangani situasi ini melalui strategi resolve. Saat membuat objek selama celah, java.time secara otomatis memajukan waktu sebesar nilai offset. Saat membuat selama tumpang tindih, varian pertama (sebelum perubahan) dipilih. Perilaku dapat diubah melalui withZoneSameInstant.

Untuk memeriksa apakah waktu berada di zona DST, gunakan zone.getRules().isDaylightSavings(instant). Metode getOffset() menunjukkan offset saat ini untuk momen tersebut, dan getRules().getDaylightSavings(instant) — besarnya koreksi DST dalam milidetik.

kotlin
fun checkDST(zdt: ZonedDateTime) {
    val rules = zdt.getZone().getRules()
    val instant = zdt.toInstant()

    if (rules.isDaylightSavings(instant)) {
        val dstAmount = rules.getDaylightSavings(instant)
        Log.d("DST", "Offset DST: $dstAmount")
    }
}

Memformat ZonedDateTime

Untuk memformat ZonedDateTime digunakan DateTimeFormatter. Format ISO standar mencakup tanggal, waktu, dan offset: “2026-07-21T15:30:00+03:00[Europe/Moscow]”. Format yang telah ditentukan: ISO_ZONED_DATE_TIME, ISO_OFFSET_DATE_TIME, ISO_INSTANT.

Gunakan DateTimeFormatter.ofLocalizedDateTime(FormatStyle) untuk pemformatan yang dilokalkan. FormatStyle dapat berupa SHORT, MEDIUM, LONG, FULL. LONG menyertakan nama zona (“MSK”), FULL — nama lengkap (“Moscow Standard Time”).

Penting: saat mengurai string dengan ZonedDateTime, format harus berisi informasi tentang zona atau offset. Jika zona tidak ditentukan, gunakan LocalDateTime.parse() dan kemudian atZone().

kotlin
val zdt = ZonedDateTime.now(ZoneId.of("Europe/Moscow"))

val iso = zdt.format(DateTimeFormatter.ISO_ZONED_DATE_TIME)

val custom = DateTimeFormatter
    .ofPattern("dd.MM.yyyy HH:mm z")
val formatted = zdt.format(custom)

val parsed = ZonedDateTime.parse(
    "2026-07-21T15:30:00+03:00",
    DateTimeFormatter.ISO_OFFSET_DATE_TIME
)

ZonedDateTime di Android: contoh praktis

Contoh pertama — menampilkan waktu pertemuan untuk pengguna di zona waktunya sendiri. Server mengembalikan ZonedDateTime dalam UTC, klien mengonversi ke zona waktu lokal perangkat.

kotlin
fun displayMeetingTime(
    serverUtc: ZonedDateTime
): String {
    val deviceZone = ZoneId.systemDefault()
    val localTime = serverUtc.withZoneSameInstant(deviceZone)
    val formatter = DateTimeFormatter
        .ofPattern("dd.MM.yyyy HH:mm z")
    return localTime.format(formatter)
}

Contoh kedua — menghitung waktu hingga acara berikutnya dengan mempertimbangkan zona waktu. Kami menggunakan ZonedDateTime untuk waktu server dan Duration.between() untuk menghitung perbedaannya.

kotlin
fun timeUntilEvent(eventTime: ZonedDateTime): String {
    val now = ZonedDateTime.now()
    val duration = Duration.between(now, eventTime)

    val hours = duration.toHours()
    val minutes = duration.toMinutes() % 60
    return "Remaining $hours h $minutes min"
}

Contoh ketiga — bekerja dengan API Retrofit. Server mengembalikan string dalam ISO-8601 dengan zona. Kami menggunakan deserializer kustom untuk konversi ke ZonedDateTime.

kotlin
data class EventResponse(
    @JsonAdapter(ZonedDateTimeAdapter::class)
    val eventTime: ZonedDateTime
)

class ZonedDateTimeAdapter : JsonAdapter<ZonedDateTime>() {
    override fun fromJson(reader: JsonReader): ZonedDateTime? {
        return ZonedDateTime.parse(
            reader.nextString()
        )
    }
}

Kesalahan dalam bekerja dengan zona waktu

Kesalahan pertama — penggunaan ZoneId.systemDefault() dalam kode server. Zona waktu server mungkin berbeda dari klien, dan penggunaan zona sistem di server menyebabkan perhitungan yang salah. Selalu tentukan zona secara eksplisit atau gunakan UTC sebagai referensi.

Kesalahan kedua — mengabaikan DST saat menghitung durasi. Duration.between() menangani transisi dengan benar, tetapi jika Anda mengurangi stempel waktu secara manual, transisi ke waktu musim panas dapat memberikan kesalahan 1 jam. Gunakan metode ChronoUnit.HOURS.between() sebagai ganti perhitungan manual.

Kesalahan ketiga — kebingungan antara withZoneSameInstant dan withZoneSameLocal. Yang pertama mengubah zona, mempertahankan momen — waktu bergeser. Yang kedua mengubah zona, mempertahankan waktu lokal — momen berubah. Pemilihan metode yang salah adalah salah satu kesalahan paling umum menurut SonarSource (2024).

Kesalahan keempat — asumsi bahwa zona waktu perangkat selalu sama dengan zona waktu pengguna. Pengguna dapat bepergian dan mengharapkan aplikasi menampilkan waktu di zona “rumah” mereka, bukan zona saat ini. Dalam hal ini, Anda perlu menyediakan pilihan zona melalui antarmuka.

Pertanyaan yang Sering Diajukan

Apa perbedaan antara ZonedDateTime dan OffsetDateTime?

ZonedDateTime berisi pengidentifikasi zona regional (misalnya “Europe/Moscow”) dan menangani DST. OffsetDateTime hanya menyimpan offset tetap (+03:00) tanpa aturan regional. Untuk penyimpanan di basis data, OffsetDateTime direkomendasikan.

Bagaimana cara mendapatkan waktu saat ini dalam UTC melalui ZonedDateTime?

Gunakan ZonedDateTime.now(ZoneOffset.UTC) atau Instant.now().atZone(ZoneOffset.UTC). Kedua varian mengembalikan momen saat ini dengan offset nol. Untuk stempel waktu sederhana, gunakan Instant.now() tanpa kaitan ke zona.

Bisakah ZonedDateTime diserialisasi melalui Gson atau Moshi?

Ya, tetapi diperlukan adapter kustom. Gson tidak mendukung ZonedDateTime secara default. Moshi — mendukung melalui adapter Rfc3339DateJsonAdapter. Untuk Jackson, disarankan menggunakan Kotlinx Serialization atau pustaka JavaTimeModule.

Bagaimana menangani situasi ketika waktu jatuh ke celah DST?

java.time secara otomatis memajukan waktu sebesar nilai offset. Misalnya, jika waktu 02:30 tidak ada saat transisi ke 03:00, ZonedDateTime akan membuat objek pada 03:30. Keberadaan celah dapat diperiksa melalui ZoneRules.getTransition(instant).

Mengapa ZonedDateTime tidak direkomendasikan untuk basis data SQL?

JDBC 4.2 mendukung OffsetDateTime, tetapi tidak ZonedDateTime secara langsung. ZonedDateTime berisi zona regional yang tidak memiliki padanan di SQL. Disarankan untuk menyimpan OffsetDateTime atau Instant, dan zona disimpan di kolom terpisah.

Kesimpulan

  • ZonedDateTime — kelas immutable untuk tanggal dan waktu dengan zona waktu, yang menangani DST dengan benar melalui IANA Time Zone Database.
  • Perbedaan utama dari LocalDateTime — adanya zona, yang menjadikan ZonedDateTime pengidentifikasi unik momen waktu.
  • Untuk konversi antar zona, gunakan withZoneSameInstant(), yang mempertahankan momen, bukan withZoneSameLocal.
  • Saat transisi ke waktu musim panas, java.time secara otomatis menyelesaikan celah dan tumpang tindih melalui aturan zona bawaan.
  • Untuk penyimpanan di basis data, gunakan OffsetDateTime atau simpan Instant dan ZoneId secara terpisah.
  • Di Android, untuk mengonversi ZonedDateTime ke waktu lokal perangkat, gunakan ZoneId.systemDefault() dengan withZoneSameInstant.
  • Untuk serialisasi JSON, diperlukan adapter kustom — gunakan Kotlinx Serialization atau Jackson JavaTimeModule.

Kami akan mengembangkan aplikasi seluler turnkey

IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.

Diskusikan proyek

Baca juga