All Snippets Snippet PHP

Date Format Normalizer

2024/01/15 Jan 15, 2024 15-01-2024 2024-01-15

Normalize inconsistent date formats into a single standard output with placeholder detection and multi-format fallback.

D
Kunwar "AKA" AJ Sharing what I have learned
Feb 20, 2026 2 min PHP

The Pattern

normalizeDate.php
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;
    }
}

What Happens Under the Hood

Let us trace what happens with different date formats from real source systems:

execution-flow.txt
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.

Usage Example

usage.php
// 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.