Banyak kebutuhan bisnis berbicara tentang durasi, bukan momen: masa berlangganan selama satu tahun, tenggat pembayaran tujuh hari setelah tagihan terbit, atau denda keterlambatan yang dihitung per hari. Chapter Date/Time Objects
sudah memperlihatkan pratinjau singkat metode diff() yang mengembalikan objek DateInterval; sekarang kita bedah kelas tersebut secara utuh. DateInterval merepresentasikan durasi sebagai kumpulan komponen — tahun, bulan, hari, jam, menit, detik — terpisah dari tanggal tertentu mana pun. Pada chapter ini kita pelajari cara membaca spesifikasi interval seperti P1Y2M3D, mengukur selisih dua waktu dengan diff(), menafsirkan arah selisih lewat flag invert, lalu memakai interval untuk menggeser tanggal dengan add() dan sub().
Representasi Durasi dengan DateInterval #
Objek DateInterval dapat dibayangkan sebagai kartu catatan berisi enam angka: jumlah tahun, bulan, hari, jam, menit, dan detik. Kelas ini tidak menyimpan tanggal maupun zona waktu; kelas ini hanya menjawab pertanyaan “seberapa lama”. Dua cara utama mendapatkan objeknya adalah membuat langsung dari string spesifikasi lewat constructor, atau menerima hasil perhitungan diff() antara dua objek waktu. Kita mulai dari cara pertama.
Membaca Spesifikasi Interval #
Constructor DateInterval menerima satu argumen string berformat menyerupai ISO 8601. String selalu diawali huruf P (period) yang menandai bagian tanggal, diikuti pasangan angka-huruf untuk tiap komponen. Huruf T (time) memisahkan bagian tanggal dari bagian jam. Tabel berikut merinci seluruh huruf yang dikenali:
| Bagian | Arti | Properti DateInterval |
|---|---|---|
P | Penanda awal bagian periode (wajib di posisi pertama) | — |
Y | Jumlah tahun | $y |
M sebelum T | Jumlah bulan | $m |
W | Jumlah minggu | $d (dikonversi: 1 minggu = 7 hari) |
D | Jumlah hari | $d |
T | Penanda awal bagian waktu | — |
H | Jumlah jam | $h |
M setelah T | Jumlah menit | $i |
S | Jumlah detik | $s |
Mari kita uji pembacaan spesifikasi tersebut:
<?php
$tahunan = new DateInterval("P1Y2M3D");
echo "Tahun: {$tahunan->y}, Bulan: {$tahunan->m}, Hari: {$tahunan->d}\n";
$waktu = new DateInterval("PT2H30M45S");
echo "Jam: {$waktu->h}, Menit: {$waktu->i}, Detik: {$waktu->s}\n";
$mingguan = new DateInterval("P2W");
echo "Dua minggu tersimpan sebagai {$mingguan->d} hari\n";Output yang Dihasilkan:
Tahun: 1, Bulan: 2, Hari: 3
Jam: 2, Menit: 30, Detik: 45
Dua minggu tersimpan sebagai 14 hariMari kita telaah baris-baris kode di atas:
- Baris 2: spesifikasi
P1Y2M3Ddibaca sebagai satu tahun, dua bulan, tiga hari; nilainya tersebar ke properti$y,$m, dan$d. - Baris 5: spesifikasi
PT2H30M45SmemakaiTkarena seluruh komposisinya adalah satuan waktu; angka menit30tersimpan di properti$i, bukan$m. - Baris 8:
P2Wtidak memiliki properti minggu tersendiri — PHP langsung mengonversinya menjadi14di properti$d.
Mengukur Selisih dengan diff() #
Metode diff() dipanggil pada satu objek waktu, menerima objek waktu lain sebagai argumen, lalu mengembalikan selisih keduanya sebagai DateInterval. Hasil diff() membaca jarak secara kalender — berapa tahun, bulan, dan hari — bukan sekadar pembagian total detik. Untuk membaca hasilnya, DateInterval::format() memakai penanda persen yang berbeda dari pola DateTime::format():
| Penanda | Arti |
|---|---|
%y, %m, %d | Jumlah tahun, bulan, hari (komponen kalender) |
%h, %i, %s | Jumlah jam, menit, detik |
%a | Total hari penuh sejak titik awal |
%R / %r | %R mencetak tanda + atau -; %r hanya mencetak - bila negatif |
Berikut skenario masa peminjaman barang yang melintasi dua tahun:
<?php
$peminjaman = new DateTimeImmutable("2024-11-10 09:00:00", new DateTimeZone("Asia/Jakarta"));
$pengembalian = new DateTimeImmutable("2026-03-20 14:30:00", new DateTimeZone("Asia/Jakarta"));
$selisih = $peminjaman->diff($pengembalian);
echo $selisih->format("%y tahun %m bulan %d hari") . "\n";
echo $selisih->format("%h jam %i menit") . "\n";
echo "Total hari: {$selisih->days}\n";Output yang Dihasilkan:
1 tahun 4 bulan 10 hari
5 jam 30 menit
Total hari: 495Mari kita amati hasilnya:
- Baris 7: pola
%y %m %dmenguraikan selisih menjadi1 tahun 4 bulan 10 hari— dari 10 November 2024 ke 10 November 2025 genap satu tahun, ditambah empat bulan ke 10 Maret 2026, ditambah sepuluh hari ke 20 Maret. - Baris 8: komponen jam tersisa
5 jam 30 menitkarena barang dipinjam pukul09:00dan dikembalikan pukul14:30. - Baris 9: properti
$daysmenyimpan total hari penuh tanpa penguraian kalender, yaitu495— angka yang tepat untuk menghitung biaya sewa harian.
<?php
$sintetis = new DateInterval("P3D");
var_dump($sintetis->days);
echo $sintetis->format("%a") . "\n";Output yang Dihasilkan:
bool(false)
(unknown)Untuk interval buatan, tidak ada “total hari” yang bisa dihitung tanpa titik awal — durasi satu bulan bisa berarti 28 hingga 31 hari tergantung bulan mana yang menjadi acuan. Kaitkan interval dengan sebuah tanggal lewat add() bila Anda membutuhkan angka hari yang pasti.
Arah Selisih dan Flag invert #
diff() selalu menghasilkan komponen bernilai positif; arah selisihnya ditandai terpisah oleh properti $invert. Ketika argumen diff() lebih akhir daripada objek pemanggil, invert bernilai 0. Ketika argumen justru lebih awal, seluruh komponen tetap positif tetapi invert bernilai 1 — sinyal bahwa selisih tersebut menghitung mundur. Pola ini umum dipakai untuk hitung mundur menuju tenggat:
<?php
$hariIni = new DateTimeImmutable("2026-01-15 08:00:00", new DateTimeZone("Asia/Jakarta"));
$batasWaktu = new DateTimeImmutable("2026-03-01 17:00:00", new DateTimeZone("Asia/Jakarta"));
$sisa = $hariIni->diff($batasWaktu);
echo "Sisa waktu: " . $sisa->format("%a hari %h jam") . "\n";
echo "Flag invert: {$sisa->invert}\n";
$dariBatas = $batasWaktu->diff($hariIni);
echo "Dihitung dari batas: " . $dariBatas->format("%R%a hari") . "\n";
echo "Flag invert: {$dariBatas->invert}\n";Output yang Dihasilkan:
Sisa waktu: 45 hari 9 jam
Flag invert: 0
Dihitung dari batas: -45 hari
Flag invert: 1Perhatikan dua arah perhitungan di atas:
- Baris 5-7:
$hariIni->diff($batasWaktu)menghitung jarak ke depan menuju 1 Maret pukul 17:00, yaitu45 hari 9 jam, denganinvertbernilai0. - Baris 9-11: urutan dibalik sehingga perhitungan menjadi mundur; komponen hari tetap
45,invertberubah menjadi1, dan penanda%Rkini mencetak tanda minus (-45 hari). Jangan menguji$selisih->days < 0— angkanya tidak pernah negatif; periksalah$selisih->invert === 1.
Menambah dan Mengurangi Waktu dengan add dan sub #
Spesifikasi interval menjadi bahan baku metode add() dan sub(). Pada DateTimeImmutable, kedua metode mengembalikan objek baru sehingga objek asli tetap utuh — perilaku yang sudah kita bahas pada Chapter DateTimeImmutable
:
<?php
$pesanan = new DateTimeImmutable("2026-01-15 08:00:00", new DateTimeZone("Asia/Jakarta"));
$jatuhTempo = $pesanan->add(new DateInterval("P1M10D"));
echo "Jatuh tempo: " . $jatuhTempo->format("Y-m-d H:i:s") . "\n";
$perkiraanKirim = $pesanan->sub(new DateInterval("P2W"));
echo "Perkiraan kirim: " . $perkiraanKirim->format("Y-m-d H:i:s") . "\n";
echo "Objek asli: " . $pesanan->format("Y-m-d H:i:s") . "\n";Output yang Dihasilkan:
Jatuh tempo: 2026-02-25 08:00:00
Perkiraan kirim: 2026-01-01 08:00:00
Objek asli: 2026-01-15 08:00:00Tiga baris keluaran di atas menceritakan tiga hal:
- Baris 4 (
adddenganP1M10D): tanggal 15 Januari maju satu bulan ke 15 Februari, lalu sepuluh hari lagi ke2026-02-25. - Baris 7 (
subdenganP2W): tanggal mundur dua minggu ke2026-01-01— minggu memang tersimpan sebagai hari, namun hasil pergeserannya tetap tepat. - Baris 10: objek
$pesanantidak berubah karenaDateTimeImmutableselalu mengembalikan objek baru; pada classDateTimeyang mutabel, kedua metode justru mengubah objek di tempat.
Aritmetika bulanan menyimpan satu kejutan yang wajib Anda ketahui. Perhatikan contoh berikut:
<?php
$akhirJanuari = new DateTimeImmutable("2026-01-31 08:00:00", new DateTimeZone("Asia/Jakarta"));
$satuBulanKemudian = $akhirJanuari->add(new DateInterval("P1M"));
echo $satuBulanKemudian->format("Y-m-d") . "\n";Output yang Dihasilkan:
2026-03-03Tanggal 31 Januari ditambah satu bulan menghasilkan 2026-03-03, bukan 28 Februari. PHP memajukan komponen bulan menjadi Februari sambil mempertahankan angka 31; karena Februari 2026 hanya memiliki 28 hari, kelebihan tiga hari meluap ke bulan berikutnya. PHP tidak menjepit hasil ke akhir bulan secara otomatis — ketika logika bisnis Anda bermakna “tanggal yang sama bulan depan, atau akhir bulan bila tidak ada”, validasi tersebut harus Anda tulis sendiri, misalnya dengan membandingkan bulan hasil penjumlahan terhadap bulan asal.
Membuat Interval dari Teks Relatif #
Constructor dengan spesifikasi P...T... terasa kaku ketika durasi datang dari luar program — masukan pengguna atau berkas konfigurasi. Metode statis DateInterval::createFromDateString() menerima teks relatif berbahasa Inggris dengan format yang sama seperti yang dikenali parser tanggal pada Chapter Parsing dan Formatting Dates
:
<?php
$sehari = DateInterval::createFromDateString("1 day");
echo "Hari: {$sehari->d}\n";
$tenggat = new DateTimeImmutable("2026-01-15 08:00:00", new DateTimeZone("Asia/Jakarta"));
$digeser = $tenggat->add(DateInterval::createFromDateString("3 days 4 hours"));
echo $digeser->format("Y-m-d H:i:s") . "\n";Output yang Dihasilkan:
Hari: 1
2026-01-18 12:00:00Baris 2 membaca teks 1 day menjadi interval satu hari di properti $d. Baris 5-8 membuktikan hasil parser langsung kompatibel dengan add(): teks 3 days 4 hours menggeser tenggat dari 15 Januari pukul 08:00 menjadi 2026-01-18 12:00:00. Bentuk ini paling berguna sebagai jembatan antara manusia yang menulis “2 weeks” dan API DateInterval yang mengharapkan spesifikasi formal.
Ringkasan #
DateInterval melengkapi kosakata waktu kita: spesifikasi P1Y2M3D untuk mendeklarasikan durasi, diff() untuk mengukur selisih kalender antara dua waktu, flag invert untuk membaca arah, properti $days untuk total hari, serta add()/sub() untuk menggeser tanggal — lengkap dengan kejutan luapan pada penjumlahan akhir bulan. Satu bahan terakhir untuk menguasai aritmetika waktu adalah cara mengulang satu interval di sepanjang rentang; itulah pekerjaan DatePeriod pada chapter berikutnya
.