Validate imported dates in PHP without accepting calendar overflow
A CSV contains the date 2026-02-31. A date parser may normalize it into March instead of treating it as an invalid business date. For an expiry date or reporting period, that silent correction is usually undesirable.
Parse and round-trip
<?php
function importDate(string $input): DateTimeImmutable
{
$date = DateTimeImmutable::createFromFormat(
'!Y-m-d', $input, new DateTimeZone('UTC')
);
$errors = DateTimeImmutable::getLastErrors();
if (!$date
|| ($errors !== false && ($errors['warning_count'] || $errors['error_count']))
|| $date->format('Y-m-d') !== $input) {
throw new InvalidArgumentException('Use a real YYYY-MM-DD date');
}
return $date;
}
assert(importDate('2026-02-28')->format('Y-m-d') === '2026-02-28');
try {
importDate('2026-02-31');
throw new RuntimeException('Expected invalid date to fail');
} catch (InvalidArgumentException) {
echo "invalid calendar date rejected", PHP_EOL;
}
The exclamation mark resets unspecified time fields. The format comparison rejects alternate representations such as an unpadded month. This function deliberately accepts one exact input format.
Separate a date from an instant
A product's expiry date may be a calendar date without a time zone. A webhook timestamp is an instant. Do not automatically convert both into midnight in the visitor's time zone. Decide which meaning the database column represents.
For an instant, require an offset or a documented source time zone, and account for daylight-saving ambiguity where relevant. UTC in this example is a predictable parsing context, not a claim that every business date originated in UTC.
Useful test cases
Include a leap day in a leap year, a leap day in a non-leap year, an impossible month, trailing spaces, and an empty string. If the business allows whitespace, normalize it before calling this strict parser and test that normalization independently.
PHP's createFromFormat reference describes overflow and parsing modifiers. getLastErrors returns false when there are no warnings or errors in modern PHP.