PHP 8.3: Granular DateTime
Exceptions
In PHP 8.3, the Date/Time
extension introduces extension-specific and granular Exception and Error classes to better convey the error and exception states. This makes it easier and cleaner to catch date-specific exceptions.
Prior to PHP 8.3, the Date/Time
extension used standard \Exception
and \Error
.
The new Exception/Error classes extend existing \Error
and \Exception
classes, which means existing code that catch \Exception
or \Error
exceptions should continue to catch these errors.
Note that the Date/Time
extension continues to throw \ValueError
(when passing invalid values to function/methods), \TypeError
(standard type errors), and \Error
exceptions (attempting to modify read-only properties, errors in unserializing data, etc).
New Exception Classes
PHP 8.3 adds nine Exception/Error classes to the Date/Time
extension. User-land PHP code is allowed to throw these exceptions as well, although these exceptions are reserved for the Date/Time
extension by convention.
Hierarchy of the Date/Time
Exception classes
The following chart shows the new DateError
Throwable
├── Error
| └── DateError
| ├── DateObjectError
│ └── DateRangeError
└── Exception
└── DateException
├── DateInvalidTimeZoneException
├── DateInvalidOperationException
├── DateMalformedStringException
├── DateMalformedIntervalStringException
└── DateMalformedPeriodStringException
DateError
and its sub classes (DateObjectError
and DateRangeError
) are reserved for the errors with the Date extension itself or the PHP run-time, and are not related to the actual values passed to the Date/Time
classes or functions.
DateException
and its sub classes are thrown when the values themselves are invalid or malformed. For PHP applications that accept user-provided or dynamic date or time values, the DateException
is the appropriate Exception to catch.
DateError
DateError
errors are thrown if the underlying timelib database is corrupt. This is an uncommon case, and indicates something wrong with the PHP setup itself.
DateObjectError
DateObjectError
errors are thrown when a Date/Time class object is not properly initialized. One common case would be when a user-land PHP class extends a Date/Time
class, but does not properly initialize it at the constructor by calling parent::__construct()
.
class Foo extends DateInterval {
public function __construct() {
// Does not call parent::__construct();
}
}
$interval = new Foo();
$interval->format('s');
DateObjectError: Object of type Foo (inheriting DateInterval) has not been correctly initialized by calling parent::__construct() in its constructor
Further, attempting to compare uninitialized Date/Time
objects result in a DateObjectError
error as well:
DateObjectError: Trying to compare uninitialized DateTimeZone objects
DateRangeError
DateRangeError
errors are thrown when attempting to process a date that exceeds the PHP integer value.
DateRangeError: Epoch doesn't fit in a PHP integer
DateException
DateException
exceptions the common type of \Exception
sub classes that will be thrown for invalid user-provided values.
DateInvalidTimeZoneException
DateInvalidTimeZoneException
exceptions are thrown when attempting to instantiate a DateTimeZone
class object with an unrecognized timezone name, or when attempting to set an out of range time zone offset.
new DateTimeZone("DoesNotExists");
new DateTimeZone("-9999");
DateInvalidTimeZoneException: DateTimeZone::__construct(): Unknown or bad timezone (DoesNotExists)
DateInvalidTimeZoneException: DateTimeZone::__construct(): Timezone offset is out of range (-9999)
DateInvalidOperationException
DateInvalidOperationException
exceptions are thrown when attempting an invalid operation on a DateTime object. Currently, calling DateTimeInterface::sub
on special time specification throws this exception. Prior to PHP 8.3, this condition resulted in a PHP warning.
$now = new DateTimeImmutable("1992-09-16 10:44:00 CET");
$e = DateInterval::createFromDateString('next wednesday');
$now->sub($e);
DateInvalidOperationException: DateTimeImmutable::sub(): Only non-special relative time specifications are supported for subtraction
DateMalformedStringException
DateMalformedStringException
exceptions are thrown when the DateTime extension could not parse a valid date/time from the given string. The most common case is when a DateTime
/DateTimeImmutable
object is instantiated with an invalid date/time string.
new DateTimeImmutable('half-life 3 release date');
DateMalformedStringException: Failed to parse time string (half-life 3 release date) at position 0 (h): The timezone could not be found in the database
DateMalformedIntervalStringException
Similar to DateMalformedStringException
, DateMalformedIntervalStringException
exceptions are thrown when Date/Time extension classes encounter an invalid interval string.
new DateInterval('until tomorrow');
new DateInterval('1992-09-16T10:44:00Z');
DateInterval::createFromDateString('next wednesday 10:44');
DateMalformedIntervalStringException: Unknown or bad format (until tomorrow)
DateMalformedIntervalStringException: Failed to parse interval (1992-09-16T10:44:00Z)
Uncaught DateMalformedIntervalStringException: String 'next wednesday 10:44' contains non-relative elements
DateMalformedPeriodStringException
DateMalformedPeriodStringException
exceptions are thrown when attempting to instantiate DatePeriod
class instances with malformed period strings.
new DatePeriod('1 mississippi');
new DatePeriod('10D');
new DatePeriod("R4");
new DatePeriod("2012-07-01T00:00:00Z/P7D");
DateMalformedPeriodStringException: Unknown or bad format (1 mississippi)
DateMalformedPeriodStringException: Unknown or bad format (10D)
DateMalformedPeriodStringException: DatePeriod::__construct(): ISO interval must contain a start date, "R4" given
DateMalformedPeriodStringException: DatePeriod::__construct(): Recurrence count must be greater than 0
Exception Class Polyfill
Although it is not possible to port the new granular exception changes to older PHP versions, it is possible to polyfill the exception/error classes to older PHP versions. This might be helpful in case the application needs to unserialize exception objects previously thrown under a PHP > 8.3 environment. The polyfill can also be helpful in case the PHP application or libraries it uses decides to make use of the new Exception/Error classes.
class DateError extends Error {}
class DateObjectError extends DateError {}
class DateRangeError extends DateError {}
class DateException extends Exception {}
class DateInvalidTimeZoneException extends DateException {}
class DateInvalidOperationException extends DateException {}
class DateMalformedStringException extends DateException {}
class DateMalformedIntervalStringException extends DateException {}
class DateMalformedPeriodStringException extends DateException {}
Backward Compatibility Impact
This is technically a BC break, because certain warnings in PHP < 8.2 are turned into Exceptions in PHP 8.3 and later. The following exceptions replace a warning condition in PHP 8.2 and early versions.
DateRangeError
:Epoch doesn't fit in a PHP integer
DateInvalidOperationException
:Only non-special relative time specifications are supported for subtraction
DateMalformedIntervalStringException
:String '%s' contains non-relative elements