Himanshu SharmaSoftware engineer
Let’s talk

PHP · Lesson 31 of 54

Dates and times

Work with dates in PHP using date(), DateTimeImmutable, formats, time zones, adding and subtracting intervals, and calculating the difference between two dates.

14 minStrings, dates and patternsUpdated 22 October 2026

Awaiting technical review. This lesson is readable but not yet listed in search engines.

By the end of this lessonCreate, format, compare and calculate with dates and times, including time zones.

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() and strtotime(), which work with Unix timestamps, and
  • the classes DateTimeImmutable, DateTimeZone and DateInterval, 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
<?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 2024

Without a second argument date() formats the current time; with one it formats that timestamp:

php
<?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";
result
2024-06-03 14:30
Mon 03/06/2024
Monday 3rd June 2024, 2:30 pm

The 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
<?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";
result
Asia/Kolkata
09:00 UTC
14:30 IST
10:00 BST

A 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
<?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";
result
03 Jun 2024, 14:30
2024-06-03T14:30:00+05:30
1717405200

DATE_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
<?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());
result
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
<?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";
result
Ordered:   Mon 03 Jun
Delivery:  Sat 08 Jun
Return by: Mon 08 Jul
Reminder:  Fri 07 Jun

P30D 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
<?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";
result
bool(true)
Customer for 0 years, 9 months, 4 days
That is 278 days in total
9 months and 4 days

To list every date in a range, for example each day of a booking, use DatePeriod:

php
<?php
$period = new DatePeriod(new DateTimeImmutable('2024-06-03'), new DateInterval('P1D'), 3);
foreach ($period as $day) {
    echo $day->format('D d M') . "\n";
}
result
Mon 03 Jun
Tue 04 Jun
Wed 05 Jun
Thu 06 Jun

Dates 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 P1M or +1 month and 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
<?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";
result
Trial ends Sat 02 Mar 2024
Length: 31 days
Last day of next month: 29 Feb 2024

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

Check yourself

1. What does date('Y-m-d') return on 3 June 2024?
2. What is the main advantage of DateTimeImmutable over DateTime?
3. What does a Unix timestamp count?
4. Which format character gives the full name of the day, such as Monday?