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:

BagianArtiProperti DateInterval
PPenanda awal bagian periode (wajib di posisi pertama)—
YJumlah tahun$y
M sebelum TJumlah bulan$m
WJumlah minggu$d (dikonversi: 1 minggu = 7 hari)
DJumlah hari$d
TPenanda awal bagian waktu—
HJumlah jam$h
M setelah TJumlah menit$i
SJumlah detik$s

Mari kita uji pembacaan spesifikasi tersebut:

php
<?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:

text
Tahun: 1, Bulan: 2, Hari: 3
Jam: 2, Menit: 30, Detik: 45
Dua minggu tersimpan sebagai 14 hari

Mari kita telaah baris-baris kode di atas:

  1. Baris 2: spesifikasi P1Y2M3D dibaca sebagai satu tahun, dua bulan, tiga hari; nilainya tersebar ke properti $y, $m, dan $d.
  2. Baris 5: spesifikasi PT2H30M45S memakai T karena seluruh komposisinya adalah satuan waktu; angka menit 30 tersimpan di properti $i, bukan $m.
  3. Baris 8: P2W tidak memiliki properti minggu tersendiri — PHP langsung mengonversinya menjadi 14 di 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():

PenandaArti
%y, %m, %dJumlah tahun, bulan, hari (komponen kalender)
%h, %i, %sJumlah jam, menit, detik
%aTotal 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
<?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:

text
1 tahun 4 bulan 10 hari
5 jam 30 menit
Total hari: 495

Mari kita amati hasilnya:

  1. Baris 7: pola %y %m %d menguraikan selisih menjadi 1 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.
  2. Baris 8: komponen jam tersisa 5 jam 30 menit karena barang dipinjam pukul 09:00 dan dikembalikan pukul 14:30.
  3. Baris 9: properti $days menyimpan total hari penuh tanpa penguraian kalender, yaitu 495 — angka yang tepat untuk menghitung biaya sewa harian.
php
<?php
$sintetis = new DateInterval("P3D");

var_dump($sintetis->days);
echo $sintetis->format("%a") . "\n";

Output yang Dihasilkan:

text
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
<?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:

text
Sisa waktu: 45 hari 9 jam
Flag invert: 0
Dihitung dari batas: -45 hari
Flag invert: 1

Perhatikan dua arah perhitungan di atas:

  1. Baris 5-7: $hariIni->diff($batasWaktu) menghitung jarak ke depan menuju 1 Maret pukul 17:00, yaitu 45 hari 9 jam, dengan invert bernilai 0.
  2. Baris 9-11: urutan dibalik sehingga perhitungan menjadi mundur; komponen hari tetap 45, invert berubah menjadi 1, dan penanda %R kini 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
<?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:

text
Jatuh tempo: 2026-02-25 08:00:00
Perkiraan kirim: 2026-01-01 08:00:00
Objek asli: 2026-01-15 08:00:00

Tiga baris keluaran di atas menceritakan tiga hal:

  1. Baris 4 (add dengan P1M10D): tanggal 15 Januari maju satu bulan ke 15 Februari, lalu sepuluh hari lagi ke 2026-02-25.
  2. Baris 7 (sub dengan P2W): tanggal mundur dua minggu ke 2026-01-01 — minggu memang tersimpan sebagai hari, namun hasil pergeserannya tetap tepat.
  3. Baris 10: objek $pesanan tidak berubah karena DateTimeImmutable selalu mengembalikan objek baru; pada class DateTime yang mutabel, kedua metode justru mengubah objek di tempat.

Aritmetika bulanan menyimpan satu kejutan yang wajib Anda ketahui. Perhatikan contoh berikut:

php
<?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:

text
2026-03-03

Tanggal 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
<?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:

text
Hari: 1
2026-01-18 12:00:00

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

Referensi #