Dates7 min readExamples run on PHP 8.5.10

date_create_immutable()

Learn how date_create_immutable() builds a DateTimeImmutable from a string, how it signals bad input with false, and how to avoid common timezone mistakes.

Signature

date_create_immutable(string $datetime = "now", ?DateTimeZone $timezone = null): DateTimeImmutable|false
Available since PHP 5.5.07.48.08.18.28.38.48.5All versions →

Purpose

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 as 2024-03-15 10:30:00, 2024-03-15T10:30:00+02:00, @86400 or a relative phrase like +1 week. The default is "now", which depends on the current clock.
  • $timezone: an optional DateTimeZone object. The default is null, meaning the script's default timezone is used (see date_default_timezone_set()). The argument is only a fallback: if $datetime already carries its own offset or zone name, that value wins and $timezone is 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.

Example
<?php
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";
Output
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.

Example
<?php
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";
Output
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.

Example
<?php
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";
Output
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:00 and a New York $timezone, the object keeps the +02:00 offset.
  • 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. @86400 is read as a Unix timestamp and the resulting object uses the +00:00 offset, regardless of the $timezone argument.
  • Ambiguous formats. A slash-separated date such as 03/04/2024 is 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 false alone.

Common mistakes

  1. Not checking for false. Chaining ->format() directly on the result fails for invalid strings. Check first, or use the constructor inside try/catch.
  2. Expecting mutation. Writing $d->modify('+1 day'); and ignoring the return value does nothing useful. Assign the result: $d = $d->modify('+1 day');.
  3. Using loose comparison. Test with === false or instanceof, so intent is clear.
  4. Relying on the server's default timezone. Set it explicitly or pass a DateTimeZone so that results do not vary between environments.
  5. Mixing it up with date_create(). The older function returns a mutable DateTime. If you want immutability, use this one.
  • 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 mutable DateTime.
  • date_default_timezone_set(): sets the fallback timezone.
  • timezone_open(): the procedural way to build a DateTimeZone.

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

Immutability: modify() returns a new object
<?php
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";
Output
2024-03-15 10:30
2024-03-16 10:30
Invalid input returns false
<?php
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";
Output
true
true
Supplying a timezone and converting to UTC
<?php
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";
Output
2024-07-04 12:00 EDT -04:00
2024-07-04 16:00 UTC