Sepanjang beberapa bab terakhir kita menulis ZoneId.of("Asia/Jakarta") sebagai mantra pembuka zona waktu, tetapi belum membedah objek di baliknya. Kenyataannya ada dua keluarga pengenal zona di java.time: zona berbasis wilayah seperti Asia/Jakarta yang membawa aturan penuh, dan offset tetap seperti +07:00 yang sekadar angka pergeseran dari UTC. Keduanya sama-sama sah di mata compiler, tetapi perilakunya berbeda pada kasus-kasus penting — terutama saat aturan daylight saving time mulai bermain. Pada Chapter ini kita bedah kedua keluarga itu, menjelajahi daftar zona yang tersedia di JDK, dan melihat bentuk-bentuk penulisan yang mungkin kita temui di kode nyata.

Zona Berbasis Wilayah vs Offset Tetap #

Class ZoneOffset adalah turunan dari ZoneId — artinya offset tetap dapat dipakai di semua tempat yang menerima ZoneId. Perbedaannya ada pada isi ZoneRules yang dibawa: zona wilayah seperti America/New_York menyimpan sejarah dan jadwal perubahan offset (termasuk jadwal pergantian daylight saving), sedangkan ZoneOffset tidak punya aturan apa pun dan selalu menampilkan angka yang sama untuk semua momen:

import java.time.Instant;
import java.time.ZoneId;
import java.time.ZoneOffset;

public class ZoneIdVsZoneOffset {
    public static void main(String[] args) {
        // Zona berbasis wilayah: membawa aturan lengkap dari basis data zona IANA.
        ZoneId wilayah = ZoneId.of("America/New_York");

        // Offset tetap: sekadar angka pergeseran dari UTC, tanpa aturan apa pun.
        ZoneOffset tetap = ZoneOffset.of("-05:00");

        Instant sebelumDST = Instant.parse("2026-03-07T12:00:00Z");
        Instant setelahDST = Instant.parse("2026-03-08T12:00:00Z");

        System.out.println("wilayah.getId() : " + wilayah.getId());
        System.out.println("offset pada 7 Maret 2026  : " + wilayah.getRules().getOffset(sebelumDST));
        System.out.println("offset pada 8 Maret 2026  : " + wilayah.getRules().getOffset(setelahDST));

        System.out.println("tetap.getId()   : " + tetap.getId());
        System.out.println("offset pada 8 Maret 2026  : " + tetap.getRules().getOffset(setelahDST));

        // Zona wilayah dapat berubah offset; offset tetap tidak pernah berubah.
        System.out.println("wilayah fixed?  : " + wilayah.getRules().isFixedOffset());
        System.out.println("tetap fixed?    : " + tetap.getRules().isFixedOffset());
    }
}

Output yang Dihasilkan:

text
wilayah.getId() : America/New_York
offset pada 7 Maret 2026  : -05:00
offset pada 8 Maret 2026  : -04:00
tetap.getId()   : -05:00
offset pada 8 Maret 2026  : -05:00
wilayah fixed?  : false
tetap fixed?    : true

Mari kita telaah baris-baris kode di atas:

  1. Baris 8 dan 11: ZoneId.of("America/New_York") membuat zona wilayah, sedangkan ZoneOffset.of("-05:00") membuat offset tetap — keduanya bertipe kompatibel ZoneId.
  2. Baris 13-14: dua momen dipilih di sekitar 8 Maret 2026, tanggal mulainya daylight saving time di New York pada tahun itu.
  3. Baris 17-18: zona wilayah melaporkan offset -05:00 untuk 7 Maret dan -04:00 untuk 8 Maret — aturan pergantian jam bekerja otomatis dari data ZoneRules.
  4. Baris 20-21: offset tetap tetap melaporkan -05:00 untuk keduanya; offset tetap tidak tahu apa pun tentang pergantian jam.
  5. Baris 24-25: isFixedOffset() menegaskan perbedaan strukturalnya — zona wilayah tidak tetap, offset tetap ya.

Menjelajah Daftar Zona #

Seluruh pengenal zona berasal dari basis data zona IANA (IANA Time Zone Database, disebut juga tz database atau tzdb) yang dibundel JDK dan diperbarui mengikuti pembaruan JDK. Method ZoneId.getAvailableZoneIds() mengembalikan Set<String> berisi seluruh pengenal; jumlahnya pada JDK 25 adalah ratusan. Mencetak seluruh isi set jelas tidak praktis untuk dibaca, sehingga contoh berikut membatasi diri pada jumlah, keanggotaan, dan cuplikan terurut:

import java.time.ZoneId;
import java.time.format.TextStyle;
import java.util.List;
import java.util.Locale;
import java.util.TreeSet;

public class MenjelajahZona {
    public static void main(String[] args) {
        List<String> semuaZona = List.copyOf(ZoneId.getAvailableZoneIds());
        System.out.println("jumlah zona tersedia : " + semuaZona.size());
        System.out.println("berisi Asia/Jakarta? : " + semuaZona.contains("Asia/Jakarta"));

        // Cetak hanya sebagian; urutkan agar keluarannya stabil dan mudah dibaca.
        TreeSet<String> terurut = new TreeSet<>(semuaZona);
        System.out.println("zona Asia/J*         : " + terurut.subSet("Asia/Ja", "Asia/Jk"));
        System.out.println("lima zona pertama    : " + terurut.stream().limit(5).toList());

        // Nama zona yang ramah untuk ditampilkan, per Locale.
        ZoneId jakarta = ZoneId.of("Asia/Jakarta");
        System.out.println("nama zona (id)       : " + jakarta.getDisplayName(TextStyle.FULL, Locale.forLanguageTag("id")));
        System.out.println("nama zona (en)       : " + jakarta.getDisplayName(TextStyle.FULL, Locale.US));
    }
}

Output yang Dihasilkan:

text
jumlah zona tersedia : 604
berisi Asia/Jakarta? : true
zona Asia/J*         : [Asia/Jakarta, Asia/Jayapura, Asia/Jerusalem]
lima zona pertama    : [Africa/Abidjan, Africa/Accra, Africa/Addis_Ababa, Africa/Algiers, Africa/Asmara]
nama zona (id)       : Waktu Jakarta
nama zona (en)       : Jakarta Time

Baris 12 menghitung jumlah pengenal — 604 pada JDK 25, angka yang dapat berbeda sedikit di versi JDK lain karena tzdb terus diperbarui. Baris 18-19 memakai TreeSet agar iterasi terurut alfabetis: cuplikan subSet("Asia/Ja", "Asia/Jk") memotong rentang Asia/Jakarta sampai Asia/Jerusalem, dan limit(5) mengambil lima pengenal pertama. Baris 23-24 memakai getDisplayName() untuk nama zona yang siap ditampilkan — Waktu Jakarta dalam Locale Indonesia dan Jakarta Time dalam Locale Inggris AS.

Bentuk Penulisan Zona dan Offset #

Selain pola Wilayah/Kota, ada beberapa bentuk penulisan lain yang perlu kita kenali agar tidak salah mengartikan kode yang kita temui. ZoneOffset.of() menerima teks seperti "+07:00" atau "-05:00"; ZoneOffset.ofHours(7) membuat offset dari jumlah jam; dan ZoneOffset.UTC adalah konstanta offset nol yang dicetak sebagai Z. Method ZoneId.of() sendiri juga menerima bentuk offset serta pengenal tetap seperti UTC, GMT, dan Etc/UTC:

import java.time.ZoneId;
import java.time.ZoneOffset;

public class BentukPenulisan {
    public static void main(String[] args) {
        // Offset dari teks, dari jumlah jam, dan konstanta UTC.
        System.out.println("ZoneOffset.of(\"+07:00\") : " + ZoneOffset.of("+07:00"));
        System.out.println("ZoneOffset.ofHours(7)   : " + ZoneOffset.ofHours(7));
        System.out.println("ZoneOffset.UTC          : " + ZoneOffset.UTC);

        // ZoneId.of juga menerima bentuk offset.
        ZoneId dariOffset = ZoneId.of("+07:00");
        System.out.println("ZoneId.of(\"+07:00\")     : " + dariOffset
                + ", normalized: " + dariOffset.normalized());

        // Pengenal zona tetap yang dikenal basis data zona.
        System.out.println("ZoneId.of(\"UTC\")        : " + ZoneId.of("UTC")
                + ", normalized: " + ZoneId.of("UTC").normalized());
        System.out.println("ZoneId.of(\"Etc/UTC\")    : " + ZoneId.of("Etc/UTC"));
    }
}

Output yang Dihasilkan:

text
ZoneOffset.of("+07:00") : +07:00
ZoneOffset.ofHours(7)   : +07:00
ZoneOffset.UTC          : Z
ZoneId.of("+07:00")     : +07:00, normalized: +07:00
ZoneId.of("UTC")        : UTC, normalized: Z
ZoneId.of("Etc/UTC")    : Etc/UTC

Ringkasan bentuk-bentuk tersebut dapat dirangkum dalam tabel berikut:

BentukContohSifat
Zona wilayahAsia/Jakartaaturan penuh, offset dapat berubah
Offset tetap+07:00, -05:00angka tetap, tanpa aturan
Konstanta UTCZoneOffset.UTCoffset nol, dicetak Z
Pengenal tetapUTC, GMT, Etc/UTCZoneId tanpa perubahan offset

Method normalized() menarik dicermati: ZoneId.of("UTC") mencetak UTC, tetapi versi ternormalisasinya adalah Z — bentuk paling ringkas yang setara. Normalisasi semacam ini berguna saat membandingkan dua ZoneId yang secara semantik sama namun ditulis berbeda.

Dengan pengenal zona dikuasai, kita kini siap mengukur rentang. Berapa lama sebuah proses berjalan, dan berapa jauh dua tanggal terpisah? Dua pertanyaan itu membutuhkan dua alat ukur berbeda — Duration untuk waktu fisik dan Period untuk rentang kalender — yang menjadi fokus bab berikutnya.

Referensi #

Berkas Sumber

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

BentukPenulisan.java

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

public class BentukPenulisan {
    public static void main(String[] args) {
        // Offset dari teks, dari jumlah jam, dan konstanta UTC.
        System.out.println("ZoneOffset.of(\"+07:00\") : " + ZoneOffset.of("+07:00"));
        System.out.println("ZoneOffset.ofHours(7)   : " + ZoneOffset.ofHours(7));
        System.out.println("ZoneOffset.UTC          : " + ZoneOffset.UTC);

        // ZoneId.of juga menerima bentuk offset.
        ZoneId dariOffset = ZoneId.of("+07:00");
        System.out.println("ZoneId.of(\"+07:00\")     : " + dariOffset
                + ", normalized: " + dariOffset.normalized());

        // Pengenal zona tetap yang dikenal basis data zona.
        System.out.println("ZoneId.of(\"UTC\")        : " + ZoneId.of("UTC")
                + ", normalized: " + ZoneId.of("UTC").normalized());
        System.out.println("ZoneId.of(\"Etc/UTC\")    : " + ZoneId.of("Etc/UTC"));
    }
}

MenjelajahZona.java

Unduh berkas
java
import java.time.ZoneId;
import java.time.format.TextStyle;
import java.util.List;
import java.util.Locale;
import java.util.TreeSet;

public class MenjelajahZona {
    public static void main(String[] args) {
        List<String> semuaZona = List.copyOf(ZoneId.getAvailableZoneIds());
        System.out.println("jumlah zona tersedia : " + semuaZona.size());
        System.out.println("berisi Asia/Jakarta? : " + semuaZona.contains("Asia/Jakarta"));

        // Cetak hanya sebagian; urutkan agar keluarannya stabil dan mudah dibaca.
        TreeSet<String> terurut = new TreeSet<>(semuaZona);
        System.out.println("zona Asia/J*         : " + terurut.subSet("Asia/Ja", "Asia/Jk"));
        System.out.println("lima zona pertama    : " + terurut.stream().limit(5).toList());

        // Nama zona yang ramah untuk ditampilkan, per Locale.
        ZoneId jakarta = ZoneId.of("Asia/Jakarta");
        System.out.println("nama zona (id)       : " + jakarta.getDisplayName(TextStyle.FULL, Locale.forLanguageTag("id")));
        System.out.println("nama zona (en)       : " + jakarta.getDisplayName(TextStyle.FULL, Locale.US));
    }
}

ZoneIdVsZoneOffset.java

Unduh berkas
java
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZoneOffset;

public class ZoneIdVsZoneOffset {
    public static void main(String[] args) {
        // Zona berbasis wilayah: membawa aturan lengkap dari basis data zona IANA.
        ZoneId wilayah = ZoneId.of("America/New_York");

        // Offset tetap: sekadar angka pergeseran dari UTC, tanpa aturan apa pun.
        ZoneOffset tetap = ZoneOffset.of("-05:00");

        Instant sebelumDST = Instant.parse("2026-03-07T12:00:00Z");
        Instant setelahDST = Instant.parse("2026-03-08T12:00:00Z");

        System.out.println("wilayah.getId() : " + wilayah.getId());
        System.out.println("offset pada 7 Maret 2026  : " + wilayah.getRules().getOffset(sebelumDST));
        System.out.println("offset pada 8 Maret 2026  : " + wilayah.getRules().getOffset(setelahDST));

        System.out.println("tetap.getId()   : " + tetap.getId());
        System.out.println("offset pada 8 Maret 2026  : " + tetap.getRules().getOffset(setelahDST));

        // Zona wilayah dapat berubah offset; offset tetap tidak pernah berubah.
        System.out.println("wilayah fixed?  : " + wilayah.getRules().isFixedOffset());
        System.out.println("tetap fixed?    : " + tetap.getRules().isFixedOffset());
    }
}