Unix Timestamp & Zona Waktu di PHP dan JavaScript

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 seb...

Unix Timestamp & Zona Waktu di PHP dan JavaScript
Iklan

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.

Iklan

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 / sistemSatuanContoh
PHP time()Detik1700000000 (10 digit)
JavaScript Date.now()Milidetik1700000000000 (13 digit)
MySQL UNIX_TIMESTAMP()Detik1700000000
Payload JWT (exp, iat)Detik1700000000

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.

Iklan
// 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:

  1. Server, database, dan aplikasi bekerja dalam UTC.
  2. Data waktu dikirim lewat API dalam format ISO 8601 dengan penanda zona, misalnya 2023-11-14T22:13:20Z, atau sebagai Unix timestamp.
  3. 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.

Iklan

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.

Iklan

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.

Dapatkan Update Terbaru

Berlangganan gratis. Artikel & tutorial coding terbaru langsung ke email kamu. Tanpa spam.

AspekTIMESTAMPDATETIME
Rentang1970-01-01 00:00:01 s.d. 2038-01-19 03:14:07 UTC1000-01-01 s.d. 9999-12-31
Konversi zonaYa: dikonversi dari zona sesi ke UTC saat simpan, dan kembali saat bacaTidak: disimpan apa adanya
Dipengaruhi time_zone sesi?YaTidak
Cocok untukWaktu 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:

Iklan
'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.

Iklan

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:

  1. 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.
  2. Konversi ganda. Server mengubah ke WIB, lalu JavaScript menambah 7 jam lagi. Solusi: tentukan satu tempat konversi saja.
  3. String tanpa zona. Backend mengirim "2024-08-17 03:00:00" (UTC) tanpa penanda, lalu browser membacanya sebagai waktu lokal. Solusi: kirim ISO 8601 dengan Z.
  4. 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 gunakan now() dari Laravel sebagai parameter.
  5. 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 VARCHAR dengan format d/m/Y sehingga sorting dan filter rusak.
  • Mengubah APP_TIMEZONE di 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 Z atau Unix timestamp, dan satuannya jelas.
  • Konversi ke WIB/WITA/WIT hanya di lapisan tampilan, di satu tempat.
  • TIMESTAMP untuk waktu kejadian, DATETIME untuk 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.

Iklan

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.

Iklan
unix timestamp zona waktu php timezone laravel app_timezone timestamp vs datetime mysql selisih 7 jam wib javascript date
Rekomendasi

Hosting Cepat untuk Website & Laravel

Butuh hosting yang ngebut dan stabil untuk deploy website atau aplikasi Laravel-mu? Ini rekomendasi yang saya pakai.

Lihat Rekomendasi Hosting
Yudhi
Ditulis oleh

Yudhi

Web Developer

Web developer yang sehari-hari berkutat dengan PHP, Laravel, JavaScript, dan MySQL. Terbiasa membangun aplikasi web dari nol โ€” merancang database, menulis fitur, memburu bug, hingga deploy ke server โ€” lalu menuangkan solusi dan tutorialnya di DhieCoderWeb agar lebih mudah diikuti developer lain.

Bagikan artikel
Kembali

Komentar (0)

Punya pertanyaan atau tambahan? Tulis di bawah โ€” tak perlu login.

Membalas komentarโ€ฆ batal

Belum ada komentar. Jadilah yang pertama!

๐Ÿš€ Partner Recommendation

Butuh Source Code & Aplikasi Premium?

Download aplikasi Laravel, POS, Sekolah, Klinik, ERP, dan source code siap pakai di GudangCode.

GudangCode
  • โœ” Source Code Premium
  • โœ” Sistem Siap Pakai
  • โœ” Lifetime Update
  • โœ” Membership Lifetime
  • โœ” Update Aplikasi Harian
Daftar Membership โ†’
Iklan
Iklan