Date, Number & Currency Formatting — i18n

Date, Number & Currency Formatting Format tanggal, angka, dan mata uang berbeda drastis antar locale. Hardcoding format adalah salah satu kesalahan i18n…

Harga, tanggal, dan jam ditulis beda di tiap negara, padahal nilainya sama. Aturannya: simpan nilai mentah, format di ujung. Bayangin buku kas Bu Sri: harga ditulis polos 700000, waktu check-in ditulis dalam UTC. Tiap tamu menerima salinan yang ditulis ulang dengan kebiasaannya sendiri: "Rp 700.000" untuk tamu Indonesia, "IDR 700,000" untuk tamu Amerika, jam 14.00 WIB yang sama ditulis 16.00 di Tokyo. Pelajaran Intl API sudah memperkenalkan pemformatnya; di sini kita masuk ke pilihan-pilihannya.

🧪 Tebakan cepat: Check-in Bu Sri adalah 2026-12-12T07:00:00Z (14.00 WIB). Tamu di Kairo memformatnya dengan timeZone: 'Africa/Cairo'. Jam berapa yang tertulis, dan apakah jamnya sama kalau tanggalnya 12 Juli? Jawabannya ada di bagian "Satu waktu, tiga jam dinding".

Simpan mentah, format di ujung

// Di database dan API: angka dan waktu UTC, bukan teks jadi
const booking = { pricePerNight: 350000, nights: 2, checkIn: '2026-12-12T07:00:00Z' };

// Di layar: format dengan locale tamu dan zona tempat kejadiannya
const total = new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', maximumFractionDigits: 0 })
  .format(booking.pricePerNight * booking.nights);              // "Rp 700.000"
const when = new Intl.DateTimeFormat('id-ID', { dateStyle: 'full', timeStyle: 'short', timeZone: 'Asia/Jakarta' })
  .format(new Date(booking.checkIn));                          // "Sabtu, 12 Desember 2026 pukul 14.00"

Mata uang: symbol, narrowSymbol, code, name

currencyDisplay memilih cara menulis mata uang. Harga kamar dalam rupiah, plus contoh nominal di mata uang lain (bukan kurs):

currencyDisplay id-ID, IDR 700000 en-US, IDR 700000 id-ID, USD 42.5 en-US, USD 42.5 en-US, EGP 2150
symbol (bawaan) Rp 700.000 IDR 700,000 US$42,50 $42.50 EGP 2,150.00
narrowSymbol Rp 700.000 Rp 700,000 $42,50 $42.50 E£2,150.00
code IDR 700.000 IDR 700,000 USD 42,50 USD 42.50 EGP 2,150.00
name 700.000 Rupiah Indonesia 700,000 Indonesian rupiahs 42,50 Dolar Amerika Serikat 42.50 US dollars 2,150.00 Egyptian pounds

Notasi ringkas dan satuan

new Intl.NumberFormat('id-ID', { notation: 'compact' }).format(1200000);                           // "1,2 jt"
new Intl.NumberFormat('id-ID', { notation: 'compact', compactDisplay: 'long' }).format(1200000);   // "1,2 juta"
new Intl.NumberFormat('id-ID', { style: 'unit', unit: 'kilometer', unitDisplay: 'long' }).format(26); // "26 kilometer"
new Intl.NumberFormat('id-ID', { style: 'unit', unit: 'celsius' }).format(12);                     // "12°C"

Tanggal: dateStyle atau field satu per satu

Opsi id-ID en-US ja-JP ar-EG
dateStyle: 'short' 12/12/26 12/12/26 2026/12/12 ١٢/١٢/٢٠٢٦
dateStyle: 'medium' 12 Des 2026 Dec 12, 2026 2026/12/12 ١٢/١٢/٢٠٢٦
dateStyle: 'long' 12 Desember 2026 December 12, 2026 2026年12月12日 ١٢ ديسمبر ٢٠٢٦
{ weekday: 'short', day: 'numeric', month: 'short' } Sab, 12 Des Sat, Dec 12 12月12日(土) السبت، ١٢ ديسمبر

Rentang dan waktu relatif

const checkIn = new Date('2026-12-12T07:00:00Z');   // Sabtu 14.00 WIB
const checkOut = new Date('2026-12-14T05:00:00Z');  // Senin 12.00 WIB
new Intl.DateTimeFormat('id-ID', { day: 'numeric', month: 'short', timeZone: 'Asia/Jakarta' }).formatRange(checkIn, checkOut);
// "12\u201314 Des"   (\u2013 adalah tanda pisah en dash)
// en-US: "Dec 12\u2009\u2013\u200914" di Chromium dan Node, "Dec 12 \u2013 14" di Firefox dan Safari
// ja-JP: "12/12~12/14", Firefox "12月12日~14日"; ar-EG: "١٢\u2013١٤ ديسمبر"

const rtf = new Intl.RelativeTimeFormat('id-ID', { numeric: 'auto' });
rtf.format(3, 'day');   // "dalam 3 hari"
rtf.format(1, 'day');   // "besok"

Satu waktu, tiga jam dinding

const fmt = (zone) => new Intl.DateTimeFormat('id-ID', { weekday: 'short', day: 'numeric', month: 'short', hour: 'numeric', minute: '2-digit', timeZoneName: 'short', timeZone: zone }).format(checkIn);
fmt('Asia/Jakarta');  // "Sab, 12 Des, 14.00 WIB"
fmt('Asia/Tokyo');    // "Sab, 12 Des, 16.00 GMT+9"   (ja-JP menulis "JST")
fmt('Africa/Cairo');  // "Sab, 12 Des, 09.00 GMT+2"

Di Kairo tertulis 09.00, karena bulan Desember Mesir memakai UTC+2. Mesir memakai waktu musim panas, jadi untuk 12 Juli offset-nya GMT+03:00 dan jamnya 10.00. Itu jawaban tebakannya. Jadi jangan pernah menyimpan "UTC+2" sebagai zona; simpan nama IANA (Africa/Cairo) dan biarkan Intl yang menghitung. "WIB" juga bukan nama zona: timeZone: 'WIB' melempar RangeError. Untuk jadwal check-in, tulis jam di zona homestay (Asia/Jakarta); jam di zona tamu boleh ditambahkan sebagai keterangan.

📌 Beda tiap mesin, diukur

Diukur di Chromium 147, Firefox 146, WebKit 26.4, dan Node 22.22.1. Yang gak ada di tabel ini sama di keempatnya.

Yang diukur Chromium 147 Firefox 146 WebKit 26.4 Node 22.22.1
Desimal bawaan IDR 2 2 2 0
349999,5 IDR dengan minimumFractionDigits: 0 saja Rp 349.999,5 Rp 349.999,5 Rp349.999,5 Rp 350.000
Simbol JPY di ja-JP ¥ ¥ ¥ ¥
Spasi di rentang en-US U+2009 spasi biasa spasi biasa U+2009
Rentang ja-JP 12/12~12/14 12月12日~14日 12/12~12/14 12/12~12/14
weekday: 'short' di ar-EG السبت السبت سبت السبت
Pesan TypeError untuk dateStyle plus weekday Invalid option : option can't set option weekday when dateStyle is used dateStyle and timeStyle may not be used with other DateTimeFormat options Invalid option : option
unit: 'day', long, ja-JP 2 日 2 日 2日 2 日

⚠️ Jebakan yang sering ketemu

TL;DR: Simpan harga sebagai angka (700000) dan waktu sebagai UTC (2026-12-12T07:00:00Z), lalu format di layar. Mata uang memakai { style: "currency", currency: "IDR" } plus currencyDisplay (symbol "Rp 700.000" atau "IDR 700,000", narrowSymbol "Rp", code "IDR", name "Rupiah Indonesia"), dan rupiah selalu dengan maximumFractionDigits: 0, karena desimal bawaannya 2 di browser dan 0 di Node 22. Locale id-ID menulis 1000000 sebagai "1.000.000". Notasi ringkas ("1,2 jt", "1.2M", "120万") membulatkan, jadi bukan untuk harga. style: 'unit' untuk km, °C, dan hari. Tanggal memakai dateStyle atau field, gak boleh dicampur. formatRange menulis "12 sampai 14 Des" dengan tanda pisah U+2013, RelativeTimeFormat menulis "dalam 3 hari". Check-in yang sama adalah 14.00 di Asia/Jakarta, 16.00 di Asia/Tokyo, dan 09.00 di Africa/Cairo (10.00 di bulan Juli); simpan nama zona IANA, bukan offset.

Yang akan kamu pelajari