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:
| Kebutuhan | Tipe yang tepat | Alasan |
|---|---|---|
| Tanggal lahir, tanggal faktur, hari libur | LocalDate | tanpa jam dan zona; tidak ada tafsir “pukul berapa” |
| Jam buka toko, jadwal harian berulang | LocalTime | berulang setiap hari; tidak terikat satu tanggal |
| Jadwal tertulis pada kalender dinding | LocalDateTime | tanggal dan jam tanpa makna zona |
| Stempel log, kedaluwarsa token, urutan kejadian | Instant | momen absolut UTC; siap dibandingkan lintas server |
| Tampilan jadwal bagi pengguna, konversi antar zona | ZonedDateTime | membawa aturan zona termasuk jadwal DST |
Empat baris pertama tabel tersebut bekerja dalam satu program kecil:
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:
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:
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:45ZBaris 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.
DateTimeFormatterimmutable dan thread-safe; deklarasikan sekali sebagaistatic final, jangan rakit ulang di setiap pemanggilan. - Tetapkan
Localeeksplisit untuk semua format dan parsing yang menghadap manusia, dan pakaiResolverStyle.STRICTsaat membaca masukan dari luar sistem. - Pisahkan durasi dan kalender. Timeout dan kedaluwarsa memakai
Duration; tenor, usia, dan jadwal berulang bulanan memakaiPeriod— jangan pernah menyamakanDuration.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.
DatedanCalendarhanya diketahui ulang lewat konversitoInstant()/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 #
- Oracle: Package java.time (Java SE 25 API)
- Oracle: Class Clock (Java SE 25 API)
- dev.java: Date Time
- JCP: JSR 310 — Date and Time API
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
MemilihTipe.java
Unduh berkasimport 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 berkasimport 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);
}
}