Tampilan tanggal ke pengguna jarang memakai format ISO 2026-01-15. Pengguna Indonesia lebih akrab dengan 15 Januari 2026, sedangkan pengguna Amerika membaca January 15, 2026. Untuk kasus ini, java.time menyediakan DateTimeFormatter dengan tiga sumber pola: predefined formatters ISO, localized formatters berbasis FormatStyle, dan custom pattern via ofPattern. Bab ini membedah ketiganya, termasuk cara mengaitkan Locale agar nama hari dan bulan tampil dalam bahasa yang benar.
Tiga Sumber Pola Formatter #
| Sumber Pola | Kapan Dipakai | Contoh |
|---|---|---|
Predefined ISO (ISO_LOCAL_DATE, dll.) | Pertukaran antar sistem, log | 2026-01-15 |
FormatStyle + Locale | Tampilan pengguna dengan bahasa lokal | 15 Januari 2026 |
ofPattern("dd MMM yyyy") | Kontrol presisi karakter per komponen | 15 Jan 2026 |
Pilihan strategi bergantung pada kebutuhan: ISO untuk pertukaran data, FormatStyle untuk UI standar per regional, dan custom pattern untuk tampilan terstruktur khusus (mis. faktur, laporan PDF).
Contoh Lengkap: Predefined, Style, dan Custom Pattern #
Kode berikut mendemonstrasikan ketiga sumber pola secara berurutan, termasuk format locale-aware untuk Bahasa Indonesia, English (US), dan Jepang:
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.util.Locale
fun main() {
val tanggal: LocalDate = LocalDate.of(2026, 1, 15)
val jadwal: LocalDateTime = LocalDateTime.of(2026, 1, 15, 8, 30)
val instan = ZonedDateTime.of(jadwal, ZoneId.of("Asia/Jakarta"))
// 1. Predefined formatters (ISO)
println("=== Predefined ISO ===")
println("ISO_DATE : ${DateTimeFormatter.ISO_DATE.format(tanggal)}")
println("ISO_LOCAL_DATE : ${DateTimeFormatter.ISO_LOCAL_DATE.format(tanggal)}")
println("ISO_DATE_TIME : ${DateTimeFormatter.ISO_DATE_TIME.format(instan)}")
println("ISO_INSTANT : ${DateTimeFormatter.ISO_INSTANT.format(instan.toInstant())}")
// 2. Predefined formatters (FormatStyle + Locale)
println()
println("=== FormatStyle ===")
val fmtTanggalID = DateTimeFormatter
.ofLocalizedDate(FormatStyle.LONG)
.withLocale(Locale.of("id", "ID"))
val fmtTanggalUS = DateTimeFormatter
.ofLocalizedDate(FormatStyle.LONG)
.withLocale(Locale.US)
println("LONG (id-ID) : ${fmtTanggalID.format(tanggal)}")
println("LONG (en-US) : ${fmtTanggalUS.format(tanggal)}")
val fmtMediumID = DateTimeFormatter
.ofLocalizedDate(FormatStyle.MEDIUM)
.withLocale(Locale.of("id", "ID"))
println("MEDIUM (id-ID) : ${fmtMediumID.format(tanggal)}")
// 3. Custom pattern via ofPattern
println()
println("=== Custom pattern ===")
val fmt1 = DateTimeFormatter.ofPattern("dd MMM yyyy")
val fmt2 = DateTimeFormatter.ofPattern("EEEE, dd MMMM yyyy", Locale.of("id", "ID"))
val fmt3 = DateTimeFormatter.ofPattern("HH:mm:ss | dd-MM-yyyy")
val fmt4 = DateTimeFormatter.ofPattern("hh:mm a, MMM d yyyy", Locale.US)
println("dd MMM yyyy : ${fmt1.format(tanggal)}")
println("EEEE (id-ID) : ${fmt2.format(tanggal)}")
println("HH:mm:ss | ... : ${fmt3.format(jadwal)}")
println("hh:mm a (US) : ${fmt4.format(jadwal)}")
// 4. Format dengan ZoneId (RFC-1123)
val fmtRfc1123 = DateTimeFormatter.RFC_1123_DATE_TIME
println()
println("=== RFC-1123 (HTTP headers) ===")
println("RFC_1123 : ${fmtRfc1123.format(instan)}")
// 5. Locale-aware bulan dan hari
println()
println("=== Locale-aware month/day ===")
val zonaJakarta = ZoneId.of("Asia/Jakarta")
val hariJakarta = jadwal.atZone(zonaJakarta)
val fmtFullID = DateTimeFormatter
.ofPattern("EEEE, dd MMMM yyyy 'pukul' HH:mm")
.withLocale(Locale.of("id", "ID"))
println("Full (id-ID) : ${fmtFullID.format(hariJakarta)}")
val fmtFullJP = DateTimeFormatter
.ofPattern("EEEE, dd MMMM yyyy 'at' HH:mm")
.withLocale(Locale.JAPAN)
println("Full (ja-JP) : ${fmtFullJP.format(hariJakarta)}")
// 6. Format Instant langsung (perlu zona)
val instanUtc = hariJakarta.toInstant()
val fmtInstant = DateTimeFormatter
.ofPattern("yyyy-MM-dd HH:mm:ss z")
.withZone(ZoneId.of("UTC"))
println()
println("=== Format Instant (UTC) ===")
println("format UTC : ${fmtInstant.format(instanUtc)}")
}Output yang Dihasilkan:
=== Predefined ISO ===
ISO_DATE : 2026-01-15
ISO_LOCAL_DATE : 2026-01-15
ISO_DATE_TIME : 2026-01-15T08:30:00+07:00[Asia/Jakarta]
ISO_INSTANT : 2026-01-15T01:30:00Z
=== FormatStyle ===
LONG (id-ID) : 15 Januari 2026
LONG (en-US) : January 15, 2026
MEDIUM (id-ID) : 15 Jan 2026
=== Custom pattern ===
dd MMM yyyy : 15 Jan 2026
EEEE (id-ID) : Kamis, 15 Januari 2026
HH:mm:ss | ... : 08:30:00 | 15-01-2026
hh:mm a (US) : 08:30 AM, Jan 15 2026
=== RFC-1123 (HTTP headers) ===
RFC_1123 : Thu, 15 Jan 2026 08:30:00 +0700
=== Locale-aware month/day ===
Full (id-ID) : Kamis, 15 Januari 2026 pukul 08:30
Full (ja-JP) : 木曜日, 15 1月 2026 at 08:30
=== Format Instant (UTC) ===
format UTC : 2026-01-15 01:30:00 UTCMari kita telaah baris-baris kode di atas:
- Baris 17-20 (predefined ISO):
ISO_DATEdanISO_LOCAL_DATEmenghasilkan format yang sama untukLocalDate—2026-01-15. Perbedaannya muncul saat memformatZonedDateTime:ISO_DATEmenambahkan offset (+07:00),ISO_LOCAL_DATEtidak.ISO_INSTANTselalu diakhiriZuntuk UTC. - Baris 26-31 (FormatStyle.LONG): dengan
Locale.of("id","ID"), hasilnya15 Januari 2026. DenganLocale.US, hasilnyaJanuary 15, 2026. Locale mengatur urutan komponen dan nama bulan;FormatStylemengatur panjang (SHORT, MEDIUM, LONG, FULL). - Baris 40-44 (custom pattern):
ofPattern("dd MMM yyyy")memberi kontrol penuh.dd= hari 2 digit,MMM= singkatan bulan (Jan),MMMM= nama bulan penuh (Januari),EEEE= nama hari penuh (Kamis).HH= jam 24 jam,hh= jam 12 jam,a= AM/PM. - Baris 53 (
RFC_1123_DATE_TIME): format standar HTTP headerDate:. HasilnyaThu, 15 Jan 2026 08:30:00 +0700. Cocok untuk response HTTP dan email RFC 2822. - Baris 61-66 (literal string dalam pattern): tanda kutip tunggal
'pukul'menandai literal yang akan dicetak apa adanya. Tanpa tanda kutip,pukulakan dianggap sebagai pola huruf —pmisalnya tidak dikenali dan akan melempar exception. - Baris 75-78 (format
InstantdenganwithZone): untuk memformatInstantlangsung, formatter harus tahu zona target lewatwithZone(...). Tanpa ini,format(Instant)melemparUnsupportedTemporalTypeExceptionkarenaInstanttidak memiliki komponen tanggal lokal.
Pattern Symbol yang Sering Dipakai #
Tabel berikut merangkum simbol pattern yang paling sering muncul di aplikasi nyata:
| Simbol | Arti | Contoh |
|---|---|---|
yyyy | Tahun 4 digit | 2026 |
MM | Bulan 2 digit | 01 |
MMM | Bulan singkatan | Jan |
MMMM | Bulan penuh | Januari |
dd | Hari 2 digit | 15 |
EEEE | Nama hari penuh | Kamis |
HH | Jam 24 jam | 08 |
hh | Jam 12 jam | 08 |
mm | Menit | 30 |
ss | Detik | 00 |
a | AM/PM marker | AM |
z | Nama zona | WIB |
Z | Offset signed | +0700 |
Referensi Lanjutan #
- DateTimeFormatter
— dokumentasi
DateTimeFormatterJava SE 21 untuk pola format tanggal dan jam - java.time package
— ringkasan paket
java.timeuntuk tipe yang diformat
Selanjutnya #
Formatting adalah satu arah: objek tanggal → string. Operasi kebalikannya adalah parsing string → objek tanggal. Bab Parsing Date-Time akan membahas LocalDate.parse(...) dengan formatter kustom dan cara menangani DateTimeParseException dengan baik.