Jam dinding di Jakarta dan di London dapat menunjukkan angka yang berbeda meskipun keduanya merujuk pada momen yang sama. Timezone adalah aturan yang menerjemahkan satu momen universal menjadi angka jam dinding setempat, dan sebaliknya. Sepanjang section ini kita berulang kali menulis new DateTimeZone("Asia/Jakarta") sebagai argumen kedua konstruktor, misalnya pada chapter Date/Time Immutable , tanpa membahasnya lebih dalam. Chapter ini mengupas kelas DateTimeZone secara utuh: membaca offset, mengonversi antarzona, membedakan zona bernama dari zona offset, memahami daylight saving time, sampai menetapkan timezone default untuk seluruh aplikasi.

Timezone dan Offset dari UTC #

Setiap objek date/time di PHP menyimpan dua informasi yang berbeda: sebuah titik waktu (instant) dan aturan zona yang menentukan bagaimana titik waktu tersebut dibaca sebagai jam dinding. Aturan zona yang paling sederhana berupa offset dari UTC, misalnya +07:00 yang berarti jam lokal berjalan tujuh jam lebih cepat daripada UTC. Metode getOffset() mengembalikan offset dalam satuan detik sehingga cocok untuk perhitungan teknis.

php
<?php
$zona = new DateTimeZone("Asia/Jakarta");

$waktu = new DateTimeImmutable("2026-01-15 08:00:00", $zona);
echo $waktu->format("Y-m-d H:i:s T P e") . "\n";

$offsetDetik = $zona->getOffset(new DateTimeImmutable("2026-01-15 01:00:00", new DateTimeZone("UTC")));
echo "Offset dalam detik: $offsetDetik\n";
echo "Offset dalam jam: " . ($offsetDetik / 3600) . "\n";

Output yang Dihasilkan:

text
2026-01-15 08:00:00 WIB +07:00 Asia/Jakarta
Offset dalam detik: 25200
Offset dalam jam: 7

Mari kita telaah baris-baris kode di atas:

  1. Baris 2: objek DateTimeZone dibuat untuk identifier bernama Asia/Jakarta.
  2. Baris 4-5: objek waktu beku 2026-01-15 08:00:00 dibuat dengan zona Jakarta, lalu format() membacanya dengan tiga karakter zona: T (singkatan zona, WIB), P (offset +07:00), dan e (identifier lengkap).
  3. Baris 7-9: getOffset() membutuhkan satu instant sebagai acuan. Objek UTC pukul 01:00 yang diberikan adalah momen yang sama dengan pukul 08:00 WIB, dan hasilnya 25200 detik.

Nilai 25200 dibagi 3600 tepat menghasilkan 7, sesuai offset WIB. Angka detik semacam ini yang dipakai ketika kita perlu menghitung selisih atau durasi secara teknis, tanpa perlu memformatnya menjadi teks.

Tiga Zona Waktu Indonesia #

Indonesia secara resmi memakai tiga zona waktu: Waktu Indonesia Barat (WIB, UTC+7) yang diwakili Asia/Jakarta, Waktu Indonesia Tengah (WITA, UTC+8) yang diwakili Asia/Makassar, dan Waktu Indonesia Timur (WIT, UTC+9) yang diwakili Asia/Jayapura. Ketiga identifier ini berasal dari basis data zona IANA yang dibawa PHP. Konversi antarzona tidak pernah mengubah momen yang disimpan; yang berubah hanyalah cara momen tersebut dibaca sebagai jam dinding.

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

echo "Jakarta : " . $rapat->format("Y-m-d H:i:s T") . "\n";
echo "Makassar: " . $rapat->setTimezone(new DateTimeZone("Asia/Makassar"))->format("Y-m-d H:i:s T") . "\n";
echo "Jayapura: " . $rapat->setTimezone(new DateTimeZone("Asia/Jayapura"))->format("Y-m-d H:i:s T") . "\n";
echo "UTC     : " . $rapat->setTimezone(new DateTimeZone("UTC"))->format("Y-m-d H:i:s T") . "\n";

Output yang Dihasilkan:

text
Jakarta : 2026-01-15 08:00:00 WIB
Makassar: 2026-01-15 09:00:00 WITA
Jayapura: 2026-01-15 10:00:00 WIT
UTC     : 2026-01-15 01:00:00 UTC

Pada kode di atas, satu objek beku pukul 08:00 WIB menjadi momen acuan tunggal. Metode setTimezone() mengembalikan objek baru (karena DateTimeImmutable tidak pernah diubah di tempat) yang membaca momen sama dengan zona berbeda. Jam dinding bergeser satu jam untuk setiap zona ke arah timur, dan hasil 01:00 di UTC menegaskan offset Jakarta memang +7.

Daftar identifier valid dapat kita telusuri lewat metode statis DateTimeZone::listIdentifiers():

php
<?php
$zonaIndonesia = array_filter(
    DateTimeZone::listIdentifiers(DateTimeZone::ASIA),
    fn (string $zona): bool => in_array(
        $zona,
        ["Asia/Jakarta", "Asia/Makassar", "Asia/Jayapura"],
        true
    )
);

echo implode(", ", $zonaIndonesia) . "\n";
echo "Jumlah identifier zona di kawasan Asia: "
    . count(DateTimeZone::listIdentifiers(DateTimeZone::ASIA)) . "\n";

Output yang Dihasilkan:

text
Asia/Jakarta, Asia/Jayapura, Asia/Makassar
Jumlah identifier zona di kawasan Asia: 82

Kode di atas menyaring 82 identifier kawasan Asia menggunakan array_filter() dengan arrow function, lalu implode() menggabungkan hasilnya menjadi satu baris. Jika kita memberikan identifier yang tidak dikenal, misalnya "Asia/Bandung", konstruktor DateTimeZone melempar Exception — selalu gunakan identifier dari daftar resmi.

Zona Bernama versus Zona Offset #

PHP menerima dua jenis nilai zona: identifier bernama dari basis data IANA dan string offset seperti "+07:00". Keduanya sama-sama valid sebagai argumen, tetapi membawa makna yang berbeda. Zona bernama membawa seluruh sejarah aturan suatu lokasi (termasuk kebijakan DST), sedangkan zona offset hanya menyimpan pergeseran tetap dari UTC.

AspekZona bernama (Asia/Jakarta)Zona offset (+07:00)
Sumber aturanBasis data IANA yang dibawa PHPPergeseran tetap dari UTC
Offset dapat berubahYa, mengikuti aturan historis dan DSTTidak, selalu tetap
Singkatan format("T")WIBGMT+0700
Cocok untukKebutuhan pengguna dan lokasi nyataPerhitungan teknis
php
<?php
$zonaBernama = new DateTimeZone("Asia/Jakarta");
$zonaOffset = new DateTimeZone("+07:00");

echo $zonaBernama->getName() . "\n";
echo $zonaOffset->getName() . "\n";

$waktu = new DateTimeImmutable("2026-01-15 08:00:00", $zonaOffset);
echo $waktu->format("Y-m-d H:i:s T e") . "\n";

Output yang Dihasilkan:

text
Asia/Jakarta
+07:00
2026-01-15 08:00:00 GMT+0700 +07:00

Metode getName() mengembalikan identifier apa adanya, sehingga zona offset tercetak sebagai +07:00. Perhatikan hasil format() pada baris terakhir: zona offset tidak memiliki singkatan resmi, sehingga karakter T menampilkan bentuk GMT+0700, sementara karakter e menampilkan +07:00. Gunakan zona bernama setiap kali zona mewakili lokasi pengguna yang nyata; sisakan zona offset untuk kebutuhan teknis seperti menandai seluruh data dengan +00:00.

Daylight Saving Time #

Daylight Saving Time (DST) adalah kebijakan menggeser jam dinding satu jam ke depan selama musim panas agar sore mendapat cahaya matahari lebih lama. Akibat kebijakan ini, offset sebuah zona bernama dapat berubah menurut tanggal: zona America/New_York memakai EST (-05:00) selama musim dingin, lalu bergeser ke EDT (-04:00) selama musim panas. PHP menghitung offset yang benar secara otomatis dari basis data IANA, selama kita memakai zona bernama dan bukan offset tetap.

php
<?php
$januari = new DateTimeImmutable("2026-01-15 12:00:00", new DateTimeZone("America/New_York"));
$juli    = new DateTimeImmutable("2026-07-15 12:00:00", new DateTimeZone("America/New_York"));

echo $januari->format("Y-m-d H:i:s T P") . "\n";
echo $juli->format("Y-m-d H:i:s T P") . "\n";

echo "Januari dalam UTC: " . $januari->setTimezone(new DateTimeZone("UTC"))->format("H:i") . "\n";
echo "Juli dalam UTC   : " . $juli->setTimezone(new DateTimeZone("UTC"))->format("H:i") . "\n";

Output yang Dihasilkan:

text
2026-01-15 12:00:00 EST -05:00
2026-07-15 12:00:00 EDT -04:00
Januari dalam UTC: 17:00
Juli dalam UTC   : 16:00

Kedua objek sengaja diberi jam dinding yang sama, 12:00, pada zona yang sama. Meskipun begitu, konversi ke UTC menghasilkan 17:00 untuk Januari dan 16:00 untuk Juli — selisih satu jam persis karena offset zona berubah di antara dua tanggal tersebut. Inilah alasan membandingkan dua waktu berdasakan teks jam dindingnya bisa menyesatkan saat zona menerapkan DST.

Timezone Default Aplikasi #

PHP memakai satu timezone default untuk seluruh proses ketika kode tidak memberikan zona secara eksplisit. Tanpa berkas php.ini, default tersebut adalah UTC. Fungsi date_default_timezone_get() membaca nilai tersebut, date_default_timezone_set() mengubahnya, dan fungsi prosedural seperti date() ikut terpengaruh karena date() tidak menerima argumen timezone.

ini
; php.ini — menetapkan timezone default untuk seluruh aplikasi
date.timezone = "Asia/Jakarta"
php
<?php
echo "Default awal : " . date_default_timezone_get() . "\n";

date_default_timezone_set("Asia/Jakarta");

echo "Default baru : " . date_default_timezone_get() . "\n";
echo date("Y-m-d H:i:s T", 1767225600) . "\n";

Output yang Dihasilkan:

text
Default awal : UTC
Default baru : Asia/Jakarta
2026-01-01 07:00:00 WIB

Baris terakhir memakai timestamp beku 1767225600 yang merujuk momen 2026-01-01 00:00:00 UTC. Karena timezone default sudah diubah ke Jakarta, date() membaca momen tersebut sebagai 07:00 WIB. Status timezone default bersifat global untuk seluruh proses, sehingga memanggil date_default_timezone_set() di tengah aplikasi besar membuat perilaku kode bergantung pada urutan eksekusi. Praktik yang lebih aman adalah menetapkannya satu kali di titik masuk aplikasi atau php.ini, lalu tetap membawa zona secara eksplisit lewat argumen konstruktor untuk kode yang sensitif.

Menyimpan Zona Pengguna di Profil #

Pola yang telah menjadi standar industri: seluruh data waktu disimpan dalam UTC, sementara zona pengguna disimpan sebagai identifier bernama pada profil pengguna. Saat menampilkan, aplikasi mengonversi instant UTC ke zona pengguna. Dengan pola ini, satu acara yang sama tampil benar bagi pengguna di Jakarta maupun di Jayapura tanpa perlu menduplikasi data.

php
<?php
$profil = [
    "nama"     => "Budi",
    "timezone" => "Asia/Jayapura",
];

// Di database, acara disimpan dalam UTC
$acaraUtc = new DateTimeImmutable("2026-01-15 01:00:00", new DateTimeZone("UTC"));

$acaraPengguna = $acaraUtc->setTimezone(new DateTimeZone($profil["timezone"]));
echo "Acara untuk {$profil['nama']}: "
    . $acaraPengguna->format("d-m-Y H:i T") . "\n";

Output yang Dihasilkan:

text
Acara untuk Budi: 15-01-2026 10:00 WIT

Array $profil menirukan satu baris data pengguna dari database: nama Budi dengan zona Asia/Jayapura. Acara tersimpan sebagai 01:00 UTC, lalu setTimezone() mengonversinya menjadi 10:00 WIT khusus untuk keperluan tampilan. Nilai tersimpan tetap satu dan tidak terkorupsi oleh zona pembacanya.

Ringkasan #

Timezone adalah penerjemah antara momen universal dan jam dinding setempat. Zona bernama membawa aturan lengkap termasuk DST, zona offset menawarkan pergeseran tetap untuk kebutuhan teknis, dan setTimezone() mengonversi cara membaca tanpa mengubah momen yang disimpan. Dengan menyimpan UTC beserta zona bernama pada profil pengguna, aplikasi kita siap melayani pembaca dari Sabang sampai Merauke. Selain pasangan tanggal-jam-zona, PHP juga mengenal representasi waktu yang jauh lebih ringkas: satu angka integer bernama Unix timestamp, topik Chapter berikutnya.

Referensi #