Parsing Dates

Parsing, Validating and Comparing Dates

The free-form parser rolls 30 February into March, which is wrong for user input. createFromFormat() parses an exact format, a leading ! resets unspecified fields to the epoch instead of "now", and a round trip through format() rejects overflow. Since PHP 8.3 bad strings throw DateMalformedStringException.

Strict validation, parse errors and comparison by instant (parse.php)PHP
<?php
function parseDate(string $input, string $format = 'Y-m-d'): ?DateTimeImmutable
{
    $d = DateTimeImmutable::createFromFormat('!' . $format, $input, new DateTimeZone('UTC'));
    return $d !== false && $d->format($format) === $input ? $d : null;   // exact round trip
}
echo implode(', ', array_map(fn($in) => "$in: " . (parseDate($in)?->format('D j M') ?? 'no'),
    ['2026-09-23', '2026-02-30', '23/09/2026', '2026-9-3'])), "\n";
$loose = DateTimeImmutable::createFromFormat('Y-m-d', '2026-02-30');   // no "!": time is now
$errors = DateTimeImmutable::getLastErrors();                         // false if none
echo $loose->format('Y-m-d H:i:s'), ' ', json_encode($errors['warnings']), ' ',
    var_export(checkdate(2, 29, 2027), true), "\n";
try { new DateTimeImmutable('next blursday'); }                     // PHP 8.3+ exception
catch (DateMalformedStringException $e) { echo get_class($e), PHP_EOL; }
$a = new DateTimeImmutable('2026-09-23 14:00', new DateTimeZone('Asia/Kuala_Lumpur'));
$b = new DateTimeImmutable('2026-09-23 06:00', new DateTimeZone('UTC'));
echo json_encode([$a == $b, $a < $b->modify('+1 second'), $a <=> $b]), ' ',
    max($a->modify('+2 days'), $b, $a->modify('-1 day'))->format('M j'), "\n";
Output
2026-09-23: Wed 23 Sep, 2026-02-30: no, 23/09/2026: no, 2026-9-3: no
2026-03-02 07:48:09 {"10":"The parsed date was invalid"} false
DateMalformedStringException
[true,true,0] Sep 25

Without !, the loose parse returned 2 March with the time of the run; only a warning in getLastErrors() betrays it. checkdate($month, $day, $year) validates separate form fields. Date objects compare with ==, <, <=> and max() by instant, so 14:00 in Kuala Lumpur equals 06:00 UTC.

For heavier date work, Carbon 16,596 (https://github.com/briannesbitt/Carbon 16,596 ) (composer require nesbot/carbon; 3.14.0 of 12 September 2026, PHP 8.1 or later) extends DateTime and DateTimeImmutable with helpers such as addWeekdays() and diffForHumans(). Laravel 2,157 's dates are Carbon objects (Laravel).