Sepanjang Section ini kita telah berkenalan dengan delapan tipe inti java.time, masing-masing dengan wilayah kerja yang jelas. Kesalahan date-time di aplikasi nyata jarang berasal dari method yang salah dipanggil; hampir semuanya berakar pada tipe yang salah dipilih sejak awal — tanggal lahir disimpan sebagai Instant hingga kehilangan makna lokalnya, atau stempel log disimpan sebagai LocalDateTime hingga tidak bisa dibandingkan antar server. Bab penutup ini merangkum seluruh materi menjadi pegangan praktis: tabel pemilihan tipe, pola simpan-tampilkan, teknik penguji, dan daftar kebiasaan yang layak dijadikan standar tim.

Memilih Tipe Sesuai Kebutuhan #

Pertanyaan pembuka setiap kali menyimpan atau menerima nilai date-time: informasi apa yang sebenarnya dimiliki data kita, dan siapa yang akan membacanya? Tanggal lahir tidak punya jam; stempel log tidak punya “waktu dinding”; jadwal rapat punya keduanya plus zona. Tabel berikut memetakan kebutuhan umum ke tipe yang tepat:

KebutuhanTipe yang tepatAlasan
Tanggal lahir, tanggal faktur, hari liburLocalDatetanpa jam dan zona; tidak ada tafsir “pukul berapa”
Jam buka toko, jadwal harian berulangLocalTimeberulang setiap hari; tidak terikat satu tanggal
Jadwal tertulis pada kalender dindingLocalDateTimetanggal dan jam tanpa makna zona
Stempel log, kedaluwarsa token, urutan kejadianInstantmomen absolut UTC; siap dibandingkan lintas server
Tampilan jadwal bagi pengguna, konversi antar zonaZonedDateTimemembawa aturan zona termasuk jadwal DST

Empat baris pertama tabel tersebut bekerja dalam satu program kecil:

java MemilihTipe.java
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class MemilihTipe {
    public static void main(String[] args) {
        // Kebutuhan 1: tanggal lahir — tanggal saja, tanpa jam dan tanpa zona.
        LocalDate tanggalLahir = LocalDate.of(2004, 6, 10);
        System.out.println("tanggal lahir : " + tanggalLahir + " (LocalDate)");

        // Kebutuhan 2: jam buka toko — waktu yang berulang setiap hari.
        LocalTime jamBuka = LocalTime.of(9, 0);
        System.out.println("jam buka toko : " + jamBuka + " (LocalTime)");

        // Kebutuhan 3: stempel log — momen absolut untuk dibaca mesin.
        Instant stempelLog = Instant.parse("2026-01-15T01:23:45Z");
        System.out.println("stempel log   : " + stempelLog + " (Instant)");

        // Kebutuhan 4: jadwal rapat — momen berzona untuk ditampilkan ke pengguna.
        ZonedDateTime rapat =
                ZonedDateTime.of(2026, 1, 15, 8, 0, 0, 0, ZoneId.of("Asia/Jakarta"));
        System.out.println("jadwal rapat  : " + rapat + " (ZonedDateTime)");
    }
}

Output yang Dihasilkan:

text
tanggal lahir : 2004-06-10 (LocalDate)
jam buka toko : 09:00 (LocalTime)
stempel log   : 2026-01-15T01:23:45Z (Instant)
jadwal rapat  : 2026-01-15T08:00+07:00[Asia/Jakarta] (ZonedDateTime)

Keempat nilai dicetak dalam format masing-masing — bukti bahwa tipe yang tepat juga menghasilkan representasi default yang tepat: 2004-06-10 untuk tanggal murni, 2026-01-15T01:23:45Z untuk momen UTC, dan 2026-01-15T08:00+07:00[Asia/Jakarta] untuk momen berzona.

Simpan UTC, Tampilkan Zona Setempat #

Aturan tunggal yang menyelesaikan hampir semua masalah zona waktu: simpan sebagai Instant (atau representasi UTC seperti teks ISO-8601 berakhiran Z) di basis data dan antar layanan, lalu konversi ke zona pengguna hanya di lapisan tampilan. Nilai UTC bebas tafsir, mudah diurutkan, dan aman dibandingkan antar server yang berzona berbeda; identitas zona pengguna cukup menyusul saat merender halaman:

import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

public class PolaSimpanTampil {
    public static void main(String[] args) {
        // Pola "simpan UTC": nilai mentah yang keluar masuk basis data / API.
        Instant tersimpan = Instant.parse("2026-01-15T01:23:45Z");
        System.out.println("tersimpan (UTC)       : " + tersimpan);

        // Pola "tampilkan zona": konversi hanya di lapisan tampilan.
        ZonedDateTime tampil = tersimpan.atZone(ZoneId.of("Asia/Jakarta"));
        DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd MMMM yyyy, HH:mm 'WIB'")
                .withLocale(Locale.forLanguageTag("id"));
        System.out.println("tampil untuk pengguna : " + tampil.format(fmt));

        // Teknik pengujian: Clock.fixed membuat now() menjadi deterministik.
        Clock jamBeku = Clock.fixed(
                Instant.parse("2026-01-15T01:00:00Z"), ZoneId.of("Asia/Jakarta"));
        System.out.println("LocalDate.now(clock)  : " + LocalDate.now(jamBeku));
        System.out.println("Instant.now(clock)    : " + Instant.now(jamBeku));
        System.out.println("ZonedDateTime.now(clock): " + ZonedDateTime.now(jamBeku));

        // Kedaluwarsa token: durasi fisik dihitung dari Instant.
        Instant kedaluwarsa = tersimpan.plus(Duration.ofMinutes(30));
        System.out.println("token kedaluwarsa     : " + kedaluwarsa);
    }
}

Output yang Dihasilkan:

text
tersimpan (UTC)       : 2026-01-15T01:23:45Z
tampil untuk pengguna : 15 Januari 2026, 08:23 WIB
LocalDate.now(clock)  : 2026-01-15
Instant.now(clock)    : 2026-01-15T01:00:00Z
ZonedDateTime.now(clock): 2026-01-15T08:00+07:00[Asia/Jakarta]
token kedaluwarsa     : 2026-01-15T01:53:45Z

Baris 14-16 menyimpan nilai mentah Instant sebagaimana adanya. Baris 19-23 mengonversi ke zona Jakarta dan memformatnya dengan Locale Indonesia menjadi 15 Januari 2026, 08:23 WIB — transformasi murni lapisan tampilan; nilai aslinya tidak pernah berubah. Baris 26-31 memperkenalkan teknik yang mengubah kode bergantung-waktu menjadi dapat diuji: alih-alih memanggil LocalDate.now() langsung, terima objek Clock sebagai parameter. Produksi memakai Clock.system(...), sedangkan pengujian memakai Clock.fixed(...) sehingga “sekarang” membeku di 2026-01-15 dan hasil tes selalu sama. Baris 34-35 menghitung kedaluwarsa token dengan Duration — waktu fisik, tipe yang memang tepat untuk pekerjaan itu.

Kebiasaan yang Layak Dijadikan Standar Tim #

  • Buat formatter sebagai konstanta. DateTimeFormatter immutable dan thread-safe; deklarasikan sekali sebagai static final, jangan rakit ulang di setiap pemanggilan.
  • Tetapkan Locale eksplisit untuk semua format dan parsing yang menghadap manusia, dan pakai ResolverStyle.STRICT saat membaca masukan dari luar sistem.
  • Pisahkan durasi dan kalender. Timeout dan kedaluwarsa memakai Duration; tenor, usia, dan jadwal berulang bulanan memakai Period — jangan pernah menyamakan Duration.ofDays(30) dengan “satu bulan”.
  • Pilih zona berbasis wilayah (Asia/Jakarta) untuk tampilan pengguna, bukan offset tetap (+07:00), agar aturan DST dan perubahan kebijakan terikutkan otomatis.
  • Tinggalkan API lama untuk kode baru. Date dan Calendar hanya diketahui ulang lewat konversi toInstant()/toZonedDateTime() saat bertemu pustaka lawas.

Dengan ini perjalanan kita pada Date-Time API tuntas: dari warisan Date yang mutable, era baru java.time yang immutable, hingga pemilihan tipe dan pola simpan-tampilkan yang membuat aplikasi tahan terhadap jebakan zona. Keterampilan berikutnya di Course ini berpindah dari waktu ke berkas: Section File I/O dan NIO membahas cara Java membaca dan menulis data ke sistem berkas dengan API modern java.nio.file.

Referensi #

Berkas Sumber

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

MemilihTipe.java

Unduh berkas
java
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class MemilihTipe {
    public static void main(String[] args) {
        // Kebutuhan 1: tanggal lahir — tanggal saja, tanpa jam dan tanpa zona.
        LocalDate tanggalLahir = LocalDate.of(2004, 6, 10);
        System.out.println("tanggal lahir : " + tanggalLahir + " (LocalDate)");

        // Kebutuhan 2: jam buka toko — waktu yang berulang setiap hari.
        LocalTime jamBuka = LocalTime.of(9, 0);
        System.out.println("jam buka toko : " + jamBuka + " (LocalTime)");

        // Kebutuhan 3: stempel log — momen absolut untuk dibaca mesin.
        Instant stempelLog = Instant.parse("2026-01-15T01:23:45Z");
        System.out.println("stempel log   : " + stempelLog + " (Instant)");

        // Kebutuhan 4: jadwal rapat — momen berzona untuk ditampilkan ke pengguna.
        ZonedDateTime rapat =
                ZonedDateTime.of(2026, 1, 15, 8, 0, 0, 0, ZoneId.of("Asia/Jakarta"));
        System.out.println("jadwal rapat  : " + rapat + " (ZonedDateTime)");
    }
}

PolaSimpanTampil.java

Unduh berkas
java
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

public class PolaSimpanTampil {
    public static void main(String[] args) {
        // Pola "simpan UTC": nilai mentah yang keluar masuk basis data / API.
        Instant tersimpan = Instant.parse("2026-01-15T01:23:45Z");
        System.out.println("tersimpan (UTC)       : " + tersimpan);

        // Pola "tampilkan zona": konversi hanya di lapisan tampilan.
        ZonedDateTime tampil = tersimpan.atZone(ZoneId.of("Asia/Jakarta"));
        DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd MMMM yyyy, HH:mm 'WIB'")
                .withLocale(Locale.forLanguageTag("id"));
        System.out.println("tampil untuk pengguna : " + tampil.format(fmt));

        // Teknik pengujian: Clock.fixed membuat now() menjadi deterministik.
        Clock jamBeku = Clock.fixed(
                Instant.parse("2026-01-15T01:00:00Z"), ZoneId.of("Asia/Jakarta"));
        System.out.println("LocalDate.now(clock)  : " + LocalDate.now(jamBeku));
        System.out.println("Instant.now(clock)    : " + Instant.now(jamBeku));
        System.out.println("ZonedDateTime.now(clock): " + ZonedDateTime.now(jamBeku));

        // Kedaluwarsa token: durasi fisik dihitung dari Instant.
        Instant kedaluwarsa = tersimpan.plus(Duration.ofMinutes(30));
        System.out.println("token kedaluwarsa     : " + kedaluwarsa);
    }
}