Dates and Times
Dates appear everywhere in web applications: order dates, deadlines, "posted 3 hours ago", opening hours in different time zones. PHP has simple functions like date() for quick formatting and a full set of classes — DateTimeImmutable, DateInterval, DateTimeZone, DatePeriod — for real date arithmetic and time zones.
Timestamps and date()
A Unix timestamp is the number of seconds since 1 January 1970 UTC. time() returns the current one; date() formats a timestamp; mktime() and strtotime() create one:
<?php $launch = mktime(14, 30, 0, 3, 14, 2026); // hour, minute, second, month, day, year echo $launch, "\n"; echo date('Y-m-d H:i:s', $launch), "\n"; echo date('l, F j, Y', $launch), "\n"; echo date('D d/m/y g:i a', $launch), "\n"; echo date('Y-m-d', strtotime('2026-03-14 +10 days')), "\n"; echo time() > $launch ? "That's in the past\n" : "Still to come\n";
1773498600 2026-03-14 14:30:00 Saturday, March 14, 2026 Sat 14/03/26 2:30 pm 2026-03-24 That's in the past
| Character | Meaning | Example |
|---|---|---|
Y / y | Year, 4 / 2 digits | 2026 / 26 |
m / n | Month with / without leading zero | 03 / 3 |
d / j | Day with / without leading zero | 04 / 4 |
F / M | Month name, full / short | March / Mar |
l / D | Weekday, full / short | Saturday / Sat |
H / G | Hour, 24-hour | 14 |
h / g, a | Hour, 12-hour; am/pm | 02 / 2, pm |
i, s | Minutes, seconds | 30, 00 |
N | Day of week, 1 (Mon) to 7 (Sun) | 6 |
c | ISO 8601 date-time | 2026-03-14T14:30:00+00:00 |
Set the time zone
Every date function uses a default time zone. Set it explicitly — in php.ini (date.timezone = UTC) or at the start of your application — instead of relying on the server's setting. Many applications store all dates in UTC and convert to the user's zone only for display:
<?php date_default_timezone_set('UTC'); echo date_default_timezone_get(), "\n";
UTC
(The examples in this course run in UTC.)
DateTimeImmutable
The date classes understand calendars, leap years and time zones. Prefer DateTimeImmutable: its methods return a new object instead of changing the original (lesson 41 shows why that matters):
<?php $start = new DateTimeImmutable('2026-01-31 09:00'); echo $start->format('Y-m-d H:i'), "\n"; echo $start->modify('+10 days')->format('Y-m-d'), "\n"; echo $start->modify('+1 month')->format('Y-m-d'), "\n"; // Feb 31 overflows into March! echo $start->modify('last day of next month')->format('Y-m-d'), "\n"; echo $start->modify('next monday')->format('l Y-m-d'), "\n"; echo $start->setDate(2026, 12, 25)->setTime(18, 0)->format('D j M, H:i'), "\n"; echo $start->format('Y-m-d'), " (unchanged)\n";
2026-01-31 09:00 2026-02-10 2026-03-03 2026-02-28 Monday 2026-02-02 Fri 25 Dec, 18:00 2026-01-31 (unchanged)
Watch out for month arithmetic: "one month after 31 January" has no obvious answer, and PHP's rule (overflowing into March) is rarely what you want. Phrases like last day of next month say exactly what you mean.
Differences and intervals
diff() returns a DateInterval with the difference broken into years, months, days and so on; ->days is the total number of days:
<?php $born = new DateTimeImmutable('1990-10-09'); $today = new DateTimeImmutable('2026-10-08'); $age = $born->diff($today); echo "Age: {$age->y} years, {$age->m} months, {$age->d} days\n"; echo "Days alive: {$age->days}\n"; $due = new DateTimeImmutable('2026-12-24'); echo $today->diff($due)->format('%a days to go'), "\n"; echo $today->add(new DateInterval('P2W3D'))->format('Y-m-d'), "\n"; // 2 weeks, 3 days var_dump($today < $due);
Age: 35 years, 11 months, 29 days Days alive: 13148 77 days to go 2026-10-25 bool(true)
Date objects can be compared with <, > and ==. Interval specs like P2W3D follow ISO 8601: P, then Y, M, W, D, then T and H, M, S for time.
Parsing user dates
The constructor understands many formats, but some are ambiguous: is 03/04/2026 March 4 or April 3? When you know the format, use createFromFormat — and check that it really matched:
<?php function parseDate(string $input): ?DateTimeImmutable { $date = DateTimeImmutable::createFromFormat('!d/m/Y', $input); return $date && $date->format('d/m/Y') === $input ? $date : null; } foreach (['14/03/2026', '31/02/2026', '2026-03-14', '1/3/2026'] as $input) { $d = parseDate($input); echo str_pad($input, 12), $d ? $d->format('l j F Y') : 'invalid', "\n"; }
14/03/2026 Saturday 14 March 2026 31/02/2026 invalid 2026-03-14 invalid 1/3/2026 invalid
The ! resets unspecified fields (like the time) to zero. Comparing the re-formatted date with the input catches dates such as 31 February, which PHP would otherwise silently roll over to 3 March.
Time zones
A DateTimeZone attached to a date object handles offsets and daylight saving time automatically:
<?php $meeting = new DateTimeImmutable('2026-07-01 14:00', new DateTimeZone('Europe/London')); foreach (['America/New_York', 'Asia/Tokyo', 'Australia/Sydney', 'UTC'] as $zone) { $local = $meeting->setTimezone(new DateTimeZone($zone)); printf("%-17s %s (UTC%s)\n", $zone, $local->format('D H:i'), $local->format('P')); } echo $meeting->getTimestamp(), "\n";
America/New_York Wed 09:00 (UTC-04:00) Asia/Tokyo Wed 22:00 (UTC+09:00) Australia/Sydney Wed 23:00 (UTC+10:00) UTC Wed 13:00 (UTC+00:00) 1782910800
setTimezone keeps the same moment and changes how it's displayed. Store moments in UTC (or as timestamps), and convert to the user's time zone at the last moment.
Ranges of dates
DatePeriod iterates over dates at a regular interval — handy for calendars and reports:
<?php $period = new DatePeriod( new DateTimeImmutable('2026-03-02'), new DateInterval('P1W'), new DateTimeImmutable('2026-04-01'), ); foreach ($period as $monday) { echo $monday->format('D j M'), "\n"; }
Mon 2 Mar Mon 9 Mar Mon 16 Mar Mon 23 Mar Mon 30 Mar
"Time ago"
<?php function timeAgo(DateTimeImmutable $then, DateTimeImmutable $now): string { $seconds = $now->getTimestamp() - $then->getTimestamp(); return match (true) { $seconds < 60 => 'just now', $seconds < 3600 => intdiv($seconds, 60) . ' min ago', $seconds < 86400 => intdiv($seconds, 3600) . ' h ago', $seconds < 7 * 86400 => intdiv($seconds, 86400) . ' days ago', default => $then->format('j M Y'), }; } $now = new DateTimeImmutable('2026-10-08 12:00'); foreach (['2026-10-08 11:59:30', '2026-10-08 11:15', '2026-10-08 02:00', '2026-10-05', '2026-01-15'] as $t) { echo timeAgo(new DateTimeImmutable($t), $now), "\n"; }
just now 45 min ago 10 h ago 3 days ago 15 Jan 2026
Passing $now in, rather than reading the clock inside the function, makes it easy to test (lesson 61).
Try it yourself
Print the next five weekdays (skipping weekends) after a given date.
Fri 9 Oct Mon 12 Oct Tue 13 Oct Wed 14 Oct Thu 15 Oct