Two ways to work with dates
Shops show delivery dates, blogs show when a post was published, subscriptions renew every month. PHP offers two styles for this:
- the functions
date(),time(),mktime()andstrtotime(), which work with Unix timestamps, and - the classes
DateTimeImmutable,DateTimeZoneandDateInterval, which are clearer and handle time zones and calendar arithmetic properly.
You will see both in existing code. For new code, prefer the classes.
Examples that print "now" give a different answer every time, so they are shown here without output. The rest use fixed dates so you can check the results.
Timestamps, time() and date()
A Unix timestamp is the number of seconds since 1 January 1970 at midnight UTC. time() returns the current one, and date() turns a timestamp into text using a format string:
<?php
echo time() . "\n"; // for example 1729584000
echo date('Y-m-d H:i:s') . "\n"; // the current date and time
echo date('l, j F Y') . "\n"; // for example Tuesday, 22 October 2024Without a second argument date() formats the current time; with one it formats that timestamp:
<?php
$timestamp = mktime(14, 30, 0, 6, 3, 2024); // 14:30 on 3 June 2024
echo date('Y-m-d H:i', $timestamp) . "\n";
echo date('D d/m/Y', $timestamp) . "\n";
echo date('l jS F Y, g:i a', $timestamp) . "\n";2024-06-03 14:30
Mon 03/06/2024
Monday 3rd June 2024, 2:30 pmThe most useful format characters:
| Character | Meaning | Example |
|---|---|---|
Y / y |
4-digit / 2-digit year | 2024 / 24 |
m / n |
month with / without leading zero | 06 / 6 |
d / j |
day of the month with / without zero | 03 / 3 |
F / M |
full / short month name | June / Jun |
l / D |
full / short day name | Monday / Mon |
H / G |
24-hour hour with / without zero | 14 |
h / g |
12-hour hour | 02 / 2 |
i / s |
minutes / seconds | 30 / 00 |
a / A |
am, pm / AM, PM | pm |
S |
English ordinal suffix | rd |
N |
day of the week, 1 (Monday) to 7 | 1 |
Time zones
The same moment is a different clock time in Delhi and in London. PHP uses the date.timezone setting unless you say otherwise. Set it for your application early, or pass a zone to each date object:
<?php
date_default_timezone_set('Asia/Kolkata');
echo date_default_timezone_get() . "\n";
$moment = new DateTimeImmutable('2024-06-03 09:00', new DateTimeZone('UTC'));
echo $moment->format('H:i T') . "\n";
echo $moment->setTimezone(new DateTimeZone('Asia/Kolkata'))->format('H:i T') . "\n";
echo $moment->setTimezone(new DateTimeZone('Europe/London'))->format('H:i T') . "\n";Asia/Kolkata
09:00 UTC
14:30 IST
10:00 BSTA good rule for web applications: store times in UTC (in the database, logs and APIs) and convert to the visitor's time zone only when you display them. Always use zone names such as Asia/Kolkata or America/New_York, not abbreviations or fixed offsets, so daylight-saving changes are handled for you.
DateTimeImmutable
DateTimeImmutable represents one point in time. Create it from almost any readable date string, then format it with the same characters as date():
<?php
$order = new DateTimeImmutable('2024-06-03 14:30', new DateTimeZone('Asia/Kolkata'));
echo $order->format('d M Y, H:i') . "\n";
echo $order->format(DATE_ATOM) . "\n";
echo $order->getTimestamp() . "\n";03 Jun 2024, 14:30
2024-06-03T14:30:00+05:30
1717405200DATE_ATOM is the ISO 8601 format used by JSON APIs and HTML <time> elements. new DateTimeImmutable() with no argument means "now".
When the input has a known format, such as a date typed as 03/06/2024, use createFromFormat() so that day and month are never confused:
<?php
$date = DateTimeImmutable::createFromFormat('!d/m/Y', '03/06/2024');
echo $date->format('Y-m-d') . "\n";
$bad = DateTimeImmutable::createFromFormat('!d/m/Y', '31/02/2024');
echo $bad->format('Y-m-d') . "\n";
var_dump(DateTimeImmutable::getLastErrors());2024-06-03
2024-03-02
array(4) {
["warning_count"]=>
int(1)
["warnings"]=>
array(1) {
[10]=>
string(27) "The parsed date was invalid"
}
["error_count"]=>
int(0)
["errors"]=>
array(0) {
}
}The ! resets the time to midnight. Note what happened to 31 February: PHP rolled it over to 2 March and reported a warning. When validating user input, check getLastErrors() (or use checkdate()) and reject dates with warnings.
Adding and subtracting
modify() takes phrases such as +3 days or next monday, and add() / sub() take a DateInterval. Because the object is immutable, each call returns a new date and the original stays the same:
<?php
$ordered = new DateTimeImmutable('2024-06-03');
$delivery = $ordered->modify('+5 days');
$returnBy = $delivery->add(new DateInterval('P30D'));
$reminder = $ordered->modify('next friday');
echo "Ordered: " . $ordered->format('D d M') . "\n";
echo "Delivery: " . $delivery->format('D d M') . "\n";
echo "Return by: " . $returnBy->format('D d M') . "\n";
echo "Reminder: " . $reminder->format('D d M') . "\n";Ordered: Mon 03 Jun
Delivery: Sat 08 Jun
Return by: Mon 08 Jul
Reminder: Fri 07 JunP30D is ISO 8601 interval notation: P for "period", then 30 D(ays). P1M is one month, P1Y2M one year and two months, and PT2H two hours (T separates the time part).
The older DateTime class has the same methods, but modify() changes the object itself. If two parts of your code share one DateTime and one of them adds five days, the other one moves too. DateTimeImmutable makes that impossible.
Comparing and measuring
Date objects can be compared with <, > and ==. diff() returns a DateInterval describing the gap:
<?php
$signup = new DateTimeImmutable('2023-08-30');
$today = new DateTimeImmutable('2024-06-03');
var_dump($signup < $today);
$gap = $signup->diff($today);
echo "Customer for {$gap->y} years, {$gap->m} months, {$gap->d} days\n";
echo "That is {$gap->days} days in total\n";
echo $gap->format('%m months and %d days') . "\n";bool(true)
Customer for 0 years, 9 months, 4 days
That is 278 days in total
9 months and 4 daysTo list every date in a range, for example each day of a booking, use DatePeriod:
<?php
$period = new DatePeriod(new DateTimeImmutable('2024-06-03'), new DateInterval('P1D'), 3);
foreach ($period as $day) {
echo $day->format('D d M') . "\n";
}Mon 03 Jun
Tue 04 Jun
Wed 05 Jun
Thu 06 JunDates in the database
MySQL stores dates in DATE, DATETIME and TIMESTAMP columns as text in YYYY-MM-DD HH:MM:SS form, the MySQL lesson on dates covers them. Format with 'Y-m-d H:i:s' before saving, and create a DateTimeImmutable from the value you read back.
Common mistakes
- Not setting a time zone, so dates are off by hours between your machine and the server.
- Parsing user dates with strtotime() or new DateTime(), which can read 03/06/2024 as March 6th (the American order). Use
createFromFormat(). - Changing a shared DateTime with modify() and moving every copy. Use
DateTimeImmutable. - Calculating months by adding 30 days. Use
P1Mor+1 monthand let PHP handle the calendar.
Exercise
A free trial starts on 2024-01-31 and lasts one month. Print the trial end date, then print how many days the trial lasted. What do you notice about "one month" from 31 January?
Show one possible answer
<?php
$start = new DateTimeImmutable('2024-01-31');
$end = $start->modify('+1 month');
echo "Trial ends " . $end->format('D d M Y') . "\n";
echo "Length: " . $start->diff($end)->days . " days\n";
$endOfMonth = $start->modify('last day of next month');
echo "Last day of next month: " . $endOfMonth->format('d M Y') . "\n";Trial ends Sat 02 Mar 2024
Length: 31 days
Last day of next month: 29 Feb 2024There is no 31 February, so PHP rolls over into March. If you mean "the end of next month", say so with last day of next month, which correctly finds 29 February in a leap year.
Key points
- date('Y-m-d H:i') formats the current time; time() is the current Unix timestamp in seconds.
- DateTimeImmutable is the modern way to work with dates: create, format, modify and compare without side effects.
- Always set the time zone, for example new DateTimeZone('Asia/Kolkata'); store times in UTC and convert for display.
- diff() returns a DateInterval with the years, months and days between two dates.