DateTimeImmutable vs. DateTime in PHP 8.4: warum Unveränderlichkeit gewinnt
AI generated
<?php
8.4
PHP · DateTimeImmutable · Value Objects · PHP 8.4
DateTimeImmutable vs. DateTime
warum Unveränderlichkeit fast immer die richtige Wahl ist

DateTime-Objekte lassen sich nachträglich verändern, und genau das erzeugt in größeren Codebasen schwer auffindbare Bugs. DateTimeImmutable löst dieses Problem strukturell, indem jede Methode eine neue Instanz statt einer Änderung am bestehenden Objekt zurückgibt. Dieser Artikel zeigt an echtem PHP-8.4-Code, wie DateTimeImmutable, DateTimeZone und DateInterval zusammenspielen und wie sich bestehender DateTime-Code migrieren lässt.

16 Min. Lesezeit DateTimeImmutable · DateTimeZone · DateInterval · Value Objects PHP 8.4

1. Das Grundproblem: warum mutable DateTime-Objekte Bugs erzeugen

Die Klasse DateTime in PHP ist von Grund auf mutable: Jeder Aufruf von modify(), add(), sub(), setDate() oder setTime() verändert das Objekt in-place und gibt aus Bequemlichkeit $this zurück, damit sich Aufrufe verketten lassen. Genau diese Bequemlichkeit ist die Ursache für eine ganze Klasse von Bugs, die erst auffallen, wenn ein DateTime-Objekt an mehreren Stellen im Code referenziert wird. Wer ein Objekt an eine Funktion übergibt und dort ->modify('+1 day') aufruft, verändert nicht nur eine lokale Kopie, sondern das Originalobjekt beim Aufrufer, weil PHP Objekte grundsätzlich per Referenz-Handle weiterreicht.

In der Praxis zeigt sich das Problem oft erst unter Last oder in Randfällen. Ein Buchungssystem speichert etwa den Startzeitpunkt eines Termins in einem DateTime-Objekt und übergibt es an eine Hilfsfunktion, die daraus das Enddatum berechnet. Berechnet die Funktion das Ende per $start->add($dauer), verändert sie dabei den ursprünglichen Startzeitpunkt mit, ohne dass dies aus der Aufrufstelle ersichtlich wäre. Das Ergebnis: falsch gespeicherte Termine, die sich nur schwer reproduzieren lassen, weil der Fehler vom Kontext abhängt, in dem das Objekt weitergereicht wurde.

Der klassische Workaround war jahrelang defensives Klonen: $kopie = clone $original; vor jeder Operation, die das Objekt eigentlich nicht verändern sollte. Das funktioniert, ist aber leicht zu vergessen, schwer zu erzwingen und macht aus jeder Codereview eine Suche nach fehlenden clone-Aufrufen. Genau dieses strukturelle Problem löst DateTimeImmutable, indem es die Unveränderlichkeit zur Eigenschaft der Klasse macht statt zur Disziplin des Entwicklers.


<?php

declare(strict_types=1);

// PROBLEM: DateTime is mutable, so modify() changes the shared object
function calculateEndTime(DateTime $start, int $minutes): DateTime
{
    // This looks like it returns a new end time, but it actually
    // mutates the $start object the caller still holds a reference to.
    return $start->modify("+{$minutes} minutes");
}

$appointmentStart = new DateTime('2026-07-23 14:00:00');
$appointmentEnd = calculateEndTime($appointmentStart, 45);

echo $appointmentStart->format('H:i'); // 14:45, WRONG! Original was mutated.
echo $appointmentEnd->format('H:i');   // 14:45, same object, same reference

// The defensive workaround before DateTimeImmutable existed:
function calculateEndTimeSafely(DateTime $start, int $minutes): DateTime
{
    $copy = clone $start; // easy to forget, not enforced by the type system
    return $copy->modify("+{$minutes} minutes");
}

2. DateTimeImmutable im Detail: API und Unterschiede zu DateTime

Seit PHP 5.5 existiert DateTimeImmutable als Schwesterklasse von DateTime. Beide implementieren dasselbe DateTimeInterface und bieten nahezu identische Methodennamen: format(), modify(), add(), sub(), diff(), setDate(), setTime(), setTimezone(). Der entscheidende Unterschied liegt im Rückgabewert: Jede Methode, die bei DateTime das Objekt in-place verändern würde, liefert bei DateTimeImmutable stattdessen eine komplett neue Instanz zurück. Das Originalobjekt bleibt in jedem Fall unangetastet, egal wie oft und von wem es referenziert wird.

Das hat eine unmittelbare Konsequenz für den Code-Stil: Wer mit DateTimeImmutable arbeitet, muss den Rückgabewert jeder Methode explizit auffangen, sonst passiert schlicht nichts. $date->modify('+1 day'); ohne Zuweisung ist bei DateTimeImmutable ein No-Op, weil die neue Instanz sofort wieder verworfen wird. Das erzwingt Code der Form $next = $date->modify('+1 day');, was auf den ersten Blick nach Mehraufwand aussieht, in Wahrheit aber jede Zustandsänderung im Code sichtbar und nachvollziehbar macht.

Weil beide Klassen DateTimeInterface implementieren, lässt sich in Funktionssignaturen gegen das Interface typisieren, wenn beide Varianten akzeptiert werden sollen. Für neuen Code sollte man dennoch möglichst konkret gegen DateTimeImmutable typisieren, weil das die Absicht klarstellt: dieser Parameter wird garantiert nicht verändert. Das Interface selbst kann von Userland-Klassen übrigens nicht implementiert werden, was bewusst so entschieden wurde, um die interne Konsistenz beider eingebauten Implementierungen zu schützen.

3. Zeitzonen korrekt handhaben mit DateTimeImmutable und DateTimeZone

Zeitzonenfehler gehören zu den häufigsten und am schwersten zu findenden Bugs in jeder Software, die mit Datum und Uhrzeit arbeitet. DateTimeImmutable spielt hier mit DateTimeZone-Objekten zusammen, ohne dass eine Konvertierung ungewollte Nebeneffekte hat: $date->setTimezone(new DateTimeZone('Europe/Berlin')) liefert eine neue Instanz mit der gewünschten Zeitzone, während das Original in seiner ursprünglichen Zeitzone unverändert bestehen bleibt. Bei DateTime müsste man dieselbe Vorsicht walten lassen wie bei jeder anderen mutierenden Operation auch.

Ein bewährtes Muster in Produktionscode: Zeitstempel werden intern konsequent in UTC gespeichert und erst an der Ausgabegrenze, also kurz vor der Anzeige oder dem Versand, in die Zeitzone des Nutzers konvertiert. Das vermeidet die Mehrdeutigkeiten, die bei Sommerzeitumstellungen entstehen, etwa die eine Stunde im Herbst, die real zweimal existiert. Mit DateTimeImmutable lässt sich dieses Muster gefahrlos implementieren, weil jede Konvertierung explizit eine neue, unabhängige Instanz erzeugt und der UTC-Ursprungswert niemals versehentlich überschrieben wird.

Statt globale Prozesskonfiguration wie date_default_timezone_set() zu nutzen, sollte die Zeitzone als explizites DateTimeZone-Objekt durch den Code gereicht werden, idealerweise per Dependency Injection. Das macht Tests deterministisch und verhindert, dass ein Request die globale Zeitzone für nachfolgende Requests im selben Worker-Prozess verändert. Die verfügbaren Bezeichner liefert DateTimeZone::listIdentifiers(), feste UTC-Offsets wie +02:00 sollte man dagegen vermeiden, weil sie Sommerzeitregeln nicht automatisch berücksichtigen.


<?php

declare(strict_types=1);

final class MeetingScheduler
{
    /**
     * @param DateTimeZone $storageZone Timezone used for internal storage (always UTC in practice)
     */
    public function __construct(
        private readonly DateTimeZone $storageZone = new DateTimeZone('UTC'),
    ) {
    }

    /**
     * Converts a user-facing local time into a stored UTC instant.
     */
    public function toStorage(DateTimeImmutable $localTime): DateTimeImmutable
    {
        // setTimezone() returns a brand-new instance, the argument is untouched
        return $localTime->setTimezone($this->storageZone);
    }

    /**
     * Converts a stored UTC instant into the recipient's local display timezone.
     */
    public function toDisplay(DateTimeImmutable $utcTime, DateTimeZone $displayZone): DateTimeImmutable
    {
        return $utcTime->setTimezone($displayZone);
    }
}

$scheduler = new MeetingScheduler();
$berlinTime = new DateTimeImmutable('2026-10-25 02:30:00', new DateTimeZone('Europe/Berlin'));
$stored = $scheduler->toStorage($berlinTime);

echo $berlinTime->format('Y-m-d H:i:s P'); // still 2026-10-25 02:30:00 +02:00, unchanged
echo $stored->format('Y-m-d H:i:s P');     // 2026-10-25 00:30:00 +00:00

4. Arithmetik: DateInterval, modify(), add(), sub() ohne Seiteneffekte

Für exakte Datumsarithmetik bringt PHP die Klasse DateInterval mit, die Zeitspannen im ISO-8601-Dauerformat abbildet, etwa P1Y2M10D für ein Jahr, zwei Monate und zehn Tage, oder PT30M für dreißig Minuten. Alternativ erzeugt DateInterval::createFromDateString('3 weeks') ein Intervall aus einer natürlichsprachlichen Beschreibung. In Kombination mit DateTimeImmutable lassen sich add() und sub() beliebig verketten, ohne dass Zwischenzustände sich gegenseitig überschreiben, weil jeder Aufruf eine eigene, unabhängige Instanz erzeugt.

Das erlaubt Verkettungen wie $date->add($eineWoche)->sub($zweiTage)->setTime(9, 0), bei denen jeder Zwischenschritt bei Bedarf separat gespeichert und weiterverwendet werden kann, ohne dass ein späterer Schritt einen früher gespeicherten Zwischenwert nachträglich verändert. Bei DateTime wäre genau das ein Risiko: Speichert man einen Zwischenschritt in einer Variablen und ruft danach eine weitere Methode auf derselben Objektreferenz auf, verändert sich auch der bereits gespeicherte Zwischenwert rückwirkend.

Für Geschäftslogik wie "addiere drei Werktage" reicht ein einfaches add() nicht aus, weil Wochenenden und Feiertage übersprungen werden müssen. Hier bewährt sich eine Schleife, die pro Iteration add(new DateInterval('P1D')) aufruft und das Ergebnis auf einen Werktag prüft. Da DateTimeImmutable bei jedem Schritt eine neue Instanz liefert, lassen sich Zwischenergebnisse gefahrlos in einem Array sammeln oder loggen, ohne dass sich ihr Wert im Nachhinein noch verändert.


<?php

declare(strict_types=1);

/**
 * Adds a number of business days (Mon-Fri) to a date, skipping weekends.
 *
 * @param DateTimeImmutable $start The starting point, never mutated
 * @param int $businessDays Number of business days to add
 * @return DateTimeImmutable The resulting date
 */
function addBusinessDays(DateTimeImmutable $start, int $businessDays): DateTimeImmutable
{
    $oneDay = new DateInterval('P1D');
    $current = $start;
    $added = 0;

    while ($added < $businessDays) {
        $current = $current->add($oneDay); // returns a fresh instance every time
        $isWeekend = (int) $current->format('N') >= 6;

        if (!$isWeekend) {
            $added++;
        }
    }

    return $current;
}

$orderDate = new DateTimeImmutable('2026-07-23'); // a Thursday
$shippingDeadline = addBusinessDays($orderDate, 3);

echo $orderDate->format('Y-m-d (D)');        // 2026-07-23 (Thu), untouched
echo $shippingDeadline->format('Y-m-d (D)'); // 2026-07-28 (Tue), Fri, Mon, Tue counted

// Chaining without side effects between intermediate steps
$deadline = $orderDate
    ->add(new DateInterval('P1W'))
    ->sub(new DateInterval('P2D'))
    ->setTime(9, 0);

5. Vergleiche und Sortierung von Datumswerten

Objekte, die DateTimeInterface implementieren, unterstützen die eingebauten Vergleichsoperatoren <, >, == und den Spaceship-Operator <=> direkt, ohne dass man vorher getTimestamp() aufrufen muss. PHP vergleicht dabei den internen Zeitpunkt und nicht die textuelle Zeitzonenangabe, sodass zwei Objekte in unterschiedlichen Zeitzonen korrekt als gleich erkannt werden, wenn sie denselben absoluten Moment repräsentieren. Das gilt für DateTimeImmutable genauso wie für DateTime, weil der Vergleich über das gemeinsame Interface funktioniert.

Beim Sortieren von Arrays mit usort() und dem Spaceship-Operator gibt es einen praktischen Unterschied: Sortiert man ein Array aus DateTime-Objekten, besteht die theoretische Gefahr, dass eine Vergleichsfunktion versehentlich eines der Objekte mutiert, etwa weil in der Callback-Funktion aus Versehen modify() statt eines reinen Vergleichs aufgerufen wird. Mit DateTimeImmutable ist dieses Risiko strukturell ausgeschlossen, weil die Callback-Funktion das Objekt gar nicht verändern kann, selbst wenn sie es versucht.

Für Bereichsprüfungen, etwa "liegt dieser Zeitpunkt zwischen Start und Ende eines Angebots", reicht ein einfacher Vergleich mit >= und <= auf den DateTimeImmutable-Instanzen aus. Wichtig ist dabei, konsequent mit derselben Zeitzone zu vergleichen oder sich auf den automatischen UTC-Vergleich der Objekte zu verlassen, statt formatierte Strings wie format('Y-m-d') textuell zu vergleichen, was bei unterschiedlichen Zeitzonen zu falschen Ergebnissen führen kann.

6. Formatierung und Parsing: format(), createFromFormat(), ISO 8601

Die Methode format() ist bei DateTimeImmutable identisch zu DateTime und akzeptiert dieselben Formatzeichen, etwa Y-m-d für ein Datum oder c beziehungsweise die Konstante DATE_ATOM für ISO-8601-konforme Zeitstempel. Da format() lediglich einen String zurückgibt und keine Mutation vornimmt, unterscheidet sich das Verhalten zwischen beiden Klassen hier nicht, der Unterschied liegt ausschließlich bei Methoden, die vorher den Zustand verändern würden.

Beim Parsing von Eingabestrings liefert die statische Methode DateTimeImmutable::createFromFormat() eine neue Instanz aus einem beliebigen Format, oder false, wenn der String nicht zum angegebenen Format passt. Ein häufiger Fehler ist es, den Rückgabewert ungeprüft weiterzureichen: Da createFromFormat() false statt einer Exception zurückgibt, führt ein fehlender Check schnell zu einem Fatal Error an einer völlig anderen Stelle im Code, wenn später eine Methode auf einem bool aufgerufen wird.

Für den Datenaustausch zwischen Systemen, etwa in REST-APIs, hat sich ISO 8601 als Standardformat etabliert. $date->format(DATE_ATOM) liefert einen Zeitstempel wie 2026-07-23T14:00:00+02:00, und der Konstruktor von DateTimeImmutable akzeptiert genau dieses Format ohne zusätzliches createFromFormat(), weil er ISO-8601-Strings direkt versteht. Das macht Round-Trips zwischen Serialisierung und Deserialisierung besonders robust.


<?php

declare(strict_types=1);

/**
 * Parses a user-supplied date string in German format (d.m.Y) into DateTimeImmutable.
 *
 * @param string $input Raw input, e.g. "23.07.2026"
 * @return DateTimeImmutable
 * @throws InvalidArgumentException If the input does not match the expected format
 */
function parseGermanDate(string $input): DateTimeImmutable
{
    $parsed = DateTimeImmutable::createFromFormat('!d.m.Y', $input);

    // createFromFormat() returns false on failure, never an exception,
    // so this check must never be skipped.
    if ($parsed === false) {
        throw new InvalidArgumentException("Invalid date format: {$input}");
    }

    return $parsed;
}

$dueDate = parseGermanDate('23.07.2026');
echo $dueDate->format(DATE_ATOM); // 2026-07-23T00:00:00+02:00

// Round-trip through ISO 8601, the constructor understands it directly
$isoString = $dueDate->format(DATE_ATOM);
$roundTripped = new DateTimeImmutable($isoString);

var_dump($dueDate == $roundTripped); // true, same instant

7. Value Objects und Unveränderlichkeit als Designprinzip

Unveränderlichkeit ist eines der zentralen Prinzipien beim Entwurf von Value Objects: Ein Wertobjekt soll seinen internen Zustand nach der Konstruktion nicht mehr verändern können, weil zwei Instanzen mit identischen Werten als gleichwertig gelten sollen, unabhängig davon, wo sie im Code herumgereicht werden. DateTimeImmutable ist das prominenteste eingebaute Beispiel für dieses Prinzip in PHP und eignet sich deshalb hervorragend als Baustein für eigene Value Objects, die Zeitpunkte oder Zeiträume abbilden.

PHP 8.4 unterstützt readonly-Eigenschaften und Constructor Property Promotion, was sich natürlich mit DateTimeImmutable-Properties kombinieren lässt: Ein Value Object wie PriceValidity oder AppointmentSlot kann seine DateTimeImmutable-Felder als readonly deklarieren, wodurch selbst ein versehentlicher Zuweisungsversuch von außen zu einem Fehler zur Compile- beziehungsweise Laufzeit führt. Methoden, die eine Änderung ausdrücken sollen, etwa withExtendedEnd(), geben dann konsequent eine neue Instanz des Value Objects zurück, statt den eigenen Zustand zu verändern.

Der praktische Nutzen zeigt sich beim Weiterreichen solcher Objekte durch verschiedene Schichten einer Anwendung: Ein Value Object mit DateTimeImmutable-Properties kann bedenkenlos in Arrays, Collections oder Caches abgelegt und über Modulgrenzen hinweg geteilt werden, ohne dass eine defensive Kopie nötig wäre. Das reduziert nicht nur Boilerplate-Code, sondern macht auch das Reasoning über den Code einfacher, weil ein einmal erzeugtes Objekt garantiert für immer denselben Wert repräsentiert.


<?php

declare(strict_types=1);

/**
 * Immutable value object representing a validity window for a price.
 */
final readonly class PriceValidity
{
    /**
     * @param DateTimeImmutable $validFrom Start of the validity window
     * @param DateTimeImmutable $validUntil End of the validity window (exclusive)
     */
    public function __construct(
        private DateTimeImmutable $validFrom,
        private DateTimeImmutable $validUntil,
    ) {
        if ($this->validFrom >= $this->validUntil) {
            throw new InvalidArgumentException('validFrom must be before validUntil');
        }
    }

    /**
     * Checks whether the given instant falls within the validity window.
     */
    public function isActiveAt(DateTimeImmutable $moment): bool
    {
        return $moment >= $this->validFrom && $moment < $this->validUntil;
    }

    /**
     * Returns a new PriceValidity with an extended end date, original stays untouched.
     */
    public function withExtendedEnd(DateInterval $extension): self
    {
        return new self($this->validFrom, $this->validUntil->add($extension));
    }
}

$validity = new PriceValidity(
    validFrom: new DateTimeImmutable('2026-08-01'),
    validUntil: new DateTimeImmutable('2026-09-01'),
);

$extended = $validity->withExtendedEnd(new DateInterval('P2W'));
// $validity itself is completely unchanged, $extended is a distinct object

8. Migration von bestehendem DateTime-Code zu DateTimeImmutable

Ein bestehendes Projekt komplett auf einmal umzustellen ist selten praktikabel, deshalb bewährt sich eine schrittweise Migration an den Rändern des Systems. Repositories, API-Clients und externe Bibliotheken, die DateTime-Objekte zurückgeben, werden an ihrer Rückgabestelle sofort in DateTimeImmutable konvertiert, etwa mit DateTimeImmutable::createFromMutable($legacyDate). So wandert die mutable Variante nach und nach an den Rand der Anwendung, während der Anwendungskern ausschließlich mit der unveränderlichen Variante arbeitet.

Besondere Vorsicht ist bei ORMs und älteren Bibliotheken geboten, die in ihren Typdeklarationen konkret DateTime statt des Interfaces erwarten oder zurückgeben. Solche Grenzen sollten explizit als Konvertierungspunkte markiert werden, idealerweise mit einem kleinen Wrapper oder Mapper, der die Konvertierung an einer einzigen Stelle bündelt statt sie über den gesamten Code zu verteilen. Mit declare(strict_types=1) und typisierten Parametern fallen Stellen, an denen versehentlich noch DateTime statt DateTimeImmutable erwartet wird, schon beim ersten Testlauf auf.

Statische Analyse hilft, die Migration abzusichern: Eine PHPStan-Regel oder schlichte Code-Review-Disziplin kann neue Verwendungen von DateTime im Anwendungskern verhindern und so schleichende Rückschritte vermeiden. Sinnvoll ist es, die Migration Modul für Modul durchzuführen statt in einem einzigen großen Refactoring, weil sich so jede Änderung isoliert testen lässt und das Risiko einer großflächigen Regression sinkt.

9. Wann DateTime doch noch sinnvoll ist

Trotz der klaren Vorteile von DateTimeImmutable gibt es wenige, aber reale Ausnahmen. In extrem performancekritischen, eng geschriebenen Schleifen mit Millionen Iterationen kann die Objekterzeugung bei jedem Schritt spürbar ins Gewicht fallen, weil DateTimeImmutable bei jeder Operation eine komplett neue Instanz alloziert, während DateTime dasselbe Objekt weiterverwendet. Bevor man deshalb auf DateTime zurückgreift, sollte man das aber tatsächlich messen, denn der Unterschied ist in den allermeisten Anwendungsfällen nicht relevant und die Robustheit von DateTimeImmutable wiegt schwerer als ein Mikro-Optimierungspotenzial, das sich selten materialisiert.

Die zweite Ausnahme betrifft Legacy-Bibliotheken und manche ältere APIs, die konkret gegen DateTime typisieren statt gegen DateTimeInterface, und damit an ihrer Grenze eine mutable Instanz erzwingen. In solchen Fällen konvertiert man am Rand mit DateTime::createFromInterface() in die geforderte Richtung und zurück mit DateTimeImmutable::createFromMutable(), statt den gesamten Code auf die veraltete API auszurichten. Für neuen Code bleibt DateTimeImmutable in praktisch jedem Fall die richtige Standardentscheidung.

Aspekt DateTime DateTimeImmutable Vorteil
Aufruf von modify() verändert Objekt in-place, gibt $this zurück gibt neues Objekt zurück, Original bleibt gleich keine versteckten Seiteneffekte bei geteilten Referenzen
Übergabe an Funktionen Funktion kann Original unbeabsichtigt verändern Funktion kann Original nicht verändern sichere Weitergabe ohne defensives clone()
Nutzung in Value Objects erfordert clone() bei jedem Getter passt direkt als readonly-Property weniger Boilerplate, klarere Semantik
Verkettung (Chaining) jede Methode verändert dasselbe Objekt weiter jede Methode liefert einen neuen Snapshot Zwischenzustände bleiben nachvollziehbar
Caching und Wiederverwendung gecachtes Objekt kann später verändert werden gecachtes Objekt bleibt garantiert unverändert sicher in Arrays, Collections, Caches ablegen

10. Zusammenfassung

Mutable DateTime-Objekte erzeugen Bugs, die von der Objektreferenz abhängen und deshalb schwer zu reproduzieren sind, sobald ein Objekt an mehr als eine Stelle im Code weitergereicht wird. DateTimeImmutable löst dieses Problem strukturell: Jede Methode, die bei DateTime mutieren würde, liefert stattdessen eine neue Instanz, während das Original garantiert unverändert bleibt. Das gilt gleichermaßen für Zeitzonenkonvertierung mit DateTimeZone, für Arithmetik mit DateInterval, für Vergleiche mit den eingebauten Operatoren und für Formatierung und Parsing über format() und createFromFormat().

Für Value Objects ist DateTimeImmutable praktisch die natürliche Wahl, weil PHP 8.4 mit readonly-Properties und Constructor Property Promotion genau dieses Unveränderlichkeitsprinzip auf Klassenebene unterstützt. Bestehender DateTime-Code lässt sich schrittweise an den Systemrändern migrieren, statt in einem riskanten Big-Bang-Refactoring. Nur in engen, nachweislich performancekritischen Schleifen oder an Grenzen zu Legacy-APIs, die konkret DateTime erwarten, bleibt die mutable Variante eine begründete Ausnahme. Für jeden neuen Code ist DateTimeImmutable die Standardentscheidung.

DateTimeImmutable vs. DateTime: Das Wichtigste auf einen Blick

Grundproblem

DateTime ist mutable: modify(), add() und sub() verändern das geteilte Objekt und erzeugen referenzabhängige Bugs.

Lösung

DateTimeImmutable gibt bei jeder Änderung eine neue Instanz zurück, das Original bleibt garantiert unverändert.

Value Objects

DateTimeImmutable passt natürlich zu readonly-Properties und Constructor Property Promotion in PHP 8.4.

Migration

Schrittweise an den Systemrändern konvertieren mit createFromMutable(), statt in einem Big-Bang-Refactoring.

11. FAQ: DateTimeImmutable vs. DateTime

1Was ist der Hauptunterschied zwischen DateTime und DateTimeImmutable?
DateTime verändert sich bei modify(), add() oder sub() selbst und gibt sich zurück. DateTimeImmutable lässt das Original unverändert und liefert stattdessen eine neue Instanz.
2Ist DateTimeImmutable langsamer als DateTime?
Minimal mehr Objekterzeugung, aber in der Praxis kaum messbar relevant. Die Sicherheit gegen versteckte Seiteneffekte überwiegt deutlich.
3Wie konvertiere ich DateTime in DateTimeImmutable?
Mit DateTimeImmutable::createFromMutable($dateTimeObjekt). Der umgekehrte Weg läuft über DateTime::createFromInterface().
4Kann ich DateTimeImmutable und DateTime mischen?
Technisch ja über DateTimeInterface, empfohlen wird aber konsequent DateTimeImmutable im Kern und DateTime nur an klaren Konvertierungsgrenzen.
5Warum gibt modify() ein neues Objekt zurück?
Weil Unveränderlichkeit die zentrale Eigenschaft von DateTimeImmutable ist. Der Rückgabewert muss deshalb immer explizit zugewiesen werden.
6Wie vergleiche ich zwei DateTimeImmutable-Objekte?
Direkt mit <, >, == oder <=>. PHP vergleicht den internen Zeitpunkt unabhängig von der textuellen Zeitzonenangabe.
7Wie gehe ich mit Zeitzonen um?
Intern in UTC speichern, erst an der Ausgabe mit setTimezone() konvertieren, Zeitzone explizit als DateTimeZone-Objekt durchreichen.
8Was ist DateInterval?
Eine Zeitspanne im ISO-8601-Dauerformat, z.B. P1D. Mit add() und sub() auf DateTimeImmutable anwendbar, jeder Aufruf liefert eine neue Instanz.
9Sollte ich DateTimeImmutable in Value Objects nutzen?
Ja, es passt ideal zu readonly-Properties und Constructor Property Promotion in PHP 8.4, ganz ohne zusätzliches clone().
10Gibt es Fälle, in denen DateTime sinnvoll bleibt?
Nur in nachweislich performancekritischen Schleifen oder an Grenzen zu Legacy-APIs, die konkret DateTime erwarten. Für neuen Code bleibt DateTimeImmutable Standard.

Mironsoft

PHP-Entwicklung mit sauberem, wartbarem Code

Datum- und Uhrzeit-Bugs im Bestandscode gefunden?

Wir prüfen bestehenden PHP-Code auf mutable DateTime-Fallstricke, migrieren schrittweise zu DateTimeImmutable und bauen Value Objects, die von Anfang an unveränderlich sind.

Code-Audit

Gezielte Suche nach mutable DateTime-Fallstricken und geteilten Objektreferenzen

Migration

Schrittweise Umstellung auf DateTimeImmutable an klaren Systemgrenzen

Value Objects

Design und Umsetzung unveränderlicher Domänenobjekte in PHP 8.4