Signature
date_create_immutable(string $datetime = "now", ?DateTimeZone $timezone = null): DateTimeImmutable|falsePurpose
date_create_immutable() turns a date/time string into a DateTimeImmutable object. It is the procedural counterpart of new DateTimeImmutable(...). The key difference is how it reports bad input: the constructor throws an exception, while this function quietly returns false.
The returned object is immutable. Methods such as modify(), setTimezone() or setDate() never change the original object. They return a new one, so a date you passed around cannot be altered by accident.
Signature
Parameters and defaults
$datetime: a string in any format PHP's date parser understands, such as2024-03-15 10:30:00,2024-03-15T10:30:00+02:00,@86400or a relative phrase like+1 week. The default is"now", which depends on the current clock.$timezone: an optionalDateTimeZoneobject. The default isnull, meaning the script's default timezone is used (seedate_default_timezone_set()). The argument is only a fallback: if$datetimealready carries its own offset or zone name, that value wins and$timezoneis ignored.
The accepted formats and their rules are the same as for DateTimeImmutable::__construct().
Return value and failure
On success you get a DateTimeImmutable. If the string cannot be parsed, you get false. Because the return type is a union, always check it before calling methods. Calling ->format() on false causes a fatal error.
Use a strict comparison (=== false) or an instanceof check. The function does not tell you why parsing failed. If you need the parser's warnings and errors, use DateTimeImmutable::getLastErrors() after a failed parse, or catch the exception from the constructor instead.
Example 1: immutability in action
The first example creates a date, derives a new one with modify(), and prints both. The original keeps its value.
date_default_timezone_set('UTC');
$start = date_create_immutable('2024-03-15 10:30:00');
$later = $start->modify('+1 day');
echo $start->format('Y-m-d H:i'), "\n";
echo $later->format('Y-m-d H:i'), "\n";2024-03-15 10:30 2024-03-16 10:30
Example 2: handling invalid input
A bad string produces false rather than an exception. A valid leap day produces an object.
date_default_timezone_set('UTC');
$result = date_create_immutable('not a date');
echo json_encode($result === false), "\n";
$ok = date_create_immutable('2024-02-29');
echo json_encode($ok instanceof DateTimeImmutable), "\n";true true
Example 3: supplying a timezone
Here the wall-clock time is interpreted in New York, then converted to UTC. setTimezone() returns a new object; the original stays in New York time.
date_default_timezone_set('UTC');
$tz = new DateTimeZone('America/New_York');
$d = date_create_immutable('2024-07-04 12:00:00', $tz);
echo $d->format('Y-m-d H:i T P'), "\n";
echo $d->setTimezone(new DateTimeZone('UTC'))->format('Y-m-d H:i T'), "\n";2024-07-04 12:00 EDT -04:00 2024-07-04 16:00 UTC
Edge cases
- Embedded zones override the argument. With
2024-07-04T12:00:00+02:00and a New York$timezone, the object keeps the+02:00offset. - Default
"now"is not deterministic. Calling the function with no arguments depends on the clock, which makes tests unstable. Pass a fixed string or inject the current time from outside. - Relative strings depend on a base.
"+1 day"or"next monday"is evaluated from the current time, not from any other object you hold. - Timestamps need the
@prefix.@86400is read as a Unix timestamp and the resulting object uses the+00:00offset, regardless of the$timezoneargument. - Ambiguous formats. A slash-separated date such as
03/04/2024is read as month/day/year, while dashes or dots with a leading day are read as day-month-year. When the layout is known in advance,DateTimeImmutable::createFromFormat()is more explicit. - Parsing is lenient about some values. The parser may accept input you consider odd, so validate user-entered dates when exactness matters rather than relying on
falsealone.
Common mistakes
- Not checking for
false. Chaining->format()directly on the result fails for invalid strings. Check first, or use the constructor insidetry/catch. - Expecting mutation. Writing
$d->modify('+1 day');and ignoring the return value does nothing useful. Assign the result:$d = $d->modify('+1 day');. - Using loose comparison. Test with
=== falseorinstanceof, so intent is clear. - Relying on the server's default timezone. Set it explicitly or pass a
DateTimeZoneso that results do not vary between environments. - Mixing it up with
date_create(). The older function returns a mutableDateTime. If you want immutability, use this one.
Related functions
DateTimeImmutable::__construct(): the object-oriented form that throws on failure.DateTimeImmutable::createFromFormat(): parse a string using an exact format.date_create(): the procedural function that creates a mutableDateTime.date_default_timezone_set(): sets the fallback timezone.timezone_open(): the procedural way to build aDateTimeZone.
Recap
date_create_immutable() parses a string into a DateTimeImmutable and returns false when parsing fails. Check the result strictly, remember that a zone inside the string beats the $timezone argument, avoid the implicit "now" in tests, and assign the results of methods like modify() because the original object never changes.
Tested examples
date_default_timezone_set('UTC');
$start = date_create_immutable('2024-03-15 10:30:00');
$later = $start->modify('+1 day');
echo $start->format('Y-m-d H:i'), "\n";
echo $later->format('Y-m-d H:i'), "\n";
2024-03-15 10:30 2024-03-16 10:30
date_default_timezone_set('UTC');
$result = date_create_immutable('not a date');
echo json_encode($result === false), "\n";
$ok = date_create_immutable('2024-02-29');
echo json_encode($ok instanceof DateTimeImmutable), "\n";
true true
date_default_timezone_set('UTC');
$tz = new DateTimeZone('America/New_York');
$d = date_create_immutable('2024-07-04 12:00:00', $tz);
echo $d->format('Y-m-d H:i T P'), "\n";
echo $d->setTimezone(new DateTimeZone('UTC'))->format('Y-m-d H:i T'), "\n";
2024-07-04 12:00 EDT -04:00 2024-07-04 16:00 UTC