karawaci.kode

2026-04-22 · 4 min

Temporal API JavaScript 2026: Akhirnya Boleh Lupa Moment.js

Temporal API JavaScript akhirnya Stage 4 di TC39 (April 2026). Bun 1.3, Node 23, dan Deno 2.5 sudah support native. Chrome 128+ dan Safari 18+ juga. Untuk SaaS Indonesia yang user-nya mostly mobile Chrome modern: bisa pakai langsung tanpa polyfill.

Saya migrasi 3 project klien dalam 2 minggu terakhir. Berikut catatannya.

Yang Moment.js / date-fns kasih, yang masih sering dipake

// Moment
moment().format('DD MMMM YYYY')
moment().add(7, 'days')
moment('2026-01-01').isBefore(moment('2026-12-31'))
moment().diff(moment().subtract(1, 'year'), 'days')

// date-fns
format(new Date(), 'dd MMMM yyyy', { locale: id })
addDays(new Date(), 7)
isBefore(parse('2026-01-01'), parse('2026-12-31'))
differenceInDays(new Date(), subYears(new Date(), 1))

Yang Temporal kasih, native

const now = Temporal.Now.plainDateISO();
const formatted = now.toLocaleString('id-ID', { 
  dateStyle: 'long' 
}); // "4 Juni 2026"

const nextWeek = now.add({ days: 7 });
const isBefore = Temporal.PlainDate.compare('2026-01-01', '2026-12-31') < 0;
const daysDiff = now.since(now.subtract({ years: 1 })).total('days');

Lebih panjang? Kadang ya, kadang tidak. Tapi lebih SAFE.

Safer karena?

  1. Timezone explicit. Tidak ada “implicit local time” hell.
// Old: ambiguous
new Date('2026-01-01') // Browser interpret as local? UTC?

// Temporal: explicit
Temporal.PlainDateTime.from('2026-01-01T00:00:00')      // No timezone
Temporal.ZonedDateTime.from('2026-01-01T00:00:00+07:00') // Explicit
Temporal.ZonedDateTime.from('2026-01-01T00:00:00[Asia/Jakarta]') // Explicit

Untuk Indonesia SaaS, banyak bug saya dulu karena confusion antara WIB/WIT/UTC. Temporal mengeliminasi ini.

  1. Immutable. Setiap operation return new instance. Tidak ada date.setMonth(...) mutation yang accidentally break upstream code.

  2. Arithmetic clearer.

// Old: date math gotcha
const lastDay = new Date(2026, 0, 31); // Jan 31
lastDay.setMonth(lastDay.getMonth() + 1); // What date? Feb 31 → Mar 3!

// Temporal: explicit overflow
const lastDay = Temporal.PlainDate.from('2026-01-31');
lastDay.add({ months: 1 }); // 2026-02-28 (clamped to month end)
lastDay.add({ months: 1, overflow: 'reject' }); // Throws RangeError

Saya pernah ngabisin 4 jam debug Feb 31 issue lima tahun lalu. Temporal eliminate this entirely.

Migration strategy

Saya tidak rewrite seluruh codebase. Strategy:

  1. New code: pakai Temporal.
  2. Old code: leave alone kecuali ada bug yang related ke date logic.
  3. Shared helpers: rewrite ke Temporal, deprecate moment-based one.
// helpers/date.ts (new)
import { Temporal } from 'temporal-polyfill'; // Polyfill untuk client-side browsers older

export function formatIndonesian(date: Temporal.PlainDate): string {
  return date.toLocaleString('id-ID', { dateStyle: 'long' });
}

export function daysUntil(target: Temporal.PlainDate): number {
  const today = Temporal.Now.plainDateISO();
  return Math.ceil(target.since(today).total('days'));
}

// helpers/date-legacy.ts (deprecated, kept untuk old code)
import dayjs from 'dayjs';
// ... existing helpers

Build size impact

Bundle saya:

LibrarySize minified+gzip
Moment.js73 KB
date-fns (yang saya pakai)18 KB
dayjs7 KB
Temporal (native, no library)0 KB
temporal-polyfill (untuk older browser)15 KB

Untuk modern-only audience: 0 KB. Untuk include polyfill: 15 KB (less than date-fns).

Yang Temporal belum cover

  1. Lunar calendar (Hijriah): untuk app yang display tanggal Hijriah untuk audience Muslim. Saya masih pakai dayjs dengan plugin Hijri. Temporal Calendar API support custom calendars, tapi setup-nya verbose.

  2. Business day calculations: hitung “5 hari kerja”, skip weekend dan holiday. Tidak native di Temporal. Saya tulis helper sendiri:

const HOLIDAYS_2026 = new Set([
  '2026-01-01', '2026-02-17', /* ... */
]);

function addBusinessDays(start: Temporal.PlainDate, n: number): Temporal.PlainDate {
  let current = start;
  let remaining = n;
  while (remaining > 0) {
    current = current.add({ days: 1 });
    const dow = current.dayOfWeek;
    if (dow >= 1 && dow <= 5 && !HOLIDAYS_2026.has(current.toString())) {
      remaining--;
    }
  }
  return current;
}

Saran

Untuk project baru: pakai Temporal. Untuk project existing: migrasi gradual, tidak harus all-at-once.

Untuk junior dev di tim: invest 2 jam baca Temporal docs. Banyak bug date-related yang dulunya “normal” untuk dialami akan hilang.

Stage 4 berarti final. API tidak akan berubah lagi. Aman untuk commit ke ini.

Ditulis oleh Reza Pradipta