Representasi bawaan toString() seperti 2026-01-15T08:00 sangat baik untuk mesin, tetapi tidak untuk manusia: pengguna Indonesia berharap membaca “15 Januari 2026” atau “Kamis, 15 Januari 2026”, sementara pengguna Amerika Serikat akrab dengan “January 15, 2026”. Mengubah nilai date-time menjadi teks adalah pekerjaan class DateTimeFormatter — pengganti SimpleDateFormat yang immutable, thread-safe, dan sadar Locale. Pada Chapter ini kita pelajari dua gaya pemformatan: pola kustom yang kita rangkaikan sendiri huruf demi huruf, dan format bawaan yang tinggal dipilih level kerinciannya.

Pola Kustom dengan ofPattern #

Method DateTimeFormatter.ofPattern("…") membuat formatter dari pola berisi huruf-huruf bermakna khusus. Jumlah pengulangan huruf menentukan bentuk keluarannya: M menjadi 1, MM menjadi 01, MMM menjadi Jan, dan MMMM menjadi Januari. Pola juga dapat memuat teks harfiah apa pun di antara huruf-huruf itu, seperti koma atau tanda miring:

HurufMaknaContoh keluaran (15 Jan 2026)
yyyytahun empat digit2026
MMbulan dua digit01
MMMnama bulan singkatJan
MMMMnama bulan penuhJanuari
ddtanggal dua digit15
dtanggal tanpa nol di depan15
E / EEEEnama hari singkat / penuhKam / Kamis
HHjam 24 jam dua digit08
mmmenit dua digit05
ssdetik dua digit03
znama zona waktuWIB

Huruf M untuk bulan dan m untuk menit memang mirip, begitu pula H untuk jam 24 jam dan h untuk jam 12 jam — kesalahan kapitalisasi adalah salah ketik paling umum saat menulis pola. Mari kita lihat pola-pola itu bekerja:

java FormatPola.java
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

public class FormatPola {
    public static void main(String[] args) {
        LocalDate tanggal = LocalDate.of(2026, 1, 15);
        LocalDateTime rapat = LocalDateTime.of(2026, 1, 15, 8, 5, 3);
        ZonedDateTime berangkat =
                ZonedDateTime.of(2026, 1, 15, 8, 0, 0, 0, ZoneId.of("Asia/Jakarta"));
        Locale id = Locale.forLanguageTag("id");

        DateTimeFormatter standar = DateTimeFormatter.ofPattern("dd/MM/yyyy").withLocale(id);
        DateTimeFormatter panjang = DateTimeFormatter.ofPattern("dd MMMM yyyy").withLocale(id);
        DateTimeFormatter hari = DateTimeFormatter.ofPattern("EEEE, dd MMMM yyyy").withLocale(id);
        DateTimeFormatter singkat = DateTimeFormatter.ofPattern("E, dd MMM yy").withLocale(id);
        DateTimeFormatter jamLengkap = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss");
        DateTimeFormatter tanpaNol = DateTimeFormatter.ofPattern("d MMMM yyyy HH:mm").withLocale(id);
        DateTimeFormatter zona = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm z");

        System.out.println("dd/MM/yyyy         : " + tanggal.format(standar));
        System.out.println("dd MMMM yyyy       : " + tanggal.format(panjang));
        System.out.println("EEEE, dd MMMM yyyy : " + tanggal.format(hari));
        System.out.println("E, dd MMM yy       : " + tanggal.format(singkat));
        System.out.println("HH:mm:ss           : " + rapat.format(jamLengkap));
        System.out.println("d MMMM yyyy HH:mm  : " + rapat.format(tanpaNol));
        System.out.println("dengan zona        : " + berangkat.format(zona));
    }
}

Output yang Dihasilkan:

text
dd/MM/yyyy         : 15/01/2026
dd MMMM yyyy       : 15 Januari 2026
EEEE, dd MMMM yyyy : Kamis, 15 Januari 2026
E, dd MMM yy       : Kam, 15 Jan 26
HH:mm:ss           : 15/01/2026 08:05:03
d MMMM yyyy HH:mm  : 15 Januari 2026 08:05
dengan zona        : 15/01/2026 08:00 WIB

Mari kita telaah baris-baris kode di atas:

  1. Baris 19-25: tujuh formatter dibuat sekali di awal — praktik yang tepat karena DateTimeFormatter immutable dan dapat dipakai ulang, termasuk sebagai konstanta static final.
  2. Baris 19-22: withLocale(Locale.forLanguageTag("id")) memastikan nama hari dan bulan keluar berbahasa Indonesia; tanpa panggilan ini, formatter memakai Locale bawaan JVM.
  3. Baris 23 (E, dd MMM yy): kombinasi bentuk singkat menghasilkan Kam, 15 Jan 26 — padat untuk tampilan tabel atau sumbu grafik.
  4. Baris 25 (d MMMM yyyy HH:mm): huruf tunggal d mencetak tanggal tanpa nol di depan dan HH:mm memisahkan jam-menit dengan teks harfiah titik dua.
  5. Baris 26 (dd/MM/yyyy HH:mm z): huruf z menampilkan nama zona WIB — berguna untuk nilai ZonedDateTime agar pembaca tahu jam mana yang sedang dimaksud.

Locale Menentukan Nama Hari dan Bulan #

Pola yang sama menghasilkan teks berbeda pada Locale berbeda: MMMM menjadi Januari dalam Locale Indonesia dan January dalam Locale Inggris. Konsekuensinya, setiap formatter yang memuat huruf E, M lebih dari dua kali, atau a (penanda AM/PM) selayaknya diberi Locale eksplisit. Data nama-nama ini berasal dari CLDR (Common Locale Data Repository) yang dibundel JDK — sumber yang sama dengan Locale pada materi sebelumnya.

Format Bawaan dengan ofLocalizedDate #

Menulis pola sendiri fleksibel, tetapi untuk kebutuhan umum java.time menyediakan formatter siap pakai per Locale. Method ofLocalizedDate, ofLocalizedTime, dan ofLocalizedDateTime menerima FormatStyle dengan empat level: FULL (lengkap dengan nama hari), LONG (nama bulan penuh), MEDIUM (nama bulan singkat), dan SHORT (angka ringkas):

java FormatLokal.java
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Locale;

public class FormatLokal {
    public static void main(String[] args) {
        LocalDate tanggal = LocalDate.of(2026, 1, 15);
        LocalDateTime rapat = LocalDateTime.of(2026, 1, 15, 8, 5);
        LocalTime jam = LocalTime.of(8, 5);
        Locale id = Locale.forLanguageTag("id");
        Locale us = Locale.US;

        FormatStyle[] daftarGaya = {
                FormatStyle.FULL, FormatStyle.LONG, FormatStyle.MEDIUM, FormatStyle.SHORT
        };
        for (FormatStyle gaya : daftarGaya) {
            String hasilId = DateTimeFormatter.ofLocalizedDate(gaya).withLocale(id).format(tanggal);
            String hasilUs = DateTimeFormatter.ofLocalizedDate(gaya).withLocale(us).format(tanggal);
            System.out.println(gaya + " | id    : " + hasilId);
            System.out.println(gaya + " | en-US : " + hasilUs);
        }

        DateTimeFormatter waktuId = DateTimeFormatter.ofLocalizedTime(FormatStyle.MEDIUM).withLocale(id);
        DateTimeFormatter waktuUs = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).withLocale(us);
        System.out.println("waktu MEDIUM id    : " + jam.format(waktuId));
        System.out.println("waktu SHORT us     : " + jam.format(waktuUs));

        DateTimeFormatter lengkap =
                DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(id);
        System.out.println("tanggal+waktu id   : " + rapat.format(lengkap));
    }
}

Output yang Dihasilkan:

text
FULL | id    : Kamis, 15 Januari 2026
FULL | en-US : Thursday, January 15, 2026
LONG | id    : 15 Januari 2026
LONG | en-US : January 15, 2026
MEDIUM | id    : 15 Jan 2026
MEDIUM | en-US : Jan 15, 2026
SHORT | id    : 15/01/26
SHORT | en-US : 1/15/26
waktu MEDIUM id    : 08.05.00
waktu SHORT us     : 8:05 AM
tanggal+waktu id   : 15 Jan 2026 08.05.00

Perhatikan perbandingan keempat level pada keluaran: FULL menghasilkan Kamis, 15 Januari 2026 versus Thursday, January 15, 2026, MEDIUM menghasilkan 15 Jan 2026 versus Jan 15, 2026, dan SHORT menghasilkan 15/01/26 versus 1/15/26 — urutan hari-bulan-tahun ikut berubah mengikuti kebiasaan tiap Locale. Detail menarik lainnya: format waktu Indonesia memakai titik sebagai pemisah (08.05.00), sedangkan format Inggris AS memakai titik dua dan penanda AM/PM (8:05 AM) — perbedaan yang otomatis diurus CLDR.

Pemformatan mengubah nilai menjadi teks; arah sebaliknya — membaca teks dari pengguna atau sistem lain menjadi nilai — punya kesulitannya sendiri. Bab berikutnya membahas parsing: format ISO bawaan, pola kustom, dan cara menangani teks yang tidak dapat dibaca.

Referensi #

Berkas Sumber

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

FormatLokal.java

Unduh berkas
java
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Locale;

public class FormatLokal {
    public static void main(String[] args) {
        LocalDate tanggal = LocalDate.of(2026, 1, 15);
        LocalDateTime rapat = LocalDateTime.of(2026, 1, 15, 8, 5);
        LocalTime jam = LocalTime.of(8, 5);
        Locale id = Locale.forLanguageTag("id");
        Locale us = Locale.US;

        FormatStyle[] daftarGaya = {
                FormatStyle.FULL, FormatStyle.LONG, FormatStyle.MEDIUM, FormatStyle.SHORT
        };
        for (FormatStyle gaya : daftarGaya) {
            String hasilId = DateTimeFormatter.ofLocalizedDate(gaya).withLocale(id).format(tanggal);
            String hasilUs = DateTimeFormatter.ofLocalizedDate(gaya).withLocale(us).format(tanggal);
            System.out.println(gaya + " | id    : " + hasilId);
            System.out.println(gaya + " | en-US : " + hasilUs);
        }

        DateTimeFormatter waktuId = DateTimeFormatter.ofLocalizedTime(FormatStyle.MEDIUM).withLocale(id);
        DateTimeFormatter waktuUs = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).withLocale(us);
        System.out.println("waktu MEDIUM id    : " + jam.format(waktuId));
        System.out.println("waktu SHORT us     : " + jam.format(waktuUs));

        DateTimeFormatter lengkap =
                DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(id);
        System.out.println("tanggal+waktu id   : " + rapat.format(lengkap));
    }
}

FormatPola.java

Unduh berkas
java
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

public class FormatPola {
    public static void main(String[] args) {
        LocalDate tanggal = LocalDate.of(2026, 1, 15);
        LocalDateTime rapat = LocalDateTime.of(2026, 1, 15, 8, 5, 3);
        ZonedDateTime berangkat =
                ZonedDateTime.of(2026, 1, 15, 8, 0, 0, 0, ZoneId.of("Asia/Jakarta"));
        Locale id = Locale.forLanguageTag("id");

        DateTimeFormatter standar = DateTimeFormatter.ofPattern("dd/MM/yyyy").withLocale(id);
        DateTimeFormatter panjang = DateTimeFormatter.ofPattern("dd MMMM yyyy").withLocale(id);
        DateTimeFormatter hari = DateTimeFormatter.ofPattern("EEEE, dd MMMM yyyy").withLocale(id);
        DateTimeFormatter singkat = DateTimeFormatter.ofPattern("E, dd MMM yy").withLocale(id);
        DateTimeFormatter jamLengkap = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss");
        DateTimeFormatter tanpaNol = DateTimeFormatter.ofPattern("d MMMM yyyy HH:mm").withLocale(id);
        DateTimeFormatter zona = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm z");

        System.out.println("dd/MM/yyyy         : " + tanggal.format(standar));
        System.out.println("dd MMMM yyyy       : " + tanggal.format(panjang));
        System.out.println("EEEE, dd MMMM yyyy : " + tanggal.format(hari));
        System.out.println("E, dd MMM yy       : " + tanggal.format(singkat));
        System.out.println("HH:mm:ss           : " + rapat.format(jamLengkap));
        System.out.println("d MMMM yyyy HH:mm  : " + rapat.format(tanpaNol));
        System.out.println("dengan zona        : " + berangkat.format(zona));
    }
}