Rapat daring antara tim di Jakarta dan tim di Tokyo punya satu momen mulai yang sama, tetapi dua angka jam yang berbeda: peserta Jakarta membuka laptop pukul 08:00, peserta Tokyo pukul 10:00. Nilai waktu dinding seperti LocalDateTime tidak cukup untuk mewakili kejadian semacam itu karena makna “pukul berapa” bergantung pada zona si pembaca. ZonedDateTime menyelesaikannya dengan menggabungkan tiga informasi dalam satu objek: tanggal-waktu lengkap, pengenal zona (ZoneId), dan offset aktual yang berlaku pada momen tersebut. Pada Chapter ini kita pelajari anatomi ZonedDateTime, konversi antarzona, dan posisinya sebagai jembatan antar tipe java.time lainnya.

Membuat Objek ZonedDateTime #

Tiga jalur pembuatan yang lazim: ZonedDateTime.of(tahun, bulan, tanggal, jam, menit, detik, nano, zoneId) mengisi seluruh komponen; kelebihan beban of(LocalDateTime, zoneId) menginterpretasikan nilai waktu dinding pada suatu zona; dan parse() membaca teks lengkap berisi waktu, offset, dan nama zona dalam kurung siku:

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class MembuatZonedDateTime {
    public static void main(String[] args) {
        ZoneId jakarta = ZoneId.of("Asia/Jakarta");

        // Jalur 1: isi komponen lengkap beserta ZoneId.
        ZonedDateTime rapat = ZonedDateTime.of(2026, 1, 15, 8, 0, 0, 0, jakarta);

        // Jalur 2: dari LocalDateTime yang sudah ada + ZoneId.
        ZonedDateTime acara = ZonedDateTime.of(LocalDateTime.of(2026, 1, 15, 20, 0), jakarta);

        // Jalur 3: parse teks lengkap berisi waktu + offset + zona.
        ZonedDateTime dariTeks = ZonedDateTime.parse("2026-01-15T08:00+07:00[Asia/Jakarta]");

        System.out.println("rapat    : " + rapat);
        System.out.println("acara    : " + acara);
        System.out.println("dariTeks : " + dariTeks);

        System.out.println("getZone()        : " + rapat.getZone());
        System.out.println("getOffset()      : " + rapat.getOffset());
        System.out.println("getDayOfWeek()   : " + rapat.getDayOfWeek());
        System.out.println("toInstant()      : " + rapat.toInstant());
        System.out.println("toLocalDateTime(): " + rapat.toLocalDateTime());
    }
}

Output yang Dihasilkan:

text
rapat    : 2026-01-15T08:00+07:00[Asia/Jakarta]
acara    : 2026-01-15T20:00+07:00[Asia/Jakarta]
dariTeks : 2026-01-15T08:00+07:00[Asia/Jakarta]
getZone()        : Asia/Jakarta
getOffset()      : +07:00
getDayOfWeek()   : THURSDAY
toInstant()      : 2026-01-15T01:00:00Z
toLocalDateTime(): 2026-01-15T08:00

Mari kita telaah baris-baris kode di atas:

  1. Baris 9 (ZonedDateTime.of(2026, 1, 15, 8, 0, 0, 0, jakarta)): pukul 08:00 diberi makna di zona Asia/Jakarta; objek menyimpan tanggal, waktu, zona, sekaligus offset yang berlaku.
  2. Baris 12 (of(LocalDateTime..., jakarta)): nilai waktu dinding diinterpretasikan pada zona yang ditentukan — pola yang tepat saat data datang dari LocalDateTime.
  3. Baris 15 (parse): teks 2026-01-15T08:00+07:00[Asia/Jakarta] adalah bentuk toString() bawaan — format ISO-8601 yang diperluas dengan offset dan nama zona dalam kurung siku.
  4. Baris 22-23: getZone() mengembalikan pengenal zona Asia/Jakarta, sedangkan getOffset() mengembalikan offset aktual +07:00 yang berlaku pada momen tersebut.
  5. Baris 24-26: toInstant() menerjemahkan momen ke garis waktu UTC (08:00 dikurangi tujuh jam menjadi 01:00Z), toLocalDateTime() mengambil kembali nilai waktu dindingnya.

Perhatikan anatomi teks 2026-01-15T08:00+07:00[Asia/Jakarta]: bagian pertama adalah tanggal-waktu lokal, bagian +07:00 adalah offset terhadap UTC, dan bagian dalam kurung siku adalah pengenal zona lengkap. Offset adalah angka yang sedang berlaku; zona adalah identitas aturan yang menentukan angka itu — pembedaan penting yang akan kita dalami pada bab berikutnya.

Konversi Antar Zona #

Dua method konversi tersedia dan keduanya membingungkan bila dipilih asal pilih. withZoneSameInstant(zonaBaru) mempertahankan momen yang sama lalu menghitung ulang jam dinding di zona tujuan — inilah yang kita maksud dengan “konversi waktu” dalam percakapan sehari-hari. Sebaliknya, withZoneSameLocal(zonaBaru) menyalin angka jam dinding apa adanya ke zona baru, sehingga momen yang diwakili menjadi berbeda:

import java.time.ZoneId;
import java.time.ZonedDateTime;

public class KonversiZona {
    public static void main(String[] args) {
        ZonedDateTime diJakarta =
                ZonedDateTime.of(2026, 1, 15, 8, 0, 0, 0, ZoneId.of("Asia/Jakarta"));
        System.out.println("di Jakarta   : " + diJakarta);

        // withZoneSameInstant: momen yang sama, dibaca dari zona lain.
        ZonedDateTime diTokyo = diJakarta.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));
        System.out.println("di Tokyo     : " + diTokyo);
        System.out.println("jam di Tokyo : " + diTokyo.getHour());

        // Keduanya menunjuk momen yang sama di garis waktu.
        System.out.println("Instant sama? " + diJakarta.toInstant().equals(diTokyo.toInstant()));

        // withZoneSameLocal: waktu dinding disalin, momennya menjadi berbeda.
        ZonedDateTime jamSama = diJakarta.withZoneSameLocal(ZoneId.of("Asia/Tokyo"));
        System.out.println("jam sama     : " + jamSama);
        System.out.println("Instant sama? " + diJakarta.toInstant().equals(jamSama.toInstant()));

        // Penjumlahan pada ZonedDateTime mengikuti waktu dinding setempat.
        System.out.println("plus 2 jam   : " + diTokyo.plusHours(2));
    }
}

Output yang Dihasilkan:

text
di Jakarta   : 2026-01-15T08:00+07:00[Asia/Jakarta]
di Tokyo     : 2026-01-15T10:00+09:00[Asia/Tokyo]
jam di Tokyo : 10
Instant sama? true
jam sama     : 2026-01-15T08:00+09:00[Asia/Tokyo]
Instant sama? false
plus 2 jam   : 2026-01-15T12:00+09:00[Asia/Tokyo]

Baris 11-13 memakai withZoneSameInstant: rapat pukul 08:00 WIB adalah pukul 10:00 JST karena Tokyo berjalan dua jam lebih maju dari Jakarta. Baris 16 membuktikan kesamaan momen — toInstant() keduanya menghasilkan objek Instant yang sama. Baris 19-21 memakai withZoneSameLocal: angka 08:00 dipertahankan di Tokyo, dan pemeriksaan Instant mengembalikan false karena kedua nilai kini merujuk dua momen berbeda. Baris 24 menegaskan bahwa penjumlahan jam pada ZonedDateTime bekerja pada waktu dinding setempat.

Jembatan Antar Tipe Date-Time #

ZonedDateTime berdiri di pusat jaringan konversi java.time. Dari satu objek ZonedDateTime kita bisa mengambil Instant untuk kebutuhan mesin, LocalDateTime untuk waktu dinding, LocalDate dan LocalTime untuk komponennya, dan sebaliknya:

Arah konversiMethodMakna hasilnya
ZonedDateTime → InstanttoInstant()momen absolut di garis waktu UTC
ZonedDateTime → LocalDateTimetoLocalDateTime()waktu dinding di zonanya
Instant → ZonedDateTimeinstant.atZone(zona)momen dibaca sebagai jam lokal
LocalDateTime → ZonedDateTimeldt.atZone(zona)waktu dinding diberi makna zona

Kita telah memakai ZoneId.of("Asia/Jakarta") berkali-kali tanpa membedah objeknya. Zona apa saja yang tersedia, apa bedanya Asia/Jakarta dengan +07:00, dan bagaimana Java mengetahui offset sebuah zona? Bab berikutnya membuka isi dunia ZoneId dan ZoneOffset.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

KonversiZona.java

Unduh berkas
java
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class KonversiZona {
    public static void main(String[] args) {
        ZonedDateTime diJakarta =
                ZonedDateTime.of(2026, 1, 15, 8, 0, 0, 0, ZoneId.of("Asia/Jakarta"));
        System.out.println("di Jakarta   : " + diJakarta);

        // withZoneSameInstant: momen yang sama, dibaca dari zona lain.
        ZonedDateTime diTokyo = diJakarta.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));
        System.out.println("di Tokyo     : " + diTokyo);
        System.out.println("jam di Tokyo : " + diTokyo.getHour());

        // Keduanya menunjuk momen yang sama di garis waktu.
        System.out.println("Instant sama? " + diJakarta.toInstant().equals(diTokyo.toInstant()));

        // withZoneSameLocal: waktu dinding disalin, momennya menjadi berbeda.
        ZonedDateTime jamSama = diJakarta.withZoneSameLocal(ZoneId.of("Asia/Tokyo"));
        System.out.println("jam sama     : " + jamSama);
        System.out.println("Instant sama? " + diJakarta.toInstant().equals(jamSama.toInstant()));

        // Penjumlahan pada ZonedDateTime mengikuti waktu dinding setempat.
        System.out.println("plus 2 jam   : " + diTokyo.plusHours(2));
    }
}

MembuatZonedDateTime.java

Unduh berkas
java
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class MembuatZonedDateTime {
    public static void main(String[] args) {
        ZoneId jakarta = ZoneId.of("Asia/Jakarta");

        // Jalur 1: isi komponen lengkap beserta ZoneId.
        ZonedDateTime rapat = ZonedDateTime.of(2026, 1, 15, 8, 0, 0, 0, jakarta);

        // Jalur 2: dari LocalDateTime yang sudah ada + ZoneId.
        ZonedDateTime acara = ZonedDateTime.of(LocalDateTime.of(2026, 1, 15, 20, 0), jakarta);

        // Jalur 3: parse teks lengkap berisi waktu + offset + zona.
        ZonedDateTime dariTeks = ZonedDateTime.parse("2026-01-15T08:00+07:00[Asia/Jakarta]");

        System.out.println("rapat    : " + rapat);
        System.out.println("acara    : " + acara);
        System.out.println("dariTeks : " + dariTeks);

        System.out.println("getZone()        : " + rapat.getZone());
        System.out.println("getOffset()      : " + rapat.getOffset());
        System.out.println("getDayOfWeek()   : " + rapat.getDayOfWeek());
        System.out.println("toInstant()      : " + rapat.toInstant());
        System.out.println("toLocalDateTime(): " + rapat.toLocalDateTime());
    }
}