phpdatedatetimevalidationphp-carbon

Why does Carbon::createFromFormat() not throw an exception when given a UK format and a US date?


I have this code:

try {
    Carbon::createFromFormat('dmY', $rawDate)->format('Ymd');
} catch (InvalidFormatException $e) {
    echo 'Oops, bad date format.';
}

If I feed in 31012024 as my $rawDate value, I get 20240131 back as the output. This is correct. Carbon has correctly read and then formatted the date as I would expect it to.

If, instead, I feed in 01312024 (the 1st day of the 31st month of the year), I get back 20260701 as the output. This is not what I'd expect, as the 31st month does not exist, and I would have assumed that Carbon would throw an InvalidFormatException exception because the month simply cannot exist.

I could run the string through a script to validate that the first two characters are lower than 31 and the third and fourth characters are lower than 12, but I don't want to account for the different month lengths and leap years.

My question is, is there a different Carbon method I could call before the createFromFormat to validate that the date format is correct first?

I cannot use parse as this would successfully parse the American date format, which I do not want the script to do; it must accept and validate a UK date format.

No other format but ddmmyyyy should be accepted.

For reference, I am using Carbon 2.72.5 and am not in a Laravel environment, so I cannot use Laravel validation tools.


Solution

  • The createFromFormat function largely does exactly what the base PHP function of the same name does, and will "overflow" any values which don't fit within the normal range of a date. So in your example, July 2026 is 31 months after the start of 2024. It simply assumed you meant "31 months after the beginning of 2024", and worked out what date that is.

    To overcome this you can use Carbon's hasFormatWithModifiers function to check the date format more precisely, before passing it to createFromFormat. This will check that the values for days and months are within the normal range of days and months.

    Caveat: It still doesn't deal with the different lengths of months - e.g. it will allow things like the 31st of September. So you may want to consider addding additional validation of your own for this.

    Example:

    if (Carbon::hasFormatWithModifiers($rawDate, 'dmY')) {
      $dt = Carbon::createFromFormat('dmY', $rawDate)->format('Ymd');
      echo ($dt);
    }
    else echo "Invalid date";
    

    Documentation reference: