Tulisan 15/01/2026 jelas bagi orang Indonesia, 01/15/2026 jelas bagi orang Amerika — dan 03/04/2026 ambigu bagi keduanya karena bisa berarti 3 April atau 4 Maret tergantung siapa yang membaca. Tanggal adalah data yang paling akrab sekaligus paling bergantung locale: nama bulan, urutan komponen, pemisah, sampai zona waktu semuanya berubah mengikuti pembaca. Chapter ini membangun lapisan localization di atas API tanggal inti dari Section Date/Time dan Localization : tabel nama bulan dan hari per locale, pola urutan penulisan, kombinasi zona waktu dan locale, serta aturan tegas memisahkan format untuk API dari format untuk layar. Ekstensi intl tidak tersedia di lingkungan buku ini, jadi IntlDateFormatter ditampilkan sebagai pembanding dan seluruh contoh kerja memakai PHP murni.

Nama Bulan dan Hari per Locale #

Method format() pada DateTimeImmutable hanya menghasilkan nama bahasa Inggris — pola F menghasilkan January, pola l menghasilkan Thursday. Untuk nama Indonesia, kita menyediakan tabel sendiri dan merakit tanggal dari komponen angka yang tidak bergantung locale:

php
<?php
$namaWaktu = [
    "id" => [
        "bulan" => [1 => "Januari", "Februari", "Maret", "April", "Mei", "Juni",
                    "Juli", "Agustus", "September", "Oktober", "November", "Desember"],
        "hari"  => [1 => "Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu", "Minggu"],
    ],
    "en" => [
        "bulan" => [1 => "January", "February", "March", "April", "May", "June",
                    "July", "August", "September", "October", "November", "December"],
        "hari"  => [1 => "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
    ],
];

function formatTanggalPanjang(DateTimeImmutable $tanggal, string $locale, array $namaWaktu): string {
    $nama = $namaWaktu[$locale];

    return sprintf(
        "%s, %d %s %d",
        $nama["hari"][(int) $tanggal->format("N")],
        (int) $tanggal->format("j"),
        $nama["bulan"][(int) $tanggal->format("n")],
        (int) $tanggal->format("Y")
    );
}

$waktu = new DateTimeImmutable("2026-01-15 08:00:00", new DateTimeZone("Asia/Jakarta"));
echo formatTanggalPanjang($waktu, "id", $namaWaktu), "\n";
echo formatTanggalPanjang($waktu, "en", $namaWaktu), "\n";

Output yang Dihasilkan:

text
Kamis, 15 Januari 2026
Thursday, 15 January 2026

Mari kita bedah cara kerjanya. Pola N menghasilkan nomor hari ISO (1 untuk Senin hingga 7 untuk Minggu) dan pola n menghasilkan nomor bulan tanpa nol di depan — keduanya angka murni yang tidak bergantung locale, sehingga aman dipakai sebagai kunci array. Tabel $namaWaktu memetakan nomor tersebut ke nama per locale, lalu sprintf() merakit hasil akhirnya. Objek $waktu sengaja dibekukan pada tanggal tetap agar output contoh deterministik; pada aplikasi nyata, nilainya datang dari data Anda. Menambah locale baru berarti menambah satu entri tabel — struktur data yang sama dengan katalog terjemahan pada chapter sebelumnya.

Urutan Penulisan Tanggal #

Lokalisasi nama belum lengkap tanpa urutan yang benar. Tiga konvensi paling umum mengurutkan hari, bulan, dan tahun secara berbeda:

LocalePola format()Tampilan untuk 15 Januari 2026
idd-m-Y15-01-2026
en-USm/d/Y01/15/2026
ISO 8601Y-m-d2026-01-15

Karena pola format() hanyalah string, pemetaan locale ke pola juga sekadar array:

php
<?php
$waktu = new DateTimeImmutable("2026-01-15 08:00:00", new DateTimeZone("Asia/Jakarta"));

$polaPerLocale = [
    "id"    => "d-m-Y",
    "en-US" => "m/d/Y",
    "ISO"   => "Y-m-d",
];

foreach ($polaPerLocale as $locale => $pola) {
    echo $locale, ": ", $waktu->format($pola), "\n";
}

Output yang Dihasilkan:

text
id: 15-01-2026
en-US: 01/15/2026
ISO: 2026-01-15

Baris ISO sengaja disertakan karena urutan tahun-bulan-hari tersebut adalah format yang tidak ambigu — kita pakai lagi di bagian akhir chapter ini untuk kebutuhan API.

php
<?php
$versiIndo = DateTimeImmutable::createFromFormat("d-m-Y", "03-04-2026");
$versiAmerika = DateTimeImmutable::createFromFormat("m/d/Y", "03/04/2026");

echo "dibaca id   : ", $versiIndo->format("Y-m-d"), "\n";
echo "dibaca en-US: ", $versiAmerika->format("Y-m-d"), "\n";

Output yang Dihasilkan:

text
dibaca id   : 2026-04-03
dibaca en-US: 2026-03-04

Dua objek lahir dari digit yang sama — 03, 04, 2026 — namun createFromFormat("d-m-Y", ...) menghasilkan 3 April sedangkan createFromFormat("m/d/Y", ...) menghasilkan 4 Maret. Saat mengambil input tanggal dari pengguna, selalu tentukan format secara eksplisit atau sediakan date picker; jangan pernah menebak pola dari string bebas.

Zona Waktu dan Locale Bersamaan #

Zona waktu dan locale menjawab dua pertanyaan yang berbeda dan keduanya wajib dijawab sebelum menampilkan waktu. Zona waktu menjawab “pukul berapa peristiwa ini terjadi di sana”; locale menjawab “bagaimana angka dan nama itu ditulis”. Menggabungkan keduanya berarti mengonversi instan waktu ke zona tujuan, lalu memformat hasilnya menurut konvensi pembaca:

php
<?php
$waktu = new DateTimeImmutable("2026-01-15 08:00:00", new DateTimeZone("Asia/Jakarta"));

echo "Jakarta : ", $waktu->format("d-m-Y H:i"), " ", $waktu->format("T"), "\n";

$newYork = $waktu->setTimezone(new DateTimeZone("America/New_York"));
echo "New York: ", $newYork->format("n/j/Y g:i A"), " ", $newYork->format("T"), "\n";

Output yang Dihasilkan:

text
Jakarta : 15-01-2026 08:00 WIB
New York: 1/14/2026 8:00 PM EST

Method setTimezone() mengembalikan objek DateTimeImmutable baru — objek lama tidak berubah, sesuai sifat immutable yang kita bahas pada Chapter DateTimeImmutable . Hasil New York layak dicermati: jam 08.00 pagi di Jakarta masih berupa pukul 8:00 PM tanggal 1/14 di New York — bukan hanya jamnya yang berubah, tanggalnya ikut mundur sehari karena konferensi kedua zona terpisah 12 jam. Pola T menghasilkan singkatan zona (WIB, EST) langsung dari data zona, dan pola g beserta A merakit format 12 jam gaya Amerika.

Format untuk API versus Tampilan #

Aturan yang membedakan dua konsumen tanggal: mesin dan manusia. Manusia ingin 15-01-2026 08.00 WIB; mesin pemanggil API Anda — aplikasi mobile, layanan pihak ketiga, job scheduler — ingin satu format standar yang bisa diurai tanpa menebak locale. Standar itu adalah ISO 8601 dengan offset eksplisit, dan praktik terbaiknya menyatakannya dalam UTC:

php
<?php
$pesanDibuat = new DateTimeImmutable("2026-01-15 08:00:00", new DateTimeZone("Asia/Jakarta"));

// Untuk konsumen API: ISO 8601, dikonversi ke UTC
$utc = $pesanDibuat->setTimezone(new DateTimeZone("UTC"));
echo "API   : ", $utc->format(DateTimeInterface::ATOM), "\n";

// Untuk layar pengguna: format locale Indonesia
echo "Layar : ", $pesanDibuat->format("d-m-Y H:i"), " ", $pesanDibuat->format("T"), "\n";

Output yang Dihasilkan:

text
API   : 2026-01-15T01:00:00+00:00
Layar : 15-01-2026 08:00 WIB

Konstanta DateTimeInterface::ATOM berisi pola ISO 8601 lengkap dengan offset, sehingga hasilnya 2026-01-15T01:00:00+00:00 — jam 08.00 WIB memang setara 01.00 UTC. Dengan offset yang eksplisit, konsumen API tidak perlu tahu server Anda di Jakarta; konsumen tinggal mengonversi ke zona penggunanya sendiri dan memformat menurut locale-nya. Baris kedua menunjukkan sisi manusia: objek asli (yang zonya tetap Jakarta) diformat untuk pembaca Indonesia.

IntlDateFormatter sebagai Pembanding #

Ekstensi intl menyatukan seluruh pekerjaan chapter ini — nama per locale, pola panjang atau pendek, dan zona waktu — dalam satu kelas:

php
<?php
// Membutuhkan ekstensi intl — tidak tersedia di lingkungan buku ini
$formatter = new IntlDateFormatter(
    "id_ID",
    IntlDateFormatter::LONG,
    IntlDateFormatter::SHORT,
    "Asia/Jakarta",
);

echo $formatter->format(new DateTimeImmutable("2026-01-15 08:00:00"));

Untuk locale id_ID, gaya LONG untuk tanggal dan SHORT untuk waktu, formatter ini menghasilkan tulisan setara 15 Januari 2026 pukul 08.00 menurut data CLDR — tanpa tabel nama dan pola manual seperti yang kita bangun di atas. Ekstensi ini tidak tersedia di lingkungan buku ini, sehingga pendekatan PHP murni tetap menjadi alat kerja utama; strukturnya identik, hanya sumber datanya yang berpindah dari kode Anda ke pustaka.

Ringkasan #

Penampilan tanggal terdiri dari tiga lapisan yang masing-masing bisa dipilih bebas: nama (tabel per locale), urutan (pola d-m-Y versus m/d/Y versus ISO), dan zona waktu (setTimezone() pada objek immutable). Pembaca manusia menerima hasil gabungan ketiganya, sedangkan konsumsi mesin menerima ISO 8601 UTC yang tidak ambigu. Dengan string pesan, angka, uang, dan kini waktu yang sadar locale, satu pertanyaan tersisa: bagaimana menyatukan semuanya menjadi aplikasi multi-bahasa yang utuh — itulah arsitektur pada Chapter Multilingual Application Design penutup section ini.

Referensi #