Stop using DateTime

Working with date & time in PHP can sometimes be annoying, leading to unexpected bugs in the code:

$startedAt = new DateTime('2019-06-30 10:00:00');

$finishedAt = $startedAt->add(new DateInterval('PT3M')); 

var_dump($startedAt->format('Y-m-d H:i:s')); //2019-06-30 10:03:00 ❌
var_dump($finishedAt->format('Y-m-d H:i:s')); //2019-06-30 10:03:00 ✅

Both $startedAt and $finishedAt are 3 minutes forward in time, because methods such as add(), sub() or modify() also change DateTime object they were called on before returning it. In the above example, this certainly isn't the desired behaviour.

We can fix this by copying reference object before acting on it, like so:

$startedAt = new DateTime('2019-06-30 10:00:00');

$finishedAt = clone $startedAt;
$finishedAt->add(new DateInterval('PT3M'));

Every time I encounter clone in PHP code things start to smell as it is usually about someone hacking someone else's bad code design. In this particular case it was used to avoid mutating behavior, but it makes the code ugly and introduces unnecessary noise.

Alternatively, this could be solved by converting original DateTime instance to DateTimeImmutable:

$startedAt = new DateTime('2019-06-30 10:00:00');

$finishedAt = DateTimeImmutable::createFromMutable($startedAt)->add(new DateInterval('PT3M'));

But why not using DateTimeImmutable from the beginning?

Uncompromising use of DateTimeImmutable

Instead of manually applying defensive techniques in order to prevent unexpected mutation when passing around date/time objects, use DateTimeImmutable that encapsulates those techniques, making your code more reliable.

$startedAt = new DateTimeImmutable('2019-06-30 10:00:00');

$finishedAt = $startedAt->add(new DateInterval('PT3M'));

var_dump($startedAt->format('Y-m-d H:i:s')); //2019-06-30 10:00:00 ✅
var_dump($finishedAt->format('Y-m-d H:i:s')); //2019-06-30 10:03:00 ✅

In most contexts, concept of a date is treated as a value, we compare dates by their values, and when we modify a date it becomes a different date. All this perfectly matches the definition of a Value Object, and one important characteristic of value objects is that they are immutable.

Verbose coding style

Immutability forces you to explicitly reassign a DateTimeImmutable object every time you act on it, because it never modifies itself but a new copy is returned. After years of working with mutable DateTime, and due to the fact that mutability is the default in imperative programming languages, it is hard to get rid of bad mutating habits and conform to the new coding style that enforces reassignment:

$this->expiresAt = $this->expiresAt->modify('+1 week');

Static analysis tools such as PHPStan and one of its extensions can warn us if we misuse DateTimeImmutable by omitting assignment.

Yet this cognitive bias towards mutability is suppressed when we perform arithmetic operations on primitive values, for example: $a + 3;. On its own, this gets perceived as an pointless statement that is clearly missing reassignment: $a = $a + 3; or $a += 3;. Would not it be lovely if we could use something similar in the case of value objects?

Some programming languages features a syntactic sugar called operator overloading that allows for implementing operators in user-defined types and classes, so that they behave much like the primitive data types. I would not mind if PHP steals this trick from another programming language that would allow us to write our code this way:

$this->expiresAt += '1 week';

One-off calculations

Some people argue that performance-wise, it is better to use DateTime when calculations are done within a single scope of execution. That is a valid point, but unless you are doing hundreds of operations, and given that references to the old DateTimeImmutable object will be garbage collected, in most practical scenarios memory consumption should not be a concern.

Date/time libraries

Carbon is a super popular library that extends PHP's date/time API with a rich set of functionality. To be more accurate, it extends API of a mutable DateTime class, which conflicts with the aim of this blog post.

So if you like working with Carbon, but you favor immutability, I suggest you consider Chronos. It is a standalone library that was originally based on Carbon, focusing on providing immutable date/time objects by default, but it also ships with mutable variants in case you need them.

Edit (05/07/2019): It turns out that Carbon does have an immutable date/time variant, which is a big plus on its account. Yet, the reason I gave Chronos advantage is that unlike Carbon, it encourages and promotes immutability by default, both in code and documentation, and that is a crucial factor with regard to the message of this post.

Final thoughts

DateTimeImmutable was first introduced back in the ancient PHP 5.5, and to my surprise many developers are discovering it only now. Use DateTimeImmutable by default whenever possible, also bearing in mind some of the tradeoffs I've described, which I consider to be more a matter of habit and shift in mindset.

datetime immutable value object