Retry Decorator
A reusable decorator that adds exponential backoff retry logic to any function without modifying its…
Normalize inconsistent date formats into a single standard output with placeholder detection and multi-format fallback.
function normalizeDate(?string $value, string $outputFormat = 'Y-m-d'): ?string
{
if ($value === null || trim($value) === '') {
return null;
}
// Detect placeholder values from legacy systems
$placeholders = ['0000-00-00', '0000-00-00 00:00:00', 'NULL', 'N/A', '-'];
if (in_array(strtoupper(trim($value)), array_map('strtoupper', $placeholders))) {
return null;
}
// Try common formats in order of likelihood
$formats = [
'Y-m-d', // 2024-01-15
'Y-m-d H:i:s', // 2024-01-15 10:30:00
'm/d/Y', // 01/15/2024
'd/m/Y', // 15/01/2024
'M d, Y', // Jan 15, 2024
'F d, Y', // January 15, 2024
'Y/m/d', // 2024/01/15
'U', // Unix timestamp
];
foreach ($formats as $format) {
$date = DateTimeImmutable::createFromFormat($format, trim($value));
if ($date !== false && $date->format($format) === trim($value)) {
return $date->format($outputFormat);
}
}
// Last resort: let PHP try to parse it
try {
$date = new DateTimeImmutable(trim($value));
return $date->format($outputFormat);
} catch (Exception) {
return null;
}
}
Let us trace what happens with different date formats from real source systems:
normalizeDate("01/15/2024")
→ Not null, not empty, not placeholder
→ Try Y-m-d: fails (not 2024-01-15 format)
→ Try Y-m-d H:i:s: fails
→ Try m/d/Y: succeeds! → return "2024-01-15"
normalizeDate("0000-00-00")
→ Matches placeholder list → return null
normalizeDate("Jan 15, 2024")
→ Try Y-m-d: fails
→ Try m/d/Y: fails
→ Try M d, Y: succeeds! → return "2024-01-15"
normalizeDate(null)
→ Null input → return null immediately
The strict check $date->format($format) === trim($value) prevents false positives. Without it, PHP would parse “30/01/2024” as a valid m/d/Y date (interpreting 30 as a month), producing a wrong result instead of falling through to the d/m/Y format.
// Normalize dates from inconsistent source systems
$record['birth_date'] = normalizeDate($row['DOB']); // "01/15/1990" → "1990-01-15"
$record['created_at'] = normalizeDate($row['created']); // "2024-01-15 10:30:00" → "2024-01-15"
$record['expired'] = normalizeDate($row['exp_dt']); // "0000-00-00" → null
$record['updated'] = normalizeDate($row['last_update']); // "Jan 15, 2024" → "2024-01-15"
// Custom output format
$record['display_date'] = normalizeDate($row['DOB'], 'F j, Y'); // → "January 15, 1990"
Dates are the most common source of ETL failures. Every system stores them differently. This function handles the formats you will encounter in production — including the MySQL “0000-00-00” placeholder that crashes most date parsers.