Become a Patron!

My Amazon wishlist can be found here.

Life Line

DateTimeImmutable

The first time that my improved DateTime support made its way into PHP was officially in PHP 5.1, although the more advanced features such as the DateTime class only made it appearance in PHP 5.2. Since its introduction the DateTime class implementation suffered from one design mistake — arguably not something that even an RFC would have highlighted.

In PHP, if you do the following:

<?php
function formatNextMondayFromNow( DateTime $dt )
{
        return $dt->modify( 'next monday' )->format( 'Y-m-d' );
}

$d = new DateTime();
echo formatNextMondayFromNow( $d ), "\n";
echo $d->format( 'Y-m-d' ), "\n";
?>

It displays:

2014-02-17
2014-02-17

The modify() method does not only return the modified DateTime object, but also changes the DateTime object it was called on. In an API like above, this is of course totally unexpected and the only way to avoid this behaviour is to do the following instead:

echo formatNextMondayFromNow( clone $d ), "\n";

This mutability property that all modifying methods of the DateTime class have is highly annoying, and something that I would now rather remove. But of course we cannot as that would break backwards compatibility.

So in PHP 5.5, after a few stumbles, I finally managed to rectify this. I did not change the original class's behaviour, but instead, I added a new class. The new DateTimeImmutable class which does not display this "mutable" behaviour, and only returns the modified object:

<?php
function formatNextMondayFromNow( DateTimeImmutable $dt )
{
        return $dt->modify( 'next monday' )->format( 'Y-m-d' );
}

$d = new DateTimeImmutable();
echo formatNextMondayFromNow( $d ), "\n";
echo $d->format( 'Y-m-d' ), "\n";
?>

Which displays:

2014-02-17
2014-02-11

Both the old DateTime and the new DateTimeImmutable classes implement the DateTimeInterface interface. This interface defines all the methods that both classes implement. Of course, they can not be methods that change the object. That is why the interface is restricted to formatting and other read-only methods, such as getTimezone.

Perhaps in the future (PHP 6), we can replace the mutable DateTime class with the new DateTimeImmutable variant. Until then, you will have to take care of this yourself!

Shortlink

This article has a short URL available: https://drck.me/dti-avg

Comments

Hi Derick,

the DateTimeImmutable class should have another static method:

create(DateTimeInterface $dt) {
  return $dt instanceof DateTimeImmutable
    ? $dt
    : new DateTimeImmutable($dt)
}

such a method is important in a constructor:

class MyClass {

  private $immutableDateTime;

  public function __construct(DateTimeInterface $dt) {
    $this->immutableDateTime = DateTimeImmutable::create($dt);
  }
}

You don't want somebody from outside to modify the internal state of MyClass.

I miss a direct way to cast from one to the other.

Too bad you are using such an example to justify DateTimeImmutable. The formatNextMondayFromNow() should clone the date:

function formatNextMondayFromNow( DateTime $dt )
{
    $dt = clone $dt;
    return $dt->modify( 'next monday' )->format( 'Y-m-d' );
}

Or better, be broken into two parts:

$now->next('monday')->format();

Where next() would return a new instance representing the next something, here "monday".

To me, with this example, it looks like DateTimeImmutable is a solution to bad usages.

I created an extension to DateTime and all the yesterday, monday, utc, local, … magic properties return new instances. Only modify() actually modifies the instance. I'm quite happy with it.

https://github.com/ICanBoogie/DateTime

@Thomas, @Per: I have added to PHP-5.6 a new method DateTimeImmutable::createFromMutable() that allows you to instantiate a non-mutable DateTimeImmutable variant based off a mutable DateTime object.

DateTimeImmutable::createFromMutable() is a good addition but conversely being able to do the opposite would have been great too DateTime::createFromImmutable()

This way switching from one class to the other would be less painfull.

Is it possible to make DateTime immutable in PHP 7 to avoid this madness?

@Daniel: Possible, but that is going to break quite a lot of code, and it's going to be difficult to spot/test. So that's not happening.

While we're at it. It would of course be nice to get rid of a mutable DateTime in PHP7 but I'm afraid that this would be too much of a break and Derick already commented on how important he considers backward compatibility.

But please add a method DateTimeImmutable::from(DateTimeInterface $dt).

This method is needed all the time when a Constructor accepts a DateTimeInterface. In this case one can either take a given DateTimeImmutable as is of create a new DateTimeImmutable from a given DateTime.

@Thomas: That method does exist:

And PHP 7 will have the createFromMutable method on DateTime too.

Add Comment

Name:
Email:

Will not be posted. Please leave empty instead of filling in garbage though!
Comment:

Please follow the reStructured Text format. Do not use the comment form to report issues in software, use the relevant issue tracker. I will not answer them here.


All comments are moderated