×
Author:
Website:
Page title:
URL:
Published:
Last revised:
Accessed:

Working with Dates and Times

Overview

The ability to store, retrieve, and output date and time information is an essential feature of many applications. It is also important that the underlying format used to store the data is platform-independent. This ensures that date and time information can be exchanged between applications, regardless of the hardware they are running on, or the system software running on that hardware.

The date and time related features in PHP are based on the ISO 8601 calendar, which extends the Gregorian calendar proleptically (backwards) to include dates prior to the introduction of the Gregorian calendar in 1582, the intention being to create a consistent dating system and simplify the identification of pre-Gregorian dates.

PHP stores date and time information that identifies a specific point in time as a 64-bit signed integer value. This value represents the time interval in seconds between that moment and the UNIX epoch, which occurred at 00:00:00 UTC (GMT) on January 1st, 1970. The value stored is effectively a timestamp. A negative value identifies a date and time that occurred before the UNIX epoch

The use of a 64-bit integer to store date and time information allows us to work with a range of dates that extends approximately 292 billion years either side of the Unix epoch. Note however that some legacy systems use a 32-bit integer to store a timestamp. These systems face potential problems, because a 32-bit signed integer can only support dates in the range December 13th 1901 to January 19th 2038.

PHP provides a number of date- and time-related classes, two of which - the DateTime class and the DateTimeImmutable class - implement the DateTimeInterface, which specifies the methods that both classes should implement. The interface also defines a number of constants that represent standard date formats, including the ISO 8601 extended format (see below). These constants are intended for use with date formatting functions and class methods.

The DateTime and DateTimeImmutable classes were introduced with PHP versions 5.2 and 5.5 respectively, so they have been around for a few years now. PHP also has a number of procedural date- and time-related functions such as date(), getdate(), mktime() and strtotime() that were introduced with PHP version 4. These older functions have not so far been deprecated and are still perfectly usable, although it is recommended to use the methods provided by the newer date- and time-related classes.

Note that DateTimeInterface cannot be implemented by a user-defined class directly. Developers must use one of either the DateTime or DateTimeImmutable classes to create date/time objects. Both of these classes implement the methods defined by DateTimeInterface, and use the same constants to specify standard date and time formats.

Use of the DateTimeImmutable class is recommended in preference to the DateTime class because, although both classes implement the same methods, objects created with DateTimeImmutable are immutable, meaning their properties cannot be changed by the methods they implement. A method such as DateTimeImmutable::modify() returns a new instance of DateTimeImmutable with a different timestamp, whereas DateTime::modify() alters the timestamp of the object on which it is called.

The functions and class methods provided by PHP for working with date and time information allow us to retrieve and store the current date and time, modify and format time and date information, work with different time zones, create timestamps, and calculate the time that has elapsed between two events. In this article, we'll be exploring how some of those functions and methods are used.

The way in which date and time data is actually presented to the end-user can vary, depending on factors such as language, locale, and the requirements of the application, and we'll be exploring the various ways in which dates and times can be formatted. First, however, we'll take a brief look at the ISO 8061 standard, which defines a universally accepted machine- and human-readable format for the exchange of date- and time-related data.

Among other things, this article will look at the DateTime and DateTimeImmutable classes and the procedural versions of their constructors, namely the date_create() and date_create_immutable() functions respectively, as well as the strtotime() function used to create a Unix timestamp. These methods and functions all accept a datetime string argument that is parsed for date and time information. The range of formats these methods and functions can understand is extensive. A detailed summary of the supported date and time formats can be found on the PHP Group website.

The ISO 8601 standard

The international standard for the exchange and communication of date- and time-related data is the ISO 8601 standard, first published (as ISO 8601:1988) in 1988 by the International Organisation for Standardization (ISO), who continue to maintain the standard. The most recent version of the standard (ISO 8601-1:2019), and the fourth to date, was published in 2019, although an amendment was published in 2022 in an attempt to clarify some technical issues, remove some ambiguities from definitions, and reintroduce the "24:00:00" format referring to the instantaneous time at the end of a calendar day.

The ISO 8601 date format uses an all-numeric, most-to-least-significant notation in which temporal terms appear in descending order of duration. Which is a fancy way of saying that year comes before month, month comes before day, and so on. The representation of date and time information is restricted to Arabic numerals and a limited set of characters that have special meaning within the standard, such as "-" and ":".

Each type of temporal value (year, month, day etc.) appearing in the representation of a specific date and time must have the same fixed number of digits, padded with leading zeros if required. Two formats are accepted. The basic format, which has a minimal number of separators, and the extended format, which includes additional separators to make the date-time representation more human-readable.

In the ISI 8601 extended format, a hyphen ("-") is used to separate date values (i.e. year, month, week and day), while a colon (":") is used to separate time values (hours, minutes and seconds). To illustrate the difference, here are the basic and extended representation for the date 31st May 2024:

20240531   (basic)
2024-05-31   (extended)

Any of the temporal values that make up the complete representation of a date and time may be omitted, providing the most-to-least-significant ordering is maintained, and providing the temporal values within the shortened representation are consecutive values in the full representation. In other words, "2024-05" is a valid (extended) representation that represents the month of May in the year 2024. The representation "2024-31" is invalid - It would be interpreted as an attempt to represent the 31st day of an unspecified month in the year 2024.

ISO 8601 specifies that a year must be represented by (at least) four digits "YYYY" - a requirement no doubt prompted by the infamous Y2K problem that caused such consternation among IT professionals in the period leading up to the start of the new millennium which occurred on 1st January 2000. Unless otherwise specified by an application, the four-digit format allows for the representation of any year between 0000 (1 BCE) and 9999 (9999 CE).

The full extended version of a calendar date takes the form "YYYY-MM-DD", where "YYYY" represents the four-digit year as we have already seen, "MM" represents the two-digit month, and "DD" represents the two-digit day. Note that the basic representation of a calendar date in the form "YYYYMM" which omits the day is not allowed because it could be confused with the truncated version of a specific date "YYMMDD", which is still widely used.

The ISO 8601 standard is based on the proleptic Gregorian calendar which, as we mentioned earlier, is also used by PHP. The Gregorian calendar, which is today used in most parts of the world, was introduced in October 1582 by Pope Gregory XIII as a replacement for the Julian calendar, principally to more accurately reflect the 365.242-day solar year (based on the time taken by the Earth to orbit the Sun) by adding an extra day to the year at regular four-year intervals. Years in which this occurs are called leap-years.

The proleptic Gregorian calendar is a backwards extension of the Gregorian calendar that accounts for dates prior to October 1582. There is inevitably some disparity between historical dates that were originally recorded prior to the adoption of the Gregorian calendar and their modern equivalent based on the proleptic Gregorian calendar. This is further complicated by the fact that many countries adopted the Gregorian calendar much later, and at different times. For example, Britain and America first adopted it in 1752, and Japan and China in 1872 and 1912 respectively.

Week numbers and ordinal dates

Many businesses use week numbers rather than calendar dates for planning activities because they provide a more convenient way to organise and track time. A year consists of either 52 or 53 full weeks, whereby week 01 is the week with the first Thursday of the year in it. Thus, if January 1st falls on a Monday, Tuesday, Wednesday or Thursday, it is in week 01. If it falls on a Friday, Saturday or Sunday, January 1st is in week 52 or 53 of the previous year.

ISO 8601 defines a standard week date representation for dates based on week numbers and weekdays. The year is represented by "YYYY" as for calendar dates. The week is represented by "Www", where "ww" is the two-digit week number in the range 01 - 53, and the day is represented by "D", a single digit in the range 1 - 7, with Monday being 1 and Sunday being 7. The following two examples represent the week number in which the date Friday 26th April 2024 falls, and the week number and day, respectively:

2024-W17
2024-W17-5

The ISO 8601 week-numbering year starts on the first Monday of week 01 and ends on the last Sunday before the next week-numbering year starts, which means that there are no gaps between one week-numbering year and the next. It also means that there can be up to three days in the first week of the week-numbering year that fall within the previous calendar year. There can also be up to three days in the last week-numbering week of the year that fall in the next calendar year.

If three days of the first week in the week-numbering year fall within the previous calendar year, they will be Monday, Tuesday and Wednesday. If three days of the last week in the week-numbering year fall within the next calendar year, they will be Friday, Saturday and Sunday. All Thursdays in a given week-numbering year will always be in the same calendar year.

ISO 8601 also defines a standard ordinal date, which represents a date using the year, as "YYYY", and the number of days that have elapsed since the beginning of that year, as "DDD", from 001 to 365 (or 366 in a leap year). The following examples represent basic and extended versions of the ordinal date on which Friday 26th April 2024 falls:

2024117
2024-117

Unlike JavaScript, PHP natively supports week numbers and ordinal dates through its date() function, as well as through its DateTime and DateTimeImmutable classes, which means that if we are developing an application that needs to be able to represent dates in either of these formats, we don't need to re-invent the wheel in order to accommodate such a requirement.

ISO 8601 time formats

ISO 8601 uses a 24-hour time system in which the basic format is "Thhmmss" and the extended format is "Thh:mm:ss". The order is important - the standard requires the same most-to-least-significant notation used for dates, whereby hours come before minutes, and minutes come before seconds.

The "T" at the beginning denotes a time, which is followed by three two-digit numbers, zero padded if necessary, representing hours (00 to 23), minutes (00 to 59) and seconds (00 to 59). None of these time values can be specified on its own. As a minimum, both hours and minutes must be used ("hhmm"), and seconds must be used together with hours and minutes ("hhmmss").

Note that a value of 24 may be used for the hour to represent midnight at the end of a calendar day (see below). A value for seconds of 60 is also allowed when it is necessary to represent a leap second. This is a one-second adjustment applied to UTC every other year or so to reconcile the precise time, as measured by atomic clocks, with the observed solar time, which can drift away from UTC slightly due to the gradual slowing of the Earth's rotation.

The "T" may be omitted in the extended version but this is only allowed in the basic version if there is no possibility of confusing times with dates. The beginning of a calendar day is expressed as "T000000" (basic) or "T00:00:00" (extended). The end of a calendar day is expressed as "T240000" or "T24:00:00". The end of one calendar day, and the beginning of the next calendar day, thus occur at the same instant in time.

The standard supports the addition of a decimal fraction to the smallest time value in the representation, should this be required by an application. Fractional values for hours, minutes and seconds can consist of one or more digits. If used with hours, the time representation cannot contain a value for minutes or seconds. If used with minutes, it cannot contain a value for seconds. The separator for fractional values can be either a comma or a period.

The time value 10.35 plus 30.75 seconds could be represented in ISO 8601 format as:

T103530.750    (basic format with period fractional separator)
T103530,750     (basic format with comma fractional separator)
T10:35:30.750   (extended format with period fractional separator)
T10:35:30,750   (extended format with comma fractional separator)

The ISO 8601 datetime string

A single moment in time can be represented by concatenating a time value with a date value, with the letter "T" placed between the two values to act as a separator. Either format (basic or extended) may be used, but both date and time must be expressed using the same format (either all basic or all extended).

The degree of precision for the time value may vary, but the date must be represented in full, regardless of whether it is represented as a calendar date, or using week numbers, or as an ordinal date. For the moment, we will limit our discussion of date/time values to those expressed using calendar dates. The representation of a date/time with millisecond precision could be expressed using either of the following formats:

YYYYMMDDThhmmss.sss

YYYY-MM-DDThh:mm:ss.sss

Less precise date/time values may also be expressed by omitting one or more values from the right-hand side of the date/time string. For example, the following basic and extended date/time formats are both valid (note however that, as we mentioned earlier, hours cannot be used without minutes):

YYYYMMDDThhmmss

YYYY-MM-DDThh:mm:ss

YYYYMMDDThhmm

YYYY-MM-DDThh:mm

A timezone designator (TZD) may also be appended to an ISO 8601 datetime string in order for localised date and time information can be calculated and displayed (we'll talk about time zones in more detail shortly). The TZD is expressed as either "Z" (zero offset from UTC), or as a value that indicates how far ahead of or behind UTC a timezone is, in the form "+hh:mm" or "-hh:mm".

The complete ISO 8601 datetime string can be defined as follows:

YYYY-MM-DDTHH:mm:ss.sssZ

or:

±YYYYYY-MM-DDTHH:mm:ss.sssZ

where:

"YYYY" is the year in the proleptic Gregorian calendar (0000 - 9999)
"±YYYYYY" is the expanded year ("+" or "-" followed by six decimal digits)
"-" is a hyphen, used to separate year, month and day values
"MM" is the month of the year (01 to 12)
"DD" is the day of the month (01 to 31)
"T" is the uppercase letter "T", used to denote the starts of a time value
"HH" is the number of hours elapsed since midnight (00 to 23)
":" is a colon, used to separate hours, minutes and seconds
"mm" is the number of minutes since the start of the hour (00 to 59)
"ss" is the number of complete seconds since the start of the minute (00 to 59)
"." is a period, used to separate seconds from milliseconds
"sss" is the number of complete milliseconds since the start of the second (000-999)
"Z" is the UTC offset - a value of "Z" represents UTC with no offset, while a value in the form "+HH:mm" or "-HH:mm" can be used to represent a local timezone offset

Note that the 64-bit integer timestamp values stored by PHP can be presented to the user using a broad range of human-readable date and time formats. The constants defined by DateTimeInterface represent a range of pre-defined date and time formats, of which the core ISO 8601 format (YYYY-MM-DDTHH:mm:ss) is just one. In addition, we can specify a customised format for the datetime string using our own format string, comprised of one or more date and time format specifiers.

Creating a timestamp

As we have seen, PHP stores the date and time information that identifies a specific point in time as a 64-bit signed integer value. This value is effectively a Unix timestamp, and represents the number of seconds that have elapsed since 00:00:00 UTC (GMT) on January 1st, 1970 (the UNIX epoch). In order to create a timestamp for the current date and time, we can use the time() function. This function requires no arguments - it simply returns the timestamp, for the date and time at which it is called, as an integer. For example:

<?php
$timestamp = time();
echo $timestamp;
// 1765901497
?>

The timestamp returned by time() will be the same regardless of location, i.e. the Coordinated Universal Time (UTC), otherwise known as Greenwich Mean Time (GMT). If you need to derive the local time for a different timezone when creating a datetime string, you will need to specify a timezone offset - but more about that later.

The time() function is useful when we want to know the instantaneous time, in seconds, at a given moment. There are many situations, however, where we want to create a timestamp for a specific date and time, either in the past or in the future. This can be achieved using PHP's mktime() function. This function takes up to six integer arguments, the first of which is mandatory. For example:

<?php
$zeroHour = mktime(0);
$now = time();
echo "Timestamp now is: $now <br>";
echo "Timestamp for zero hour is: $zeroHour <br>";
echo "Time difference is: " . $now - $zeroHour . " seconds<br>";
// Timestamp now is: 1765972118
// Timestamp for zero hour is: 1765928918
// Time difference is: 43200 seconds
?>

This might seem a little strange until you know how the mktime() function works. In the above example we have passed a value of 0 to mktime() as the first (and only) argument. The six arguments accepted by mktime() are all integer values, and represent the hour, minute, second, month, day, and year, in that order (the month/day/year ordering is a little strange, but that's just how it is . . . ).

We have set the hour to 0, but because the other arguments have been omitted, mktime() uses values for minute, second, month, day and year based on the current date and time. That effectively means that the difference between the current timestamp and the timestamp returned by mktime() whenever our script runs is always going to be an exact number of hours. Indeed, 43,200 seconds divided by 60 gives us 720 minutes. If we divide 720 minutes by 60, we get 12 hours.

Let's suppose we want to know exactly how many seconds have elapsed since midnight. We need to set all of the time-related arguments (hour, minute and second) to 0 (the month, day and year arguments will default to the current month, day and year). For example:

<?php
$midnight = mktime(0, 0, 0);
$now = time();
echo "Timestamp now is: $now <br>";
echo "Timestamp for 00:00:00 this morning is: $midnight <br>";
echo "Time difference is: " . $now - $midnight . " seconds<br>";
// Timestamp now is: 1765974289
// Timestamp for 00:00:00 this morning is: 1765926000
// Time difference is: 48289 seconds
?>

If you run this script and refresh the browser window a few times you should see that the difference between the $midnight and $now variables is incremented by 1 for each second that passes.

Perhaps the most common use case for mktime() is to create a timestamp for a specific date and time - either some event that occurred in the past, or some event that is planned for a future date. For example:

<?php
$xmas = mktime(0, 0, 0, 12, 25);
$now = time();
$ss = $xmas - $now;
$dd = floor($ss / 86400);
$ss -= $dd * 86400;
$hh = floor($ss / 3600);
$ss -= $hh * 3600;
$mm = floor($ss / 60);
$ss -= $mm * 60;
if($ss > 0) {
echo "It will be Christmas in exactly $dd days, $hh hours $mm minutes and $ss seconds!";
}
else {
echo "Merry Christmas and a Happy New Year!";
}
// It will be Christmas in exactly 7 days, 9 hours 36 minutes and 8 seconds!
?>

In the above script we have passed nearly all of the time and date parameters for midnight (00:00:00) on Christmas day, but have omitted the last argument to ensure that, whenever this script is run, the mktime() function will use the current year to formulate the timestamp.

Each time the script is run prior to midnight on Christmas day of the current year, the timestamp will have a positive non-zero value and the user will get a message telling them how many days, hours, minutes and seconds remain until the start of Christmas day. Once the value is zero or less (i.e. a negative number), they will receive a Christmas and New Year greeting each time the script runs.

One word of warning: If no arguments are supplied to mktime() a fatal error will occur. The same thing happens if any of the arguments are non-numeric. If a non-integer numeric value is used for any of the arguments, the script will still work, but a warning will be generated. So far so good.

The real problem here is that, if all of the arguments supplied are integer values, the function will return a timestamp regardless of whether the values entered represent a valid date. The mktime() function does not appear to check whether or not the integer values entered for any of the arguments are valid. This is what happens when we use arbitrary random integer values as arguments:

<?php
$xmas = mktime(54,99,2000,150,-1);
$now = time();
$ss = $xmas - $now;
$dd = floor($ss / 86400);
$ss -= $dd * 86400;
$hh = floor($ss / 3600);
$ss -= $hh * 3600;
$mm = floor($ss / 60);
$ss -= $mm * 60;
if($ss > 0) {
echo "It will be Christmas in exactly $dd days, $hh hours $mm minutes and $ss seconds!";
}
else {
echo "Merry Christmas and a Happy New Year!";
}
// It will be Christmas in exactly 4183 days, 16 hours 6 minutes and 24 seconds!
?>

This is clearly a nonsensical result! The programmer is responsible for ensuring that the arguments passed to mktime() represent a valid time and date, because the function itself does not apply any checks and balances in that respect.

We can also create a timestamp using the strtotime() function. This function takes two arguments, the first of which can be any valid datetime string, or relative terms (in English) such as "next Thursday” or "last Saturday”.

The second (optional) argument is a timestamp which is used as a basis for calculating relative dates if applicable (if omitted, this argument defaults to null). For example:

<?php
$timestamp = strtotime("now");
echo $timestamp . " (" . date("Y-m-d H:i:s", $timestamp) . ")<br>";
$timestamp = strtotime("+1 day");
echo $timestamp . " (" . date("Y-m-d H:i:s", $timestamp) . ")<br>";
$timestamp = strtotime("December 25 2025");
echo $timestamp . " (" . date("Y-m-d H:i:s", $timestamp) . ")";
// 1766248022 (2025-12-20 17:27:02)
// 1766334422 (2025-12-21 17:27:02)
// 1766617200 (2025-12-25 00:00:00)
?>

We mentioned earlier that PHP now provides classes that enable us to create datetime objects, namely DateTime and DateTimeImmutable . Unsurprisingly, both of these classes provide the means to create a timestamp. In fact, they both inherit the same getTimestamp() function from DateTimeInterface.

The caveat here is that getTimestamp() is a non-static method, which means it can only be called on an instance of either the DateTime or DateTimeImmutable classes. For example:

<?php
$myDate = new DateTime;
$timestamp = $myDate->getTimestamp();
echo $timestamp;
// 1765983943
?>

The above script creates an object variable called $myDate as an instance of the DateTime class. It then calls the getTimestamp() method on $myDate, and assigns the value returned by getTimestamp() to the integer variable $timestamp.

In fact, we now have the information about the current date and time stored in two places. We have assigned the timestamp to the $timestamp variable, and we can also retrieve the timestamp value from the object using the getTimestamp() method. Do we really need to store the timestamp in a variable if we can retrieve it from a DateTime object?

Actually, you may remember that, as we mentioned earlier, an instance of the DateTime class is mutable, which means its properties can be changed by the methods it implements. This will obviously include any time and date information stored within it. If we want to retain the original timestamp value, we can either assign it to a separate variable as we have done here, or create an instance of DateTimeImmutable instead of DateTime. For example:

<?php
$myDate = new DateTimeImmutable;
var_dump($myDate->getTimestamp());
// int(1765988290)
?>

We will be looking in more detail at the DateTime and DateTimeImmutable classes in due course. Before we do so, we'll take a look at the different ways in which we can create and use a datetime string. Like the timestamp, the datetime string can store information about a specific moment in time.

In fact, there are various ways in which we can derive a timestamp from a datetime string, and vice versa. The main difference is that a datetime string presents information about a specific date and time in a human-readable format, whereas getting the same information from a timestamp presents a significant challenge. Fortunately, PHP provides a number of functions and class methods to facilitate this task.

Creating a datetime string

PHP provides a number of ways to create a datetime string. One obvious method is to simply create a string containing the date and time components in a valid format. We have already seen the ISO 8601 basic and extended formats for a full datetime string:

YYYYMMDDThhmmss.sss

YYYY-MM-DDThh:mm:ss.sss

Both of these formats specify the year, month, day, hour, minute, second and millisecond. Let's create a datetime string for a specific date based on the extended format. For example:

"2025-12-18T17:23:25.755"

The first part of this datetime string specifies a date of December 18th, 2025. The second part specifies a time of 17:23 plus 25 seconds and 755 milliseconds. If we pass this string to the strtotime() function we should get a timestamp:

<?php
$datetime = "2025-12-18T17:23:25.755";
$timestamp = strtotime($datetime);
echo $timestamp;
// 1766075005
?>

So far this looks good. We can check whether this timestamp accurately represents the date and time we specified by passing it to the date() function, which converts a valid Unix timestamp to a readable format - effectively, the reverse of what the strtotime() function does. The date() function takes two argument - a format string that specifies which date and time components appear in the datetime string (and in what format), and a valid Unix timestamp. For example:

<?php
$datetime = date("Y-m-d H:i:s.v", 1766075005);
echo $datetime;
// 2025-12-18 17:23:25.000
?>

As you can see, the datetime string returned by date() is almost identical to the one we passed to the strtotime() function. There are two changes. The first is that the "T” separating the date and time parts of the datetime string has been replaced by a space. That's OK - we could have used a space instead of the "T” anyway, because "2025-12-18 17:23:25.755" is also a valid datetime string.

The second change is that the "755” representing milliseconds has been replaced by "000”. The reason for this is that the Unix timestamp only stores the number of seconds between the date and time in question and the Unix epoch. It cannot store milliseconds (thousandths of a second) or microseconds (millionths of a second).

The date() function recognises the "v” time format specifier, which indicates a millisecond component, in the format string, but cannot derive a corresponding value from the timestamp. Nevertheless, because the millisecond format specifier has been used, date() inserts three zeros in the datetime string in order to fulfil this obligation. The other format specifiers are fairly self explanatory:

"Y” is the year in full (at least four digits)
"m” is the two-digit month (01 - 12)
"d” is the two-digit day (01 - 31)
"H” is the two-digit hour in 24-hour format (00 - 23)
"i” is the two-digit minute (00 - 59)
"s” is the two-digit second (00 - 59)
"v” is the three-digit millisecond

The three date values (year, month and day) are separated by a hyphen (-), and the time values (hour, minute and second) are separated by a colon (:). The millisecond component represents a fraction of a second, so a period (.) is used here between the "s” and "v” format specifiers.

Characters other than those shown here can be used to separate the elements of a datetime string. Any characters not recognised as format specifiers will appear in the datetime string exactly as they appear in the format string.

Note that the timestamp argument for date() is optional. If omitted, date() will return a datetime string representing the current date and time in the specified format. For example:

<?php
var_dump(date("Y/m/d H.i"));
// string(16) "2025/12/19 12.49"
?>

Working with fractions of a second

As we have seen, procedural functions like strtotime() and date() allow us to manipulate and fine-tune date and time information to a precision of one second. For most purposes, this is probably more than adequate. However, if we need to work with milliseconds and microseconds - for example to measure the time taken to execute a block of code, or time interval between two inputs received from an external sensor, we can turn to the microtime() function.

The microtime() function takes a single (optional) Boolean argument (true or false) and returns the current Unix timestamp together with a fractional value that represents microseconds. If no argument is supplied, or the argument is set to false (the default), the return value will be a string containing the elapsed microseconds as a fractional value and the Unix timestamp, separated by a space. For example:

<?php
$timestamp = microtime();
var_dump($timestamp);
// string(21) "0.47030200 1766151026"
?>

If the Boolean argument is set to true, the value returned is a floating-point value for which the integer component is the Unix timestamp and the fractional component is the number of elapsed microseconds. For example:

<?php
$timestamp = microtime(true);
var_dump($timestamp);
// float(1766158773.397863)
?>

Note: using echo instead of var_dump() may display a result with only four digits after the decimal point instead of six, depending on how your version of PHP is configured. This is almost certainly due to the precision directive in the php configuration file php.ini being set to 14 by default (precision=14).

You can set the precision directive to 16 or 17 significant digits by editing php.ini. Increasing the value beyond that is unlikely to result in an increase in the precision of the value displayed, and will not make any difference to the value actually stored in a floating-point variable - it will only affect the total number of significant digits displayed.

Creating a DateTime object

So far, we have seen how a datetime string can be derived from a timestamp and vice versa using the strtotime() and date() functions. We have also seen how we can obtain date and time values for the current date and time, with microsecond accuracy, using the microtime() function.

It's time now to look at the DateTime and DateTimeImmutable classes which, so far, we have only mentioned in passing. Essentially, both of these classes behave in the same way, because they both implement the DateTimeInterface, which we have also mentioned. We'll begin by looking at the DateTime class. The complete DateTime class definition can be found on the PHP Group website.

The only significant difference between the DateTime and DateTimeImmutable classes is that the methods defined by DateTimeInterface can alter the property values stored in an object that is an instance of the DateTime class, whereas they cannot change the property values stored in an object that is an instance of DateTimeImmutable. We'll come back to the implications of that a bit later. For now, we'll focus on objects that are instances of the DateTime class.

Let's start by creating a DateTime object that holds the current date and time, and use var_dump() to see just what it is we have created:

<?php
$now = new DateTime();
var_dump($now);
/*
object(DateTime)#1 (3) {
["date"]=> string(26) "2025-12-19 17:57:26.250305"
["timezone_type"]=> int(3)
["timezone"]=> string(13) "Europe/Berlin"
}
*/
?>

As you can see, we have created a DateTime object whose visible attributes include a datetime string with microsecond precision, a timezone type, and a timezone.

The first line tells us that $now is an object (no surprises there). The "#1” tells us that this is the first instance of DateTime object to be created. If we were to create a second instance of the DateTime class called $later, then var_dump($later) would display "#2” in this position. The "(3)” tells us that the DateTime object has three properties, which are:

["date”] - a complete datetime string
["timezone_type”] - an integer value in the range 1 - 3
["timezone”] - this can be a UTC offset such as "+0010” (type 1), a timezone abbreviation such as "GMT” (type 2), or a timezone identifier such as "Europe/Berlin" (type 3).

Internally, the DateTime object stores individual values for year, month, day, hour, minute, second, and microsecond, as well as the specific timezone. These values are accessed and modified, either directly or indirectly, using the methods inherited by the DateTime class from DateTimeInterface.

We can create a DateTime object that holds a specific date and time by passing a valid datetime string to the DateTime constructor. For example:

<?php
$dateTime = new DateTime("2025-12-19 17:57:26.250305");
var_dump($dateTime);
/*
object(DateTime)#1 (3) {
["date"]=> string(26) "2025-12-19 17:57:26.250305"
["timezone_type"]=> int(3)
["timezone"]=> string(13) "Europe/Berlin"
}
*/
?>

Note that the DateTime constructor can accept two arguments, the first of which, as we have seen, is a datetime string. If we do not provide a datetime string, this argument defaults to "now”. We can also use "now” explicitly as the datetime string, which has the same effect as calling the DateTime constructor with no arguments. For example:

<?php
$dateTime = new DateTime("now");
var_dump($dateTime);
/*
object(DateTime)#1 (3) {
["date"]=> string(26) "2025-12-20 09:17:08.011625"
["timezone_type"]=> int(3)
["timezone"]=> string(13) "Europe/Berlin" }
*/
?>

The second (optional) argument is an instance of the DateTimeZone class, which we'll be looking at later in this article. If not provided, this argument defaults to null, and the local timezone is used.

We can also create a DateTime object using the date_create() function. This function takes the same arguments as the DateTime constructor, the first of which is a valid datetime string. The second (optional) argument is an instance of the DateTimeZone class. If omitted, these arguments default to "now” and null, respectively.

The return value is a DateTime object (or false if the arguments passed to date_create() are not valid. Here is an example of how we might use date_create():

<?php
$date = date_create("2025-12-24 00:00:00");
var_dump($date);
/*
object(DateTime)#1 (3) {
["date"]=> string(26) "2025-12-24 00:00:00.000000"
["timezone_type"]=> int(3)
["timezone"]=> string(13) "Europe/Berlin" }
*/
?>

Another option for creating a DateTime object is to use the static DateTime method DateTime::createFromFormat(). This method takes three arguments, the first of which must be a valid format string, and the second a valid datetime string. The third (optional) argument is an instance of the DateTimeZone class. If this argument is omitted, it defaults to null. Here is an example:

<?php
$date = DateTime::createFromFormat("d-m-Y H:i:s", "24-12-2025 00:00:00");
var_dump($date);
/*
object(DateTime)#1 (3) {
["date"]=> string(26) "2025-12-24 00:00:00.000000"
["timezone_type"]=> int(3)
["timezone"]=> string(13) "Europe/Berlin" }
*/
?>

Note that the each of the format specifiers in the format string must be matched by appropriate corresponding values in the datetime string. If the number of format specifiers does not match the number of time and date components in the datetime string, the createFromFormat() method will return false.

If the value provided for a given date or time component is invalid, the results can vary. In the above example, we have specified the month as "m” in the format string, indicating that the month should be represented by a two-digit number in the range 01 to 12. If we were to inadvertently enter 120 for this value instead of 12, the createFromFormat() method returns false. For example:

<?php
$date = DateTime::createFromFormat("d-m-Y H:i:s", "25-120-2025 00:00:00");
var_dump($date);
// bool(false)
?>

However, the createFromFormat() method will happily accept any two-digit number for the month. In the example below, we have specified a value of 99 for the month, which is obviously incorrect, but createFromFormat() has calculated a date based on 99 calendar months from the beginning of 2025 and outputs the date as "2033-03-25”.

<?php
$date = DateTime::createFromFormat("d-m-Y H:i:s", "25-99-2025 00:00:00");
var_dump($date);
/*
object(DateTime)#1 (3) {
["date"]=> string(26) "2033-03-25 00:00:00.000000"
["timezone_type"]=> int(3)
["timezone"]=> string(13) "Europe/Berlin"
}
*/
?>

Formatting dates and times

We have seen that date and time information can be stored in various different ways in PHP. The simplest internal representation is the Unix timestamp - a 64-bit integer that represents a date and a time in terms of the number of seconds separating it from the Unix Epoch (January 1 1970 00:00:00 GMT). We can also save date and time information as a human-readable datetime string.

In fact, as we have seen, we can easily convert a timestamp to a datetime string using the date() function, and just as easily convert a datetime string to a timestamp using the strtotime() function. Perhaps the most versatile way to store date and time information, however, is to use a DateTime or DateTimeImmutable object, either of which can store date and time information with nanosecond precision if required.

We'll look first at some of the ways in which we can format a datetime string using the date() function with the current date and/or time. The pre-defined constants provided by DateTimeInterface produce the following results:

<?php
echo date(DATE_ATOM) . "<br>"; // 2026-01-07T16:19:25+01:00
echo date(DATE_COOKIE) . "<br>"; // Wednesday, 07-Jan-2026 16:19:25 CET
echo date(DATE_ISO8601) . "<br>"; // 2026-01-07T16:19:25+0100
echo date(DATE_ISO8601_EXPANDED) . "<br>"; // +2026-01-07T16:19:25+01:00
echo date(DATE_RFC822) . "<br>"; // Wed, 07 Jan 26 16:19:25 +0100
echo date(DATE_RFC850) . "<br>"; // Wednesday, 07-Jan-26 16:19:25 CET
echo date(DATE_RFC1036) . "<br>"; // Wed, 07 Jan 26 16:19:25 +0100
echo date(DATE_RFC1123) . "<br>"; // Wed, 07 Jan 2026 16:19:25 +0100
echo date(DATE_RFC7231) . "<br>"; // Wed, 07 Jan 2026 16:19:25 GMT
echo date(DATE_RFC2822) . "<br>"; // Wed, 07 Jan 2026 16:19:25 +0100
echo date(DATE_RFC3339) . "<br>"; // 2026-01-07T16:19:25+01:00
echo date(DATE_RFC3339_EXTENDED) . "<br>"; // 2026-01-07T16:19:25.000+01:00
echo date(DATE_RSS) . "<br>"; // Wed, 07 Jan 2026 16:19:25 +0100
echo date(DATE_W3C) . "<br>"; // 2026-01-07T16:19:25+01:00
?>

Other than that, we can format a datetime string in just about any way we like by combining the date and time format specifiers, in virtually any order, with string literals, punctuation characters and wite space. Not however that if we use string literals that contain characters that are used as date or time format specifiers, we need to escape those characters. Consider the following example:

<?php
$formatStr = "It is g:i A on l, jS F, Y";
echo date($formatStr);
// 031 5614 4:56 PM 20261 Wednesday, 7th January, 2026
?>

Let's quickly run though the formatting characters used here:

g is the 12-hour format of the hour without leading zeros (0-12).
i is the minutes with leading zeros (00-59).
A is the anti-meridian or post-meridian in uppercase (AM or PM).
l is the full textual representation of the day (Sunday-Saturday).
j is the day of the month without leading zeros (1-31).
S is the two-character English ordinal suffix for the day of the month (st, nd, rd or th).
F is the full textual representation of the month (January - December).
Y is the full numeric representation of the year (at least 4 digits).

As you can see, this is not quite what we would expect to see, even though the time and date have been displayed correctly. The reason is that every single character in the string literals "It is ” and "on ", except for the spaces, are format specifiers. Here is a corrected version:

<?php
$formatStr = "\I\\t \i\s g:i A \o\\n l, jS F, Y";
echo date($formatStr);
// It is 5:06 PM on Wednesday, 7th January, 2026
?>

Not that if we enclose the format string in double quotes, the "t” and the "n” must be escaped twice because, as well as being format specifiers, they become escape sequences in their own right when accompanied by a backslash (i.e. the tab and newline characters respectively). We could of course get around this by removing the string literals from the equation altogether. For example:

<?php
$timeFormat = "g:i A";
$dateFormat = "l, jS F, Y";
echo "It is " . date($timeFormat) . " on " . date($dateFormat);
// It is 5:41 PM on Wednesday, 7th January, 2026
?>

This solution is not particularly satisfactory if we're going to use this particular format for dates and times on a regular basis. It creates more work in the long run because we now have two format strings to think about instead of one, and we have to type in the string literals "It is " and " on " every time. It's also less efficient in terms of coding, because we have to call the date() function twice - once for the time, and once for the date.

That doesn't mean, of course, that we can't have different format specifier variables to hold different format strings for different situations, such as when we just need to display a date without displaying any time information, or vice versa. In fact, we could define our own constants to represent commonly used date and/or time format strings.

If we are using a DateTime or DateTimeImmutable object to store datetime information, we can use the DateTimeInterface::format() method, which both the DateTime class and the DateTimeImmutable class implement, to format a datetime string. Virtually any format string we can use with the date() function can also be used with the format() method to produce similar results, because it recognises the same set of format specifiers. For example:

<?php
$date = new DateTime();
$formatStr = "\I\\t \i\s g:i A \o\\n l, jS F, Y";
echo $date->format($formatStr);
// It is 6:24 PM on Wednesday, 7th January, 2026
?>

And of course, we can use the constants provided by DateTimeInterface with similar results:

<?php
$date = new DateTime();
echo $date->format(DATE_ATOM) . "<br>"; // 2026-01-07T18:29:31+01:00
echo $date->format(DATE_COOKIE) . "<br>"; // Wednesday, 07-Jan-2026 18:29:31 CET
echo $date->format(DATE_ISO8601) . "<br>"; // 2026-01-07T18:29:31+0100
echo $date->format(DATE_ISO8601_EXPANDED) . "<br>"; // +2026-01-07T18:29:31+01:00
echo $date->format(DATE_RFC822) . "<br>"; // Wed, 07 Jan 26 18:29:31 +0100
echo $date->format(DATE_RFC850) . "<br>"; // Wednesday, 07-Jan-26 18:29:31 CET
echo $date->format(DATE_RFC1036) . "<br>"; // Wed, 07 Jan 26 18:29:31 +0100
echo $date->format(DATE_RFC1123) . "<br>"; // Wed, 07 Jan 2026 18:29:31 +0100
echo $date->format(DATE_RFC7231) . "<br>"; // Wed, 07 Jan 2026 18:29:31 GMT
echo $date->format(DATE_RFC2822) . "<br>"; // Wed, 07 Jan 2026 18:29:31 +0100
echo $date->format(DATE_RFC3339) . "<br>"; // 2026-01-07T18:29:31+01:00
echo $date->format(DATE_RFC3339_EXTENDED) . "<br>"; // 2026-01-07T18:29:31.475+01:00
echo $date->format(DATE_RSS) . "<br>"; // Wed, 07 Jan 2026 18:29:31 +0100
echo $date->format(DATE_W3C) . "<br>"; // 2026-01-07T18:29:31+01:00
?>

The possibilities are almost endless when it comes to specifying the output format for a datetime string. A full list of format specifiers, together with a description of what each specifier does, is provided below. We can use these format specifiers in various combinations to create and format string representations of dates and/or times.



Date and Time Format Specifiers
CharacterDescription
aAnti-meridian or post-meridian in lowercase (am or pm).
AAnti-meridian or post-meridian in uppercase (AM or PM).
BSwatch Internet time (000 - 999).
cISO 8601 date (e.g. 2025-12-18T15:19:21+00:00). Only compatible with the non-expanded format (up to year 9999). Later dates will result in an invalid string. For later dates and expanded format, see x and X.
dDay of the month, 2 digits with leading zeros (01-31).
DA textual representation of a day, three letters (Mon - Sun).
eTimezone identifier (e.g. UTC, GMT, Atlantic/Azores etc.).
FA full textual representation of a month (January - December).
g12-hour format of an hour without leading zeros (1-12).
G24-hour format of an hour without leading zeros (0-23).
h12-hour format of an hour with leading zeros (01-12).
H24-hour format of an hour with leading zeros (00-23).
iMinutes with leading zeros (00-59).
IWhether or the date is in daylight saving time (1) or not (0).
jDay of the month without leading zeros (1-31).
lA full textual representation of a day (Sunday-Saturday).
LWhether it's a leap year (1) or not (0).
mNumeric representation of a month, with leading zeros (01-12).
MA short textual representation of a month, three letters (Jan-Dec).
nNumeric representation of a month, without leading zeros (0-12).
NISO 8601 numeric representation of the day of the week (Mon = 1, Sun = 7).
oISO 8601 week-numbering year. This has the same value as Y, except that if the ISO week number (W) belongs to the previous or next year, that year is used instead (e.g. 1999, 2003).
ODifference to Greenwich Mean Time (GMT) without colon between hours and minutes (e.g. +0200).
pThe same as P (see below), but returns Z instead of +00:00 (e.g. Z, +02:00).
PDifference to Greenwich Mean Time (GMT) with colon between hours and minutes (e.g. +02:00).
rThe RFC2822/RFC5322 formatted date (e.g. Thu, 21 Dec 2000 16:01:07 +0200).
sSeconds with leading zeros (00-59).
SEnglish ordinal suffix for the day of the month, 2 characters(st, nd, rd or th).
tNumber of days in the given month (28-31).
TTimezone abbreviation if known, otherwise the GMT offset (e.g. EST, MDT, +05).
uMicroseconds (e.g. 123456). Note that date() will always generate 000000 since it takes an int parameter, whereas DateTimeInterface::format() does support microseconds if an object of type DateTime or DateTimeImmutable was created with microseconds.
USeconds since the Unix Epoch (January 1 1970 00:00:00 GMT).
vMilliseconds (e.g. 655). Same note as for u.
wNumeric representation of the day of the week (Sunday = 0, Saturday = 6).
WISO 8601 week number of year, weeks starting on Monday (01-53).
xAn expanded full numeric representation of a year, if required, or a standard full numeral representation if possible (like Y - see below). At least four digits. Years BCE are prefixed with a -, years beyond (and including) 10000 are prefixed by a + (e.g. 0055, 0787, 1999, +10191).
XAn expanded full numeric representation of a year, at least 4 digits, with "-" for years BCE, and "+” for years CE.
yA two-digit representation of a year (e.g. 03 or 99).
YA full numeric representation of a year, at least 4 digits, with - for years BCE (e.g. -0055, 0787, 1999, 2003, 10191).
zThe day of the year, starting from 0 (0-365).
ZTimezone offset in seconds. The offset for time zones West of UTC is always negative; for those East of UTC, it is always positive (-43200 - 50400).


The DateTimeImmutable class

The DateTimeImmutable class implements virtually the same properties and methods as the DateTime class. The primary difference is that methods that modify the properties of a DateTimeImmutable object return a new instance of DateTimeImmutable without changing the properties of the DateTimeImmutable object on which they are called. For example:

<?php
$xmas2025 = new DateTimeImmutable("2025-12-25");
var_dump($xmas2025);
/*
object(DateTimeImmutable)#1 (3) {
["date"]=> string(26) "2025-12-25 00:00:00.000000"
["timezone_type"]=> int(3)
["timezone"]=> string(13) "Europe/Berlin"
}
*/
echo "<br>";
$newYear2026 = $xmas2025->add(DateInterval::createFromDateString("1 week"));
var_dump($newYear2026);
/*
object(DateTimeImmutable)#3 (3) {
["date"]=> string(26) "2026-01-01 00:00:00.000000"
["timezone_type"]=> int(3)
["timezone"]=> string(13) "Europe/Berlin"
}
*/
?>

As you can see, the DateTimeImmutable class has its own constructor method that takes the same arguments as the DateTime constructor. Other ways of creating a DateTimeImmutable object include the functions date_create_immutable(), which accepts the same arguments as date_create(), and date_create_immutable_from_format(), which accepts the same arguments as date_create_from_format(). The complete DateTimeImmutable class definition can be found on the PHP Group website.

Accessing datetime components

It will often be the case that we want to extract individual date and time elements from a timestamp, datetime string or DateTime object. The method used to achieve this will depend on which of these date and time representations we are dealing with. We'll start by looking at how we can get the information we need from a timestamp.

As we know, a timestamp is a 64-bit signed integer that identifies a specific point in time, i.e. the number of seconds separating that point in time and the Unix epoch - 00:00:00 UTC (GMT) on January 1st, 1970, with negative values representing dates prior to the epoch. We have already seen that we can use the date() function to create a datetime string from a timestamp, using a format string to output the datetime string in the required format.

It follows that we should be able extract individual date and time components from a timestamp using the date() function. We might, for example, wish to store the individual date components (year, month, day, hour, minute and second) as the elements of an associative array. We could do something like this:

<?php
$timestamp = time();
$datetime_array["year"] = intval(date("Y", $timestamp));
$datetime_array["month"] = intval(date("m", $timestamp));
$datetime_array["day"] = intval(date("d", $timestamp));
$datetime_array["hour"] = intval(date("H", $timestamp));
$datetime_array["minute"] = intval(date("i", $timestamp));
$datetime_array["second"] = intval(date("s", $timestamp));
foreach($datetime_array as $key=>$value) {
echo "$key: $value<br>";
}
// year: 2025
// month: 12
// day: 21
// hour: 22
// minute: 26
// second: 39
?>

We can now access individual date/time components as integer values. That's quite a lot of code though. Fortunately, we don't need to write that much code because we can use PHP's built-in getdate() function to achieve the same result.

The getdate() function takes a timestamp as its argument and returns an associative array containing all of the date/time components we were able to retrieve using the date() function and more, and it does so in a single line of code. If the timestamp argument is omitted, the current local time is used instead. Consider the following example:

<?php
$timestamp = time();
$datetime_array = getdate($timestamp);
foreach($datetime_array as $key=>$value) {
echo "$key: $value<br>";
}
// seconds: 57
// minutes: 47
// hours: 21
// mday: 21
// wday: 0
// mon: 12
// year: 2025
// yday: 354
// weekday: Sunday
// month: December
// 0: 1766350077
?>

We now have an array variable that contains all of the information we need about the date and time for most purposes, including the timestamp itself. The associative array returned by the getdate() function stores the following key-value pairs:



Date and Time Array Elements
KeyDescription
"seconds"Numeric representation of seconds (0 - 59)
"minutes"Numeric representation of minutes (0 - 59)
"hours"Numeric representation of hours (0 - 23)
"mday"Numeric representation of the day of the month (0 - 31)
"wday"Numeric representation of the day of the week (0 = Sunday, 1 = Monday, . . . 7 = Saturday)
"mon"Numeric representation of the month (1 - 12)
"year"Full numeric representation of year (0000 - 9999)
"yday"Numeric representation of day of year (0 - 365)
"weekday"Full text representation of day of week ("Sunday”, "Monday”, . . . "Saturday”)
"month"Full text representation of month ("January”, "February”, . . . "December”)
0Timestamp (seconds elapsed since the Unix Epoch)


If we need to get this information from a DateTime object rather than a timestamp, there are no methods available to get the individual values for year, month, day etc., which may seem to be something of an oversight on the part of the PHP design team. The DateTime class does however inherit the getTimestamp() method from DateTimeInterface, so we could do something like the following:

<?php
$date = new DateTime("2025-12-25 12:30");
$timestamp = $date->getTimestamp();
$datetime_array = getdate($timestamp);
foreach($datetime_array as $key=>$value) {
echo "$key: $value<br>";
}
// seconds: 0
// minutes: 30
// hours: 12
// mday: 25
// wday: 4
// mon: 12
// year: 2025
// yday: 358
// weekday: Thursday
// month: December
// 0: 1766662200
?>

If we need to work with millisecond or microsecond values, we need to use a DateTime or DateTimeImmutable object to store date and time information, because a timestamp can only store the number of seconds between a given date and time and the Unix epoch, whereas a DateTime or DatetimeImmutable object can store the time with microsecond resolution.

At the time of writing, there is does not appear to be a specific method available for retrieving the microsecond value stored in a DateTime or DateTimeImmutable object, but we can still retrieve a microsecond value by doing something like the following:

<?php
$date = new DateTime();
$ms = (int) $date->format("u");
var_dump($date);
echo "<br>";
var_dump($ms);
/*
object(DateTime)#1 (3) {
["date"]=> string(26) "2025-12-22 19:00:49.597063"
["timezone_type"]=> int(3)
["timezone"]=> string(13) "Europe/Berlin"
}
/*
// int(597063)
?>

Comparing dates and times

We often need to compare the dates on which two events occur in order to ascertain the order in which those events occur, or whether they occur on the same date. We might want to make a more fine-grained comparison involving both dates and times. Making such comparisons may be necessary, for example, when placing events in a historical context, when scheduling future events, or when creating a timetable.

We need to exercise caution when comparing dates and times, however. Comparing the timestamps for two events that occurred within milliseconds of one another, for example, will reveal which event occurred first. If we only need to know whether those events occur on the same calendar date, simply comparing timestamps will not provide the answer.

The same is true when comparing two DateTime or two DateTimeImmutable objects. If two objects are compared using the equality operator (==), they will be considered equal only if they are of the same class and have the same attributes and values. Again, this can lead to misleading results if we just want to know whether two events occur on the same day. For example:

<?php
$date1 = new DateTime('2024-10-12 10:00');
$date2 = new DateTime('2024-10-12 11:00');

if($date1 < $date2) {
echo "\$date1 is earlier than \$date2";
}
else if($date1 > $date2) {
echo "\$date1 is later than \$date2";
}
else {
echo "\$date1 is the same as \$date2";
}
// $date1 is earlier than $date2
?>

If we just want to know if two events occurred on the same date, regardless of what time they occurred, we need to restrict the comparison to only the date components stored in each DateTime or DateTimeImmutable object. We could, for example, do something like this:

<?php
$date1 = new DateTime('2024-10-12 10.00');
$date2 = new DateTime('2024-10-12 11.00');

$dateStr1 = $date1->format("Y-m-d");
$dateStr2 = $date2->format("Y-m-d");

if($dateStr1 < $dateStr2) {
echo "\$date1 is earlier than \$date2";
}
else if($dateStr1 > $dateStr2) {
echo "\$date1 is later than \$date2";
}
else {
echo "\$date1 is the same as \$date2";
}
// $date1 is the same as $date2
?>

When comparing both the date and the time at which two events occur, we need to consider what it is exactly we are trying to ascertain. For example, do we consider two events to have occurred at the same time if they occurred within fifteen minutes of each other, or do we only say they occurred at the same time if there is a precise match? In the former case, we could do something like this:

<?php
$event1 = new DateTime('2024-10-12 10:45');
$event2 = new DateTime('2024-10-12 11:00');

$timestamp1 = $event1->getTimestamp();
$timestamp2 = $event2->getTimestamp();

if(abs($timestamp1 - $timestamp2) <= 900) {
echo "\$event1 and \$event2 occur at about the same time";
}
else if($timestamp1 < $timestamp2) {
echo "\$event1 occurs before \$event2";
}
else {
echo "\$event2 occurs before \$event1";
}
// $event1 and $event2 occur at about the same time
?>

For some applications, we might need to make exact comparisons. For example, if a school or college day is split into a number of teaching sessions, each occupying a specific time slot, we could compare the start times of all classes scheduled for a particular classroom on a particular day in order to detect possible timetabling clashes.

Calculating date and time intervals

We typically perform date arithmetic when we want to determine how much time has elapsed, or will elapse, between two dates, or between two instants in time. We might, for example, want to determine a person's current age, given their date of birth. This would involve subtracting their date of birth from the current date using date arithmetic to see how many years (and possibly also months or days) have elapsed since they were born.

We know that PHP can store date and time information in a timestamp, in a DateTime or DateTimeImmutable object, or as a datetime string. Calculating the difference between two timestamps will give us a time interval, in seconds, which we can convert to minutes, hours, days and years for a more meaningful representation of the elapsed time. For example, suppose we want to find someone's current age in years. We could do something like this:

<?php
$dob = new DateTime("1955-02-25");
$today = new DateTime();

$born = $dob->getTimestamp();
$now = $today->getTimestamp();

$age = (int) ($now - $born) / (365.25 * 24 * 60 * 60);
echo "The subject is $age years old.";
// The subject is 70 years old.
?>

In this example, we use two DateTime objects, one to store a person's date of birth and one to store the current date. We then extract a timestamp from each DateTime object, calculate the difference between them to ascertain the person's age in seconds, divide that by the number of seconds in an average year (365.25 × 24 × 60 × 60), and round the result of the calculation down to the nearest integer value using the (int) operator to get the person's age in years.

The above example will give us a person's current age in years, based on their date of birth. It is sufficiently accurate to enable us (for example) to ascertain whether that person is old enough to drive a motor vehicle, or to vote in an election, or to qualify for a pension. Sometimes, however, we need to calculate the time interval between two events with far more precision.

If we want to find the precise interval between one date/time and another date/time, we can do so by using the date_diff() function, which takes as its arguments two DateTime objects that represent the start and end points of the interval we are interested in finding. For example, we could rewrite the previous example as follows:

<?php
$dob = new DateTime("1955-02-25");
$today = new DateTime();
echo "The the subject's date of birth is " . $dob->format("d-m-Y") . ".<br>";
echo "The date today is " . $today->format("d-m-Y") . ".<br>";
$age = date_diff($dob, $today);
echo $age->format("The subject's age is: %y years, %m months, and %d days.");

// The the subject's date of birth is 25-02-1955.
// The date today is 28-12-2025.
// The subject's age is: 70 years, 10 months, and 3 days.
?>

Note that the date_diff() function is an alias of DateTimeInterface::diff(), which is implemented by both the DateTime and DateTimeImmutable classes. The return value is an object that is an instance of the DateInterval class, which we will be looking at shortly.

We can use the date_diff() function to calculate the time interval between two DateTime objects with microsecond precision if required, which means we can calculate the duration of very small intervals as well as very large intervals. Having said that, for calculating extremely small intervals with a duration of tiny fractions of a second, we are probably better off using the hrtime() function.

The high-resolution time function hrtime() takes a single (optional) Boolean argument which defaults to false if not specified. The return value is a two-element array (by default), a number (if the argument is set to true), or false if the function fails for some reason.

If an array is returned, it consists of two integers. The first represents the number of seconds that have elapsed since some arbitrary point in time. The second represents the number of nanoseconds that have elapsed in the current one-second time interval.

If a number is returned it is either a floating-point value (in a 32-bit system) or an integer (in a 64-bit system) that represents the number of elapsed nanoseconds. For example:

<?php
$nanotime1 = hrtime();
$nanotime2 = hrtime(true);
var_dump($nanotime1);
echo "<br>";
var_dump($nanotime2);

// array(2) { [0]=> int(1480263) [1]=> int(344938800) }
// int(1480263344939300)
?>

You might have noticed the slight difference in the nanosecond values in the above example (344939300 vs. 344938800). This reflects the very small time that elapses between the execution of the first two program statements (500 nanoseconds).

To put things into perspective, a nanosecond is one thousand millionth (or one billionth) of a second, so 500 nanoseconds is 0.0000005 seconds, or 0.5 microseconds. This degree of precision makes the hrtime() function well-suited to measuring the execution time of individual procedures within a program. The following example demonstrates how hrtime() might be used to calculate the time taken for a block of code to execute:

<?php
$start = hrtime(true);
for ($i = 0; $i < 1000000; $i++) {
// do nothing one million times!
}
$end = hrtime(true);
$nan = $end - $start;
$sec = $nan / 1e9;
echo "Execution time: $nan nanoseconds ($sec seconds)";

// Execution time: 8515000 nanoseconds (0.008515 seconds)
?>

The DateInterval object

We can store the precise time interval between one date/time and another date/time in a DateInterval object, which can store either a fixed time interval in years, months, days, hours etc., or a datetime string that represents that time interval in a format that is compatible with the DateTime and DateTimeImmutable constructors.

One way to create a DateInterval object is to calculate the difference between two DateTime or DateTimeImmutable objects using the DateTimeInterface::diff() method. This method accepts two arguments, both of which must be objects of type DateTime or DateTimeImmutable, and returns a DateInterval object that represents the difference between them. For example, we could calculate the exact age of the current millennium like this:

<?php
$millStart = new DateTimeImmutable("2000-01-01");
$now = new DateTime();
echo "It is now " . $now->format("Y-m-d H:i:s.u"). "<br>";
$millAge = $millStart->diff($now);
echo "The time elapsed since the start of the millennium is: <br>";
echo $millAge->format("%y years<br>%m months<br>%d days<br>%h hours<br>%i minutes<br>%s seconds<br>%f microseconds");

// It is now 2025-12-27 21:39:21.632599
// The time elapsed since the start of the millennium is:
// 25 years
// 11 months
// 26 days
// 21 hours
// 39 minutes
// 21 seconds
// 632599 microseconds
?>

The DateInterval class is defined as follows:

class DateInterval {
public int $y;
public int $m;
public int $d;
public int $h;
public int $i;
public int $s;
public float $f;
public int $invert;
public mixed $days;
public bool $from_string;
public string $date_string;

public __construct(string $duration)
public static createFromDateString(string $datetime): DateInterval
public format(string $format): string
}

The first six properties are all integer variables that store the time interval as years (y), months (m), days (d), hours (h), minutes (i), and seconds (s). The property that follows the seconds property is a floating-point variable (f) that stores the microsecond value as a fraction of a second.

The next property ($invert) is an integer value that indicates whether the interval represents a negative time interval (1) or a positive time interval (0).

The $invert property is followed by the $days property which the official documentation indicates has the type mixed. If the DateInterval object was created by DateTimeImmutable::diff() or DateTime::diff(), it will be an integer value representing the total number of complete days between the start and end dates, otherwise it will be false (a Boolean value).

The final two properties are dependent upon how the DateInterval object is created. The $fromString property is a Boolean property and will be set to false if the class constructor is used, or if the DateTimeInterface::diff() method is used. If the static createFromDateString() method is used, it will be set to true.

The last property is $date, a datetime string representing a relative datetime period which will only be set if the createFromDateString() method is used to create the DateInterval object. It will be set according to whatever string value is passed to the createFromDateString() method, providing the string represents a valid datetime period. For example:

<?php
$interval = DateInterval::createFromDateString("1 week");
var_dump($interval);

// object(DateInterval)#1 (2) { ["from_string"]=> bool(true) ["date_string"]=> string(6) "1 week" }
?>

if the static DateInterval::createFromDateString() method is used to create a DateInterval object, only the $from_string and $date_string properties are populated, and will be set to true and the string passed to createFromDateString() as an argument, respectively. Otherwise, the $from_string property is set to false, and the $date_string property is left empty.

If we use the DateInterval class constructor method to create a DateInterval object, the argument passed to the constructor method is a string that specifies the duration of the interval in years, months, weeks, days, hours etc., or some combination thereof. For example, we could create a DateInterval object representing an interval of one week as follows:

<?php
$interval = new DateInterval("P7D");
var_dump($interval);

/*
object(DateInterval)#1 (10) {
["y"]=> int(0)
["m"]=> int(0)
["d"]=> int(7)
["h"]=> int(0)
["i"]=> int(0)
["s"]=> int(0)
["f"]=> float(0)
["invert"]=> int(0)
["days"]=> bool(false)
["from_string"]=> bool(false)
}
*/
?>

The string passed to the DateInterval constructor defines the duration and takes a very specific format. It can include the following period designators:



Duration Period Designators
DesignatorDescription
YYears
MMonths
DDays
WWeeks (if used, converted into days)
HHours
MMinutes
SSeconds


The string starts with an uppercase "P” (for period), followed by a number and a period designator for each included datetime component. If both date and time components are included in the duration, they must be separated by an uppercase "T”. If only time components are used, the "T” must still be included and immediately follows the "P”.

Note that, if creating a DateInterval object using two or more period designators, the ordering is important. For example, years must come before months, months before days, and so on. The following code creates a DateInterval object to represent the tropical year (or solar year):

<?php
$tropicalYear = new Dateinterval("P365DT5H48M46S");
var_dump($tropicalYear);
/*
object(DateInterval)#1 (10) {
["y"]=> int(0)
["m"]=> int(0)
["d"]=> int(365)
["h"]=> int(5)
["i"]=> int(48)
["s"]=> int(46)
["f"]=> float(0)
["invert"]=> int(0)
["days"]=> bool(false)
["from_string"]=> bool(false)
}
*/
?>

Date and time arithmetic

We can use a DateInterval objects together with DateTime or DateTimeImmutable objects to create new DateTimeImmutable object, or a new or modified DateTime object, that represent some past or future date and/or time. For example, suppose we have a meeting to discuss a major project on a fixed date and wish to schedule a follow up meeting six weeks from that date to review progress. We might do something like the following:

<?php
$initialMeeting = new DateTimeImmutable("2026-01-07 15:30");
$reviewPeiod = new DateInterval("P6W");
$firstReviewDate = $initialMeeting->add($reviewPeiod);
echo "Initial meeting: " . $initialMeeting->format("d-m-Y H:i") . "<br>";
echo "Review meeting: " . $firstReviewDate->format("d-m-Y H:i");

// Initial meeting: 07-01-2026 15:30
// Review meeting: 18-02-2026 15:30
?>

In the above example, $initialMeeting and $firstReviewDate are DateTimeImmutable objects, and $reviewPeiod is a DateInterval object representing a period of six weeks. We use the DateTimeImmutable::add() method to add the period represented by $reviewPeiod to the date represented by $initialMeeting, and assign the result to $firstReviewDate. We use a DateTimeImmutable object to store the date and time of the initial project meeting because we don't want to change this object's properties.

Alternatively, we can achieve exactly the same result as in the previous example using the DateTimeImmutable::modify() method, which accepts any valid datetime string, as demonstrated by the following code:

<?php
$initialMeeting = new DateTimeImmutable("2026-01-07 15:30");
$firstReviewDate = $initialMeeting->modify("+6 weeks");
echo "Initial meeting: " . $initialMeeting->format("d-m-Y H:i") . "<br>";
echo "Review meeting: " . $firstReviewDate->format("d-m-Y H:i");

// Initial meeting: 07-01-2026 15:30
// Review meeting: 18-02-2026 15:30
?>

Subtracting a period of time from a DateTime or DateTimeImmutable object can be achieved in a similar manner, using either of the above methods. Let's say we want to start a search for documents bearing a date within the last six months. We could find the precise start date for our search by doing something like the following:

<?php
$now = new DateTime();
$now->setTime(0,0);
echo $now->format("d-m-Y H:i:s");
echo "<br>";
$searchInterval = new DateInterval("P6M");
$startFromDate = $now->sub($searchInterval);
echo $startFromDate->format("d-m-Y H:i:s");
// 04-01-2026 00:00:00
// 04-07-2025 00:00:00
?>

We could achieve the same result using the DateTimeImmutable::modify() method:

<?php
$now = new DateTime();
$now->setTime(0,0);
echo $now->format("d-m-Y H:i:s");
echo "<br>";
$startFromDate = $now->modify("-6 months");
echo $startFromDate->format("d-m-Y H:i:s");
// 04-01-2026 00:00:00
// 04-07-2025 00:00:00
?>

In the above examples, we obtain the current date by using the DateTime constructor without supplying any arguments, and then called the setTime() method in order to set the current time to 00:00 hours, because we want to make sure that all documents created six months ago or later are included in our search regardless of the time of day they were created.

When trying to determine whether some past date occurs before or after a given point in time, bear in mind that you cannot compare two DateInterval objects directly. We can however use a DateInterval object to establish a target date, and then compare other dates with the target date in order to establish whether they occur before, after, or on that date.

We can also use the DateTimeInterface::diff() method to establish the time that has elapsed between two dates, as we have seen previously. For example, suppose we have a company policy that employees must change their network login password every six months for security reasons. We might do something like the following:

<?php
$PasswordTTL = new DateInterval("P6M");
$now = new DateTime();
$now->setTime(0,0);
echo "Today is: " . $now->format("d-m-Y") . ".";
echo "<br>";
$oldest = $now->sub($PasswordTTL);
$lastLogin = new DateTime("2025-10-23");
echo "Last login: " . $lastLogin->format("d-m-Y") . ".";
if($lastLogin < $oldest) {
echo "<br><br>The password has expired.";
}
else {
$daysLeft = $oldest->diff($lastLogin);
echo "<br>The password expires in " . $daysLeft->format("%a") . " days.";
}
// Today is: 04-01-2026.
// Last login: 23-10-2025.
// The password expires in 111 days.
?>

When adding or subtracting times we are usually interested in much smaller time intervals than when adding or subtracting dates. We have already seen examples of using the hrtime() function to find the execution time in nanoseconds of a block of code. For most day-to-day purposes, however, we are normally only interested in time intervals that can be expressed in terms of hours, minutes and seconds.

Nevertheless, we can still use DateTime and DateInterval objects to add a fixed time interval to some specific point in time and achieve results that are precise enough for most purposes. For example, suppose we have some industrial process such as heat treatment that takes a precise amount of time. We could do something like this:

<?php
$start = new DateTime();
echo "Process started: " . $start->format("d-m-Y H:i:s") . ".";
echo "<br>";
$processTime = new DateInterval("PT2H45M");
echo "Processing time: " . $processTime->format("%hh %im %ss") . ".";
echo "<br>";
$completion = $start->add($processTime);
echo "Process completion: " . $completion->format("d-m-Y H:i:s") . ".";
// Process started: 04-01-2026 15:07:32.
// Processing time: 2h 45m 0s.
// Process completion: 04-01-2026 17:52:32.
?>

We can also use the DateTimeImmutable::modify() method to achieve the same result. For example:

<?php
$start = new DateTime();
echo "Process started: " . $start->format("d-m-Y H:i:s") . ".";
echo "<br>";
$processTime = new DateInterval("PT2H45M");
echo "Processing time: " . $processTime->format("%hh %im %ss") . ".";
echo "<br>";
$completion = $start->modify("+" . $processTime->format("%h hours, %i minutes, %s seconds"));
echo "Process completion: " . $completion->format("d-m-Y H:i:s") . ".";
// Process started: 04-01-2026 15:35:05.
// Processing time: 1h 45m 0s.
// Process completion: 04-01-2026 17:20:05.
?>

Subtracting a fixed amount of time from some specific point in time can be achieved in similar fashion using DateTime and DateInterval objects. We have already seen how to calculate the difference between two DateTime or DateTimeImmutable objects. Now we want to be able to calculate the precise time at which some past event occurred, or when some ongoing process started, if we know the amount of time that has elapsed since that time. For example:

<?php
$stopWatchTime = new DateInterval("PT16M45S");
$now = new DateTimeImmutable();
$startTime = $now->sub($stopWatchTime);
echo "The time now is " . $now->format("H:i:s") . "<br>";
echo "Time elapsed: " . $stopWatchTime->format("%hh:%im:%ss<br>");
echo "Start time: " . $startTime->format("H:i:s");
// The time now is 12:39:17
// Time elapsed: 0h:16m:45s
// Start time: 12:22:32
?>

As previously, we can use the DateTimeImmutable::modify() method to achieve the same result. For example:

<?php
$stopWatchTime = new DateInterval("PT16M45S");
$now = new DateTimeImmutable();
$startTime = $now->modify($stopWatchTime->format("-%h hours, -%i minutes, -%s seconds"));
echo "The time now is " . $now->format("H:i:s") . "<br>";
echo "Time elapsed: " . $stopWatchTime->format("%hh:%im:%ss<br>");
echo "Start time: " . $startTime->format("H:i:s");
// The time now is 13:00:51
// Time elapsed: 0h:16m:45s
// Start time: 12:44:06
?>

The DatePeriod class

Objects of the DatePeriod class can be used to store an iterable sequence of dates and times that occur at regular intervals over some specified period of time. The DatePeriod class is defined as follows:

class DatePeriod implements IteratorAggregate {

/* Constants */
public const int EXCLUDE_START_DATE;
public const int INCLUDE_END_DATE;

/* Properties */
public readonly ?DateTimeInterface $start;
public readonly ?DateTimeInterface $current;
public readonly ?DateTimeInterface $end;
public readonly ?DateInterval $interval;
public readonly int $recurrences;
public readonly bool $include_start_date;
public readonly bool $include_end_date;

/* Methods */
public __construct(
DateTimeInterface $start,
DateInterval $interval,
int $recurrences,
int $options = 0
)
public __construct(
DateTimeInterface $start,
DateInterval $interval,
DateTimeInterface $end,
int $options = 0
)
public __construct(string $isostr, int $options = 0)
public static createFromISO8601String(string $specification, int $options = 0): static
public getDateInterval(): DateInterval
public getEndDate(): ?DateTimeInterface
public getRecurrences(): ?int
public getStartDate(): DateTimeInterface
}

As you can see, the DatePeriod class provides three constructor methods, although the third variant (public __construct(string $isostr, int $options = 0)) has now been deprecated. The first constructor takes four arguments. The first of these is a DateTimeInterface object that provides the start date for the period. The second argument is a DateInterval object that defines the interval between each of the recurring dates.

The third argument is an integer that specifies the number of recurrences. The total number of dates stored in the DatePeriod object is the number of recurrences plus one - the start date - if the (optional) fourth argument is omitted.

The fourth argument is an integer value that can be used either to exclude the start date from the sequence of stored dates, or to include an end date (essentially tacking an additional date onto the end of the sequence), using the constants EXCLUDE_START_DATE or INCLUDE_END_DATE respectively. If omitted, the argument defaults to 0. For example:

<?php
$start = new DateTime("2026-01-09 15:00");
$interval = new DateInterval("P7D");
$recurrences = 4;
$period = new DatePeriod($start, $interval, $recurrences);
echo "Dates:<br>";
foreach($period as $date) {
echo $date->format("d-m-Y H:i") . "<br>";
}
echo "<br>Start date: " . $period->start->format("d-m-Y H:i");
echo "<br>Recurrences: " . $period->recurrences;
// Dates:
// 09-01-2026 15:00
// 16-01-2026 15:00
// 23-01-2026 15:00
// 30-01-2026 15:00
// 06-02-2026 15:00
//
// Start date: 09-01-2026 15:00
// Recurrences: 5
?>

Note that each date stored in the DatePeriod object is either a DateTime object or a DateTimeImmutable object, depending on whether or not the object passed to the DatePeriod constructor as the start date is of type DateTime or DateTimeImmutable.

We can use the EXCLUDE_START_DATE constant to exclude the start date from the sequence of dates stored in a DatePeriod object like this:

<?php
$start = new DateTime("2026-01-09 15:00");
$interval = new DateInterval("P7D");
$recurrences = 4;
$option = DatePeriod::EXCLUDE_START_DATE;
$period = new DatePeriod($start, $interval, $recurrences, $option);
echo "Dates:<br>";
foreach($period as $date) {
echo $date->format("d-m-Y H:i") . "<br>";
}
echo "<br>Start date is excluded.";
echo "<br>Recurrences: " . $period->recurrences;
// Dates:
// 16-01-2026 15:00
// 23-01-2026 15:00
// 30-01-2026 15:00
// 06-02-2026 15:00
//
// Start date is excluded.
// Recurrences: 4
?>

If we instead want to add an additional date to the end of the sequence, we can use the INCLUDE_END_DATE constant, as per this example:

<?php
$start = new DateTime("2026-01-09 15:00");
$interval = new DateInterval("P7D");
$recurrences = 4;
$option = DatePeriod::INCLUDE_END_DATE;
$period = new DatePeriod($start, $interval, $recurrences, $option);
echo "Dates:<br>";
foreach($period as $date) {
echo $date->format("d-m-Y H:i") . "<br>";
}
echo "<br>Start date: " . $period->start->format("d-m-Y H:i");
echo "<br>Recurrences: " . $period->recurrences;
// Dates:
// 09-01-2026 15:00
// 16-01-2026 15:00
// 23-01-2026 15:00
// 30-01-2026 15:00
// 06-02-2026 15:00
// 13-02-2026 15:00
//
// Start date: 09-01-2026 15:00
// Recurrences: 6
?>

Note that we do not need to specify an end date for the DatePeriod object for this constructor. The end date is determined by the value of $recurrences, and whether or not we use the INCLUDE_END_DATE constant for the optional fourth argument. Note also, however, that calling the getEndDate() method will cause an error since we have not defined an end date as such.

The second constructor also takes four arguments. The first and second arguments are the same as for the first constructor - a DateTimeInterface object that provides the start date for the period, and a DateInterval object that defines the interval between each of the recurring dates, respectively.

The third argument is a DateTimeInterface object that provides the end date for the period. This time, the number of dates stored will be dependent on the end date rather than the other way around. For example:

<?php
$start = new DateTime("2026-01-09 15:00");
$end = new DateTime("2026-02-13 15:00");
$interval = new DateInterval("P7D");
$period = new DatePeriod($start, $interval, $end);
echo "Dates:<br>";
foreach($period as $date) {
echo $date->format("d-m-Y H:i") . "<br>";
}
echo "<br>Start date: " . $period->start->format("d-m-Y H:i");
echo "<br>End date: " . $period->end->format("d-m-Y H:i");
echo "<br>Recurrences: " . $period->recurrences;
// Dates:
// 09-01-2026 15:00
// 16-01-2026 15:00
// 23-01-2026 15:00
// 30-01-2026 15:00
// 06-02-2026 15:00
//
// Start date: 09-01-2026 15:00
// End date: 13-02-2026 15:00
// Recurrences: 1
?>

Because the number of recurrences is not explicitly passed to the constructor, the number of recurrences reported is the minimum number of dates that must be stored (0), plus 1 if the start date is not excluded. Note also that the specified end date (13-02-2026) is, by default, not included in the sequence of dates stored in the DatePeriod object, even though we have provided an end date to the constructor.

If we use the EXCLUDE_START_DATE constant for the optional fourth argument, the start date will not be included in the sequence of stored dates, as we have already seen. If we use the INCLUDE_END_DATE constant for this argument, the result will depend on whether or not the end date we specified falls on one of the defined intervals.

If it the end date coincides with the end of an interval, and the INCLUDE_END_DATE constant has been used for the fourth argument, it will be included in the sequence of stored dates. For example:

<?php
$start = new DateTime("2026-01-09 15:00");
$end = new DateTime("2026-02-13 15:00");
$interval = new DateInterval("P7D");
$option = $option = DatePeriod::INCLUDE_END_DATE;
$period = new DatePeriod($start, $interval, $end, $option);
echo "Dates:<br>";
foreach($period as $date) {
echo $date->format("d-m-Y H:i") . "<br>";
}
echo "<br>Start date: " . $period->start->format("d-m-Y H:i");
echo "<br>End date: " . $period->end->format("d-m-Y H:i");
echo "<br>Recurrences: " . $period->recurrences;
// Dates:
// 09-01-2026 15:00
// 16-01-2026 15:00
// 23-01-2026 15:00
// 30-01-2026 15:00
// 06-02-2026 15:00
// 13-02-2026 15:00
//
// Start date: 09-01-2026 15:00
// End date: 13-02-2026 15:00
// Recurrences: 2
?>

This time, because we have not excluded the start date and have included the end date, the number of recurrences reported (i.e. the minimum number of dates that must be stored) is 2.

If the end date does not coincide with the end of an interval, or if the fourth argument is omitted, then the end date will not be included in the list of iterable dates stored in the DatePeriod object. For example:

<?php
$start = new DateTime("2026-01-09 15:00");
$end = new DateTime("2026-02-13 15:00");
$interval = new DateInterval("P7D");
$period = new DatePeriod($start, $interval, $end);
echo "Dates:<br>";
foreach($period as $date) {
echo $date->format("d-m-Y H:i") . "<br>";
}
echo "<br>Start date: " . $period->start->format("d-m-Y H:i");
echo "<br>End date: " . $period->end->format("d-m-Y H:i");
echo "<br>Recurrences: " . $period->recurrences;
// Dates:
// 09-01-2026 15:00
// 16-01-2026 15:00
// 23-01-2026 15:00
// 30-01-2026 15:00
// 06-02-2026 15:00
//
// Start date: 09-01-2026 15:00
// End date: 13-02-2026 15:00
// Recurrences: 1
?>

Note that if the end date is included by using the INCLUDE_END_DATE constant for the constructor's fourth argument, the number of recurrences reported will still be 2, even if the end date is not included in the iterable list of dates stored in the DatePeriod object because it does not coincide with the end of an interval. This would appear to be an oversight of sorts, but should not cause any problems, as long as you don't rely on it to determine the minimum number of iterable dates that must be stored in the DatePeriod object.

Most of the public methods defined for the DatePeriod class (getDateInterval(), getStartDate(), getEndDate() and getRecurrences()) are fairly self explanatory. The static createFromISO8601String() method, as its name suggests, creates a DatePeriod object from an ISO8601 string, which takes the form Rn/<interval>. This method is only supported in PHP versions 8.3.0 and higher (the highest version provided by the latest versions of XAMPP for Windows or Linux at the time of writing is PHP 8.2.12).

Time zones - an overview

A timestamp is an integer value that represents a precise instant in time. It specifies the time according to Coordinated Universal Time (UTC) or its equivalent Greenwich Mean Time (GMT). When that is translated into a human-readable form, however, the date and time displayed to the user is normally dependent on the user's locale. In other words, it is the local date and time that will be displayed on the user's device rather than the UTC date and time.

The user's local time will depend on what timezone they are in, which will in turn depend - to a large extent - on their longitude. Longitude is a measure of how far west or east a location is with respect to the Prime Meridian. Lines of longitude (or meridians) are 360 lines that run from the North Pole to the South pole, spaced at 1° intervals. The Prime Meridian is assigned a longitude of 0°, and is the line of longitude that passes through Greenwich, England.

Meridians to the west of the Prime Meridian are assigned a negative value based on how many degrees they are to west of it. Likewise, meridians to the east of the Prime Meridian have a positive value based on how many degrees they are east of it. The line that lies exactly ±180° from the prime meridian (i.e. the line directly opposite to it on the globe) is called the Antemeridian, and is often referred to as the International Date Line (IDL).

There are 24 equally-spaced nominal time zones around the globe, each of which is 15° degrees wide. The first of these is the Greenwich Mean Time (GMT) timezone, which is centred on the Prime Meridian. Each timezone is separated from its neighbours by one hour. The eleven time zones to the west of the GMT timezone are ahead of GMT, whilst the eleven time zones to its east are behind GMT. The eastern half of the timezone centred on the Antemeridian can be considered to be 12 hours ahead of GMT, while the western half is 12 hours behind GMT.

In reality, the picture is more complicated. Many countries, for example, are simply too large to occupy a single timezone, while other countries, even if small enough to lie within a single timezone, lie on the boundary between two adjacent time zones. It would obviously make no sense to have to adjust your watch each time you step over an imaginary line. Each country or state must therefore formulate its own policy with respect to adopting time zones. The graphic below shows the time zones currently used in Europe.

An added complication is that some countries adopt daylight saving schemes while others do not. Those that do will have a different time offset from UTC at different times of the year. Unfortunately, the sheer volume of information available with respect to different locales and the time zones they use renders it impractical to attempt to provide a comprehensive listing here. There are however a number of online sources available if you just need to find current timezone information for one or more specific locations. A good place to start might be the Time and Date website.

We want to ensure, however, that we are able to display the correct local time for a specific time-zone regardless of whether or not that timezone is affected by daylight saving. In order to do so, we need to be able to refer to the correct timezone by name, because specifying an offset in hours and minutes is a hard-wired solution that does not allow for changes in the offset due to daylight saving.

The Internet Assigned Numbers Authority (IANA), which is responsible for the assignment of IP addresses, also maintains a database of representative locations and the time zones in which they reside. You can find a current (at the time of writing) text-based listing of IANA timezone locations here. An IANA representative location uses a country and/or region name followed by the name of a city (usually the most populous in that country or region). For example, Greece has only one timezone, which is represented by "Europe/Athens".

The time displayed is automatically adjusted for daylight savings if that is in effect in the specified locale. Note that most browsers use the Unicode Common Locale Data Repository (CLDR) database. There are well over three hundred representative locations in both the CLDR database and the IANA database. Be aware, however, that there is not a strict one-to-one correspondence. Some of the CLDR representative locations differ from their counterparts in the IANA database. You can find the most recent CLDR listing of representative locations here.

Working with time zones

PHP provides a DateTimeZone class to facilitate working with time zones. This class enables us to display the time in different time zones without having to jump through too many hoops. It also enables us to quickly calculate the difference between two time zones. The DateTimeZone class is defined as follows:

class DateTimeZone {
/* Constants */
public const int AFRICA;
public const int AMERICA;
public const int ANTARCTICA;
public const int ARCTIC;
public const int ASIA;
public const int ATLANTIC;
public const int AUSTRALIA;
public const int EUROPE;
public const int INDIAN;
public const int PACIFIC;
public const int UTC;
public const int ALL;
public const int ALL_WITH_BC;
public const int PER_COUNTRY;
/* Methods */
public __construct(string $timezone)
public getLocation(): array|false
public getName(): string
public getOffset(DateTimeInterface $datetime): int
public getTransitions(int $timestampBegin = PHP_INT_MIN, int $timestampEnd = 2147483647): array|false
public static listAbbreviations(): array
public static listIdentifiers(int $timezoneGroup = DateTimeZone::ALL, ?string $countryCode = null): array
}

Being able to determine what the current time is in another part of the world is particularly important for financial and commercial organisations that engage in global trade. Knowing when financial markets in a particular part of the world open and close, or when an overseas trading partner is contactable via telephone, is crucial to many organisations.

PHP currently supports 419 different time zones, which we can list using the static listIdentifier() method of the DateTimeZone class. This method returns an array of time zones, as demonstrated by the following example:

<?php
$number = 1;
$timezones = DateTimeZone::listIdentifiers();
foreach ($timezones as $timezone) {
echo $number++ . ". ";
echo $timezone . "<br>";
}
// 1. Africa/Abidjan
// 2. Africa/Accra
// 3. Africa/Addis_Ababa
.
.
.
// 417. Pacific/Wake
// 418. Pacific/Wallis
// 419. UTC
?>

The total number of time zones listed is obviously many times greater than the nominal 24 time zones we would expect to see if time zones were purely dependent on the 15-degree spacing between meridian lines. As we have mentioned in our overview of time zones, however, the situation is complicated by the fact that many large countries span multiple time zones. There are also geopolitical and commercial factors to consider, as well as the adoption or otherwise of daylight-saving measures in different parts of the world.

As you can probably see, the time zones are grouped together in regions such as Africa, America, Asia, Europe, and so on. The timezone name is comprised of this regional name, typically followed by the name of a major population centre like New York (America/New_York), a group of islands such as the Galapagos (Pacific/Galapagos), or a single island like Mauritius (Indian/Mauritius).

If we are only interested in a certain part of the world, we can narrow the list down using one or both of the two optional parameters ($timezoneGroup and $countryCode), which default to DateTimeZone::ALL and null respectively. For example, we can limit the list to just one regional group:

<?php
$number = 1;
$timezones = DateTimeZone::listIdentifiers(DateTimeZone::AMERICA);
foreach ($timezones as $timezone) {
echo $number++ . ". ";
echo $timezone . "<br>";
}
// 1. America/Adak
// 2. America/Anchorage
// 3. America/Anguilla
.
.
.
// 141. America/Whitehorse
// 142. America/Winnipeg
// 143. America/Yakutat
?>

Or we can restrict the listing even further by limiting it to a single country. For example:

<?php
$number = 1;
$timezones = DateTimeZone::listIdentifiers(DateTimeZone::PER_COUNTRY, "US");
foreach ($timezones as $timezone) {
echo $number++ . ". ";
echo $timezone . "<br>";
}
// 1. America/Adak
// 2. America/Anchorage
// 3. America/Boise
.
.
.
// 27. America/Sitka
// 28. America/Yakutat
// 29. Pacific/Honolulu
?>

We get an interesting result when we use the listIdentifiers() method to list the time zones for China. Not only is China the largest sovereign nation in the world by population, ahead of India in second place and the USA in third place, it is the third largest country on earth by landmass after Russia and Canada. This is what happens:

<?php
$number = 1;
$timezones = DateTimeZone::listIdentifiers(DateTimeZone::PER_COUNTRY, "CN");
foreach ($timezones as $timezone) {
echo $number++ . ". ";
echo $timezone . "<br>";
}
// 1. Asia/Shanghai
// 2. Asia/Urumqi
?>

In fact, neither of these timezone identifiers is strictly correct. China has only one official timezone which is named Beijing Time, although the Asia/Shanghai and Asia/Beijing time zone identifiers are essentially synonymous. The apparent anomaly is due to the rules used to select representative locations for the IANA timezone database, on which PHP bases its timezone identifier list (Shanghai has a larger population than Beijing, despite Beijing being the capital).

There is also a method available to list the abbreviations used for time zones. The static listAbbreviations() method takes no arguments, and returns a multi-dimensional array of timezone abbreviations . However, according to the PHP Group's website:

"The returned list of abbreviations includes all historical use of abbreviations, which can lead to correct, but confusing entries. There are also conflicts, as PST is used both in the US and in the Philippines. . . . The list that this function returns is therefore not suitable for building an array with options to present a choice of timezone to users. . . . Note: The data for this function are precompiled for performance reasons, and are not updated when using a newer [timezone database].”

If we do not specify a timezone when creating a DateTime object, the timezone used will be the local timezone. Actually, it would be more accurate to say that it will be whatever timezone is specified by the date.timezone property in the php.ini configuration file on the server, which is usually set according to the timezone in which the server physically resides.

If we need to specify a timezone that is different to the server's default timezone - maybe because we are writing a script that will be used in a different timezone - we can set a different default timezone for our script using the date_default_timezone_set() function. We can also retrieve information about the timezone currently in effect using the date_default_timezone_get() function. For example:

<?php
date_default_timezone_set("Europe/London");
.
.
.
$timezone = date_default_timezone_get();
echo "The default timezone is: $timezone.";
// The default timezone is: Europe/London.
?>

The DateTimezone class also provides the getName() and getLocation() methods for retrieving information about the timezone currently in effect, as illustrated by the following example:

<?php
date_default_timezone_set("Europe/London");
// .
// .
// .
$tzName = date_default_timezone_get();
$timezone = new DateTimezone($tzName);
echo "The default timezone is: " . $timezone->getName() . "<br><br>";
$location = $timezone->getLocation();
foreach($location as $key=>$value) {
echo "$key: $value<br>";
}
// The default timezone is: Europe/London
//
// country_code: GB
// latitude: 51.50833
// longitude: -0.1252800000000036
// comments:
?>

We can use a DateTimeZone object to determine the current time virtually anywhere in the world. For example, suppose we want to know both the local time and the current time in New York. We could do something like this:

<?php
echo "My server timezone is: " . date_default_timezone_get() . "<br>";
$localTime = new DateTime();
echo "The current local time is: " . $localTime->format('l, d-m-Y, H:i:s') . "<br>";
$NYdateTime = new DateTime("now", new DateTimeZone("America/New_York"));
echo "The current time in New York is: " . $NYdateTime->format('l, d-m-Y, H:i:s');
// My server timezone is: Europe/Berlin
// The current local time is: Thursday, 15-01-2026, 20:12:47
// The current time in New York is: Thursday, 15-01-2026, 14:12:47
?>

We actually do quite a lot in the above example in one line of code. We create a new DateTime object ($NYdateTime) using "now” as the first argument to get the current date and time, and then create a new DateTimezone object for the (optional) second argument, to provide the timezone information. As you can see, the DateTimezone constructor takes a single argument - a string containing the timezone identifier, in this case "America/New_York".

The date_default_timezone_get() function takes no arguments, and returns the timezone identifier for the default timezone used by all date- and time-related functions in a script. If the default timezone has been set using the date_default_timezone_set() function, the identifier for that timezone is returned. Otherwise, the timezone identifier specified for the date.timezone property in the php.ini configuration file is returned, if set. If the date.timezone property has not been set, the timezone identifier defaults to "UTC”.

Using a DateTimeZone object, we can find the current time in any one of the 419 time zones supported by PHP, determine the time difference between our own timezone and some other timezone (or between any two time zones), and calculate the offset from UTC of a given timezone. To find the current UTC offset in seconds for a particular timezone, we can use the getOffset() method provided by the DateTimezone class. For example:

<?php
$timezone = new DateTimeZone("Europe/Berlin");
$winter = new DateTime("2026-01-01");
$summer = new DateTime("2026-06-01");
$utcOffsetWinter = $timezone->getOffset($winter);
$utcOffsetSummer = $timezone->getOffset($summer);
echo "The UTC offset for Germany in winter is $utcOffsetWinter.<br>";
echo "The UTC offset for Germany in summer is $utcOffsetSummer.";
// The UTC offset for Germany in winter is 3600.
// The UTC offset for Germany in summer is 7200.
?>

The time zone in Germany is Central European Time (CET), which is one hour ahead of UTC. However, Germany currently implements Central European Summer Time (CEST) during the summer months, which is two hours ahead of UTC. As a result, we get two different values for Germany's UTC offset, depending on whether the DateTime object passed to getOffset() represents a date in summer or winter. The time in Germany is thus one hour (3600 seconds) ahead of UTC in winter, and two hours (3600 seconds) ahead in summer.

The getOffset() method extracts the timestamp from the DateTime argument provided to it as its argument in order to determine the date on which to calculate the timezone's offset from UTC. As we have seen, this is necessary because the offset will vary depending on whether or not daylight saving (or some other factor) is in effect in the timezone in question.

It can also be the case that a county's policy with respect to daylight saving changes over time. Brazil, for example, applied daylight saving (mostly) uniformly between 1931 and 1987. From 1988 to 2019, daylight saving varied across Brazil on a regional basis, mainly to accommodate regional differences in the number of daylight hours. As of 2020, Brazil abolished daylight saving altogether.

We can get information about when a timezone transitions from (for example) standard time to daylight saving and back again using the getTransitions() method of the DateTimeZone class. This method takes two arguments, both of which are timestamps. The first argument signifies the start of the period we are interested in and the second argument signifies the end of that period. The value returned is a multi-dimensional array containing details of all transitions (if any) that occur during the specified period.

Let's suppose we want to find out what kind of timezone-related transitions occurred in the period between January 1, 2025 and December 31, 2025 in the United Kingdom. We could do something like this:

<?php
date_default_timezone_set("UTC");
$timezone = new DateTimeZone("Europe/London");
$start = strtotime("2026-01-01");
$end = strtotime("2026-12-31");
$transitions = $timezone->getTransitions($start, $end);
foreach($transitions as $transition) {
foreach($transition as $key=>$value) {
echo "$key: $value<br>";
}
echo "<br>";
}
// ts: 1767225600
// time: 2026-01-01T00:00:00+00:00
// offset: 0
// isdst:
// abbr: GMT
//
// ts: 1774746000
// time: 2026-03-29T01:00:00+00:00
// offset: 3600
// isdst: 1
// abbr: BST
//
// ts: 1792890000
// time: 2026-10-25T01:00:00+00:00
// offset: 0
// isdst:
// abbr: GMT
?>

We have set the default timezone for our script to UTC in this example using the date_default_timezone_set() function. We do this because we want to relate the script's output to Coordinated Universal Time rather than whatever timezone the server happens to reside in (the technologyuk.net server is located in Germany). But what is the information displayed actually telling us about the transitions in the UK during 2026?

The array returned by getTransitions() holds three elements, each of which is an associative subarray comprising five key-value pairs. The first of these subarrays does not represent a transition as such - it simply gives us information about the timezone at the start of the specified period. The remaining two subarrays contain information about actual transitions (in this case, the transitions from GMT to BST and back again).

The first element in each subarray is a timestamp. In the first subarray, it is the timestamp for the start of the period we are interested in. In the remaining two subarrays, the timestamp represents the instant at which a transition occurs.

The second element is a datetime string in standard ISO 8601 format. It gives us the UTC date and time corresponding to the timestamp in human-readable form. The third array element is the offset (in seconds) from UTC, and the fourth element is a Boolean value that indicates whether or not daylight saving is in effect. The last element is the timezone abbreviation commonly used to represent the timezone after a given transition occurs.

So, what does the output from our script tell us? To summarise, at 00:00 hours on January 1, 2006, the United Kingdom is using Greenwich Mean Time (GMT). The time in the UK is thus aligned with UTC, since GMT and UTC are essentially synonymous. At 01:00 on March 29 2026, the UK transitions to British Summer Time (BST), which is one hour (3600 seconds) ahead of UTC. At 01:00 on October 25 2026, the UK transitions back to GMT, once more aligning itself with UTC.

As well as being able to find out what the current time is in another part of the world at any given moment, it is sometimes useful to be able to calculate the difference between two time zones. This is not as straightforward as it might seem because some countries implement daylight saving and some don't - and even if two countries both implement daylight saving the periods involved may differ in terms of their start and end dates.

Let's say we want to find the current time difference between Los Angeles and Tokyo. We could do something like the following:

<?php
$losAngeles = new DateTime("2026-01-01", new DateTimezone("America/Los_Angeles"));
$tokyo = new DateTime("2026-01-01", new DateTimezone("Asia/Tokyo"));
$timeDifference = $losAngeles->diff($tokyo);
echo $timeDifference->format("The time difference between Los Angeles and Tokyo is %R%H hours %I minutes");
// The time difference between Los Angeles and Tokyo is -17 hours 00 minutes
?>

From this result, we can determine that Los Angeles is exactly 17 hours behind Tokyo on January 1 2026. Putting this another way, when it is 00:00 hours on January 1, 2026 in Los Angeles, it will be 17:00 on the same day in Tokyo.

There may be occasions when we want to change the timezone of a DateTime object for some reason. For example, we might want to advise an overseas associate of the time and date of some forthcoming event. We can do this using the setTimezone() method provided by the DateTime class. For example:

<?php
$liveBroadcast = new DateTime("2026-01-31 16:30", new DateTimezone("Europe/London"));
echo "Live broadcast event:<br><br>";
echo $liveBroadcast->format("H:i T, l, dS F, Y") . " (for UK studio audience).<br>";
$liveBroadcast->setTimezone(new DateTimeZone("America/New_York"));
echo $liveBroadcast->format("H:i T, l, dS F, Y") . " (for New York studio audience).";
// Live broadcast event:
//
// 16:30 GMT, Saturday, 31st January, 2026 (for UK studio audience).
// 11:30 EST, Saturday, 31st January, 2026 (for New York studio audience).
?>

What should become clear to you by now is that keeping track of dates and times in a global environment is not a trivial task. Using DateTimezone and DateTime objects, the programmer is able to get the current date and time for a particular location or calculate the time difference between two different locations without having to worry about factors such as daylight saving.

Internationalisation

As we have seen, we can set a default timezone for a script to ensure that the user will see the correct date and time information for their location regardless of the default timezone specified in the server's php.ini configuration file or its physical location. However, the format in which that information is presented can vary, even among English-speaking countries.

In the United States, for example, the date format used is mm-dd-yyyy as opposed to the more widely-used dd-mm-yyyy format. This could lead to confusion. For example, the date "10-08-2026” would be interpreted in the United Kingdom (and many other countries) as the 10th day of August, 2026. In the USA, it would be interpreted as the 8th day of October, 2026.

Of course, we can always specify the correct format in which to display date and time information to a user in a particular locale if we are familiar with the requirements for that particular locale. If not, we might have to do some research before writing our code, which could be somewhat tedious.

Fortunately, PHP provides the IntlDateFormatter class, which makes it much easier for us to format dates and times appropriately for a specific locale. The complete IntlDateFormatter class definition can be found on the PHP Group website. A full exploration of the features of this class is somewhat beyond the scope of this article, but the following example should give you some idea of how it can be used:

<?php
date_default_timezone_set("Europe/London");
$formatter = new IntlDateFormatter("en-GB");
echo $formatter->format(new DateTime()) . "<br>";
date_default_timezone_set("Europe/Berlin");
$formatter = new IntlDateFormatter("de-DE");
echo $formatter->format(new DateTime()) . "<br>";
date_default_timezone_set("Europe/Athens");
$formatter = new IntlDateFormatter("el-GR");
echo $formatter->format(new DateTime()) . "<br>";
date_default_timezone_set("Europe/Moscow");
$formatter = new IntlDateFormatter("ru_Ru");
echo $formatter->format(new DateTime());

// Tuesday, 20 January 2026 at 11:50:09 Greenwich Mean Time
// Dienstag, 20. Januar 2026 um 12:50:09 Mitteleuropäische Normalzeit
// Τρίτη 20 Ιανουαρίου 2026 - 1:50:09 μ.μ. Χειμερινή ώρα Ανατολικής Ευρώπης
// вторник, 20 января 2026 г., 14:50:09 Москва, стандартное время
?>

This script outputs the current date and time in full, including the day of the week, the name of the month, and the standard timezone information, for each of four different countries (the United Kingdom, Germany, Greece and Russia* respectively). Note that the default timezone has to be set for each country individually, otherwise the date and time displayed will be determined by whatever default timezone has been set in the php.ini configuration file on the server, regardless of the chosen format.

Note: If you try running this script and receive the error message "Fatal error: Uncaught Error: Class "IntlDateFormatter" not found . . . ”, you should uncomment the following line in your php.ini configuration file (simply delete the semicolon prefix):

;extension=intl

* Russia has eleven time zones; we have used Moscow time for this example.