Unix timestamp adalah jumlah detik sejak 1 Januari 1970 pukul 00:00:00 UTC, dan nilainya sama di seluruh dunia pada detik yang sama. Aturan praktis yang menyelamatkan Anda dari sebagian besar bug waktu: simpan dan olah waktu dalam UTC, ubah ke zona waktu pengguna (misalnya WIB) hanya saat ditampilkan. Perlu diingat juga bahwa PHP memakai detik, sedangkan JavaScript memakai milidetik.
Panduan ini membahas timestamp dan zona waktu di PHP, Laravel, JavaScript, dan MySQL, termasuk penyebab bug klasik "jamnya selisih 7 jam" yang sering dialami developer di Indonesia.
Apa itu Unix timestamp (epoch)?
Titik nol waktu Unix, disebut epoch, adalah 1970-01-01T00:00:00Z. Setiap detik sesudahnya menambah satu angka. Misalnya timestamp 1700000000 menunjuk ke 14 November 2023 pukul 22:13:20 UTC, yang sama dengan 15 November 2023 pukul 05:13:20 WIB. Angkanya satu, hanya cara menampilkannya yang berbeda.
Karena tidak membawa informasi zona waktu, timestamp sangat cocok untuk menyimpan "momen" yang absolut: kapan pesanan dibuat, kapan token kedaluwarsa, kapan pengguna terakhir login. Anda bisa mengonversi timestamp ke tanggal (dan sebaliknya) secara cepat dengan Timestamp Converter.
Detik vs milidetik
Ini sumber kebingungan nomor satu ketika data berpindah antara backend PHP dan frontend JavaScript.
| Bahasa / sistem | Satuan | Contoh |
|---|---|---|
PHP time() | Detik | 1700000000 (10 digit) |
JavaScript Date.now() | Milidetik | 1700000000000 (13 digit) |
MySQL UNIX_TIMESTAMP() | Detik | 1700000000 |
Payload JWT (exp, iat) | Detik | 1700000000 |
Klaim exp pada JWT memakai detik; Anda bisa melihat nilainya beserta tanggal kedaluwarsanya dengan JWT Decoder.
Cara cepat mengenalinya: timestamp detik untuk tahun-tahun sekarang berjumlah 10 digit, sedangkan milidetik 13 digit. Jika tanggal Anda tiba-tiba muncul di tahun 1970 atau di tahun puluhan ribu, hampir pasti terjadi salah satuan.
// JS: dari detik (PHP) ke Date
const d = new Date(1700000000 * 1000);
// JS: dari Date ke detik (untuk dikirim ke PHP)
const seconds = Math.floor(Date.now() / 1000);
Prinsip utama: simpan dalam UTC
Mengapa UTC? Karena UTC tidak punya daylight saving time, tidak berubah karena kebijakan pemerintah, dan menjadi acuan bersama semua sistem. Indonesia sendiri memiliki tiga zona waktu: WIB (UTC+7), WITA (UTC+8), dan WIT (UTC+9). Jika aplikasi Anda dipakai di Jakarta, Makassar, dan Jayapura, menyimpan waktu dalam WIB akan membuat data dari wilayah lain rancu.
Polanya sederhana:
- Server, database, dan aplikasi bekerja dalam UTC.
- Data waktu dikirim lewat API dalam format ISO 8601 dengan penanda zona, misalnya
2023-11-14T22:13:20Z, atau sebagai Unix timestamp. - Konversi ke zona pengguna hanya dilakukan di lapisan tampilan, baik di Blade maupun di browser.
Waktu di PHP: DateTime dan zona waktu
PHP memiliki zona waktu default yang diambil dari date.timezone di php.ini atau dari date_default_timezone_set(). Fungsi seperti date() memakai zona ini, sedangkan time() selalu mengembalikan timestamp yang sama apa pun zonanya.
<?php
date_default_timezone_set('UTC');
$ts = time(); // tidak terpengaruh zona
echo date('Y-m-d H:i:s', $ts); // ditampilkan dalam UTC
$dt = new DateTimeImmutable('@' . $ts); // selalu UTC (+00:00)
$jkt = $dt->setTimezone(new DateTimeZone('Asia/Jakarta'));
echo $jkt->format('d/m/Y H:i T'); // contoh: 15/11/2023 05:13 WIB
// dari string lokal ke timestamp
$x = new DateTimeImmutable('2024-08-17 10:00:00', new DateTimeZone('Asia/Jakarta'));
echo $x->getTimestamp(); // timestamp UTC yang benar
Gunakan nama zona IANA seperti Asia/Jakarta, Asia/Makassar, dan Asia/Jayapura, bukan offset tetap seperti +07:00. Untuk Indonesia hasilnya memang sama karena tidak ada daylight saving, tetapi nama zona tetap lebih aman bila aplikasi Anda kelak melayani negara lain. Pilih DateTimeImmutable dibanding DateTime agar objek tidak berubah diam-diam ketika dimodifikasi.
Laravel: APP_TIMEZONE dan Carbon
Di Laravel 11, zona waktu aplikasi diatur lewat APP_TIMEZONE di .env dan dibaca oleh config/app.php ('timezone' => env('APP_TIMEZONE', 'UTC')). Di Laravel 10, nilainya ditulis langsung di config/app.php. Rekomendasinya: biarkan tetap UTC.
Banyak developer mengubahnya menjadi Asia/Jakarta agar jam di database "terlihat benar". Ini bekerja selama semua penggunanya di WIB dan tidak ada sistem lain yang membaca database. Begitu ada integrasi dengan payment gateway, aplikasi mobile, atau pengguna di WITA, masalah mulai muncul. Mengubah zona waktu aplikasi di tengah jalan juga berbahaya, karena data lama dan baru tersimpan dengan acuan berbeda tanpa penanda.
Carbon, library tanggal yang dipakai Laravel, memudahkan konversi:
use Illuminate\Support\Carbon;
$now = now(); // zona APP_TIMEZONE (UTC)
$wib = now()->setTimezone('Asia/Jakarta');
echo $wib->translatedFormat('l, d F Y H:i'); // tampil dalam WIB
$order->created_at->timezone('Asia/Jakarta')->format('d/m/Y H:i');
// dari timestamp: sebutkan zona secara eksplisit
$c = Carbon::createFromTimestamp(1700000000, 'UTC');
echo $c->timestamp; // 1700000000
Sebaiknya selalu sebutkan zona saat membuat Carbon dari timestamp, karena perilaku default berubah antarversi (Carbon 3 memakai UTC sebagai default untuk createFromTimestamp). Untuk menampilkan waktu sesuai zona pengguna, simpan zona tiap pengguna di kolom seperti users.timezone, lalu gunakan nilai itu saat format.
Saat model Eloquent diubah menjadi JSON, Laravel 7 ke atas menserialisasi tanggal ke ISO 8601 dalam UTC, misalnya "2023-11-14T22:13:20.000000Z". Format ini aman diparse oleh JavaScript.
MySQL: TIMESTAMP vs DATETIME
Kedua tipe kolom ini terlihat mirip tetapi berperilaku berbeda.
Berlangganan gratis. Artikel & tutorial coding terbaru langsung ke email kamu. Tanpa spam.
| Aspek | TIMESTAMP | DATETIME |
|---|---|---|
| Rentang | 1970-01-01 00:00:01 s.d. 2038-01-19 03:14:07 UTC | 1000-01-01 s.d. 9999-12-31 |
| Konversi zona | Ya: dikonversi dari zona sesi ke UTC saat simpan, dan kembali saat baca | Tidak: disimpan apa adanya |
Dipengaruhi time_zone sesi? | Ya | Tidak |
| Cocok untuk | Waktu kejadian (created_at) | Jadwal, tanggal lahir, waktu di masa depan jauh |
Konsekuensinya, kolom TIMESTAMP yang sama bisa tampil berbeda bila Anda membukanya dari phpMyAdmin (zona sesi server, misalnya SYSTEM = WIB) dan dari Laravel (zona sesi lain). Data tidak rusak; hanya cara tampilnya yang ikut zona sesi. Kolom DATETIME tidak tahu zona sama sekali, jadi Anda harus disiplin menentukan acuannya, idealnya UTC.
-- cek zona waktu server dan sesi
SELECT @@global.time_zone, @@session.time_zone, NOW(), UTC_TIMESTAMP();
-- set sesi ke UTC
SET time_zone = '+00:00';
Di Laravel Anda bisa memaksa zona sesi MySQL dengan menambahkan kunci timezone pada koneksi di config/database.php:
'mysql' => [
// ...
'timezone' => '+00:00',
],
Batas tahun 2038 pada TIMESTAMP juga perlu diperhatikan untuk data seperti masa berlaku langganan jangka panjang. Untuk tanggal jauh di masa depan, gunakan DATETIME. Jika Anda masih baru dengan tipe data MySQL, lihat juga panduan belajar MySQL untuk pemula.
JavaScript: Date dan Intl
Objek Date di JavaScript menyimpan satu angka, yaitu milidetik sejak epoch dalam UTC. Zona waktu baru berperan saat Anda membaca atau memformatnya. Metode seperti getHours() memakai zona browser, sedangkan getUTCHours() dan toISOString() memakai UTC.
const d = new Date('2023-11-14T22:13:20Z');
d.toISOString(); // "2023-11-14T22:13:20.000Z"
d.getHours(); // 5 bila browser di WIB
new Intl.DateTimeFormat('id-ID', {
dateStyle: 'full',
timeStyle: 'short',
timeZone: 'Asia/Jakarta',
}).format(d); // "Rabu, 15 November 2023 pukul 05.13"
Intl.DateTimeFormat().resolvedOptions().timeZone; // zona browser
Hati-hati dengan cara parsing string:
new Date('2024-08-17')(hanya tanggal) diperlakukan sebagai UTC tengah malam, sehingga di WIB tampil pukul 07:00.new Date('2024-08-17T10:00:00')(tanggal + jam tanpa zona) diperlakukan sebagai waktu lokal browser.- String dengan spasi seperti
'2024-08-17 10:00:00'bukan format ISO resmi, dan hasil parsing-nya tidak dijamin konsisten di semua browser.
Karena itu, selalu kirim waktu dari API dengan penanda zona (Z atau +07:00) atau sebagai angka timestamp.
Bug "selisih 7 jam" dan penyebabnya
Jika jam di aplikasi Anda selalu meleset tepat 7 jam (atau 8/9 jam di WITA/WIT), hampir pasti ada satu lapisan yang menganggap waktu sebagai UTC sementara lapisan lain menganggapnya WIB. Penyebab yang paling sering:
- Waktu UTC ditampilkan tanpa konversi. Database menyimpan 03:00 UTC, Blade langsung menampilkan
$order->created_at, pengguna melihat 03:00 padahal di Jakarta sudah 10:00. Solusi: konversi dengan->timezone('Asia/Jakarta')saat tampil. - Konversi ganda. Server mengubah ke WIB, lalu JavaScript menambah 7 jam lagi. Solusi: tentukan satu tempat konversi saja.
- String tanpa zona. Backend mengirim
"2024-08-17 03:00:00"(UTC) tanpa penanda, lalu browser membacanya sebagai waktu lokal. Solusi: kirim ISO 8601 denganZ. - Zona aplikasi dan zona MySQL berbeda. Laravel memakai UTC, tetapi query mentah memakai
NOW()yang mengikuti zona sesi MySQL (WIB). Solusi: samakan zona sesi, atau gunakannow()dari Laravel sebagai parameter. - Cron dan jadwal. Scheduler Laravel memakai zona aplikasi. Tugas yang Anda kira jalan pukul 08:00 WIB ternyata jalan pukul 08:00 UTC (15:00 WIB). Solusi: pakai
->timezone('Asia/Jakarta')pada jadwal.
// routes/console.php (Laravel 11)
Schedule::command('report:daily')
->dailyAt('08:00')
->timezone('Asia/Jakarta');
Ekspresi cron yang dipakai scheduler juga bisa Anda periksa artinya lewat Cron Explainer, tetapi ingat bahwa cron sistem di server mengikuti zona waktu server, bukan APP_TIMEZONE.
Kesalahan umum lainnya
- Membandingkan timestamp detik dengan milidetik.
- Menyimpan tanggal sebagai
VARCHARdengan formatd/m/Ysehingga sorting dan filter rusak. - Mengubah
APP_TIMEZONEdi aplikasi yang sudah berisi data tanpa migrasi data. - Menghitung "hari ini" di server UTC padahal yang dimaksud adalah hari ini di WIB. Gunakan
now('Asia/Jakarta')->startOfDay(), lalu ubah ke UTC untuk query.
Checklist
APP_TIMEZONE=UTC, server dan sesi MySQL juga UTC.- API mengirim ISO 8601 dengan
Zatau Unix timestamp, dan satuannya jelas. - Konversi ke WIB/WITA/WIT hanya di lapisan tampilan, di satu tempat.
TIMESTAMPuntuk waktu kejadian,DATETIMEuntuk tanggal jauh di masa depan.- Jadwal cron diberi zona waktu eksplisit.
FAQ
Apakah Unix timestamp terpengaruh zona waktu?
Tidak. Pada momen yang sama, time() di Jakarta dan di London menghasilkan angka yang sama. Zona waktu hanya berpengaruh saat timestamp diubah menjadi tanggal dan jam yang bisa dibaca.
Bolehkah APP_TIMEZONE diisi Asia/Jakarta?
Boleh dan akan bekerja untuk aplikasi kecil yang semua penggunanya di WIB. Namun UTC tetap pilihan yang lebih aman untuk jangka panjang. Yang paling penting adalah konsisten dan tidak mengubahnya setelah data terkumpul.
Bagaimana dengan masalah tahun 2038?
Sistem yang menyimpan timestamp sebagai integer 32-bit bertanda akan overflow pada 19 Januari 2038. PHP di sistem 64-bit aman, tetapi kolom MySQL TIMESTAMP masih memiliki batas tersebut. Untuk tanggal setelah 2038, gunakan DATETIME atau BIGINT.
Mengapa tanggal di JavaScript mundur satu hari?
Biasanya karena string tanggal saja diparse sebagai UTC lalu ditampilkan di zona dengan offset negatif. Di WIB efeknya justru jam bergeser ke 07:00. Untuk tanggal murni tanpa jam, simpan dan tampilkan sebagai string YYYY-MM-DD tanpa mengubahnya ke Date.