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
<?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";
Output
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
CharacterMeaningExample
Y / yYear, 4 / 2 digits2026 / 26
m / nMonth with / without leading zero03 / 3
d / jDay with / without leading zero04 / 4
F / MMonth name, full / shortMarch / Mar
l / DWeekday, full / shortSaturday / Sat
H / GHour, 24-hour14
h / g, aHour, 12-hour; am/pm02 / 2, pm
i, sMinutes, seconds30, 00
NDay of week, 1 (Mon) to 7 (Sun)6
cISO 8601 date-time2026-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
<?php

date_default_timezone_set('UTC');
echo date_default_timezone_get(), "\n";
Output
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
<?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";
Output
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
<?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);
Output
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
<?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";
}
Output
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
<?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";
Output
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
<?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";
}
Output
Mon 2 Mar
Mon 9 Mar
Mon 16 Mar
Mon 23 Mar
Mon 30 Mar

"Time ago"

PHP
<?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";
}
Output
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.

Try it yourself · main.php
Edit the code, copy it into main.php, then run it with php main.php (PHP 8.5). How to install PHP
Output
Fri 9 Oct
Mon 12 Oct
Tue 13 Oct
Wed 14 Oct
Thu 15 Oct