php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #79580 date_create_from_format misses leap year
Submitted: 2020-05-10 13:22 UTC Modified: 2020-05-10 13:41 UTC
Votes:1
Avg. Score:5.0 ± 0.0
Reproduced:1 of 1 (100.0%)
Same Version:1 (100.0%)
Same OS:1 (100.0%)
From: mcab at acm dot org Assigned:
Status: Verified Package: Date/time related
PHP Version: 7.2.30 OS: Linux
Private report: No CVE-ID: None
Have you experienced this issue?
Rate the importance of this bug to you:

 [2020-05-10 13:22 UTC] mcab at acm dot org
Description:
------------
date_create_from_format when format is 'z Y' is a day out when day number is after feb28.


Test script:
---------------
$TZ= timezone_open( "UTC" );
$refdate=date_create_from_format ( "z Y" ,"31 2020" ,$TZ );
echo date_format($refdate,"d/m/Y")."<br>";

$refdate=date_create_from_format ( "z Y" ,"60 2020" ,$TZ );
echo date_format($refdate,"d/m/Y")."<br>";

$refdate=date_create_from_format ( "z Y" ,"91 2020" ,$TZ );
echo date_format($refdate,"d/m/Y")."<br>";

$refdate=date_create_from_format ( "z Y" ,"121 2020" ,$TZ );
echo date_format($refdate,"d/m/Y")."<br>";

$refdate=date_create_from_format ( "z Y" ,"130 2020" ,$TZ );
echo date_format($refdate,"d/m/Y")."<br>";

/*  Output is 
01/02/2020
02/03/2020   <- This has missed the fact that 2020 is a leap year
02/04/2020   <- All future date are wrong
02/05/2020
11/05/2020
*/


Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2020-05-10 13:41 UTC] cmb@php.net
-Status: Open +Status: Verified
 [2020-05-10 13:41 UTC] cmb@php.net
Confirmed: <https://3v4l.org/Drfk6>.
 [2020-05-10 13:45 UTC] rowan dot collins at gmail dot com
It looks like the problem is that the 'z' specifier works by setting "month=1, day=X" (in this case, "the 60th of January"), then normalising the date immediately. But at that point, it doesn't know what year to use for the normalisation, so picks 1970 (which wasn't a leap year).

https://heap.space/xref/php-src/ext/date/lib/parse_date.c?r=9c608bd1#25213

If you reverse the format, you get the expected result, because the correct year has already been selected when the "normalise" call happens:

$TZ=timezone_open( "UTC" );
$refdate=date_create_from_format ( "Y z" ,"2020 60" ,$TZ );
echo date_format($refdate,"d/m/Y");

# 01/03/2020


A possible fix would be to delay the call to timelib_do_normalize until after the whole input has been parsed, including the year specifier.
 [2020-05-10 17:21 UTC] rowan dot collins at gmail dot com
Test and tentative fix here: https://github.com/php/php-src/compare/PHP-7.3...IMSoP:bug-79580 (will actually need a change to timelib, but I'm not sure how to test it there).

This implements the approach in my last comment, but results in a change of behaviour for the slightly odd case of specifying a day-of-year and then a month within the same time string:

echo DateTimeImmutable::createFromFormat("z m Y", '58 3 2019')->format('z: Y-m-d'), "\n";
# Previously "86: 2019-03-28"
# Now "117: 2019-04-28"

I'm not sure what to do about that, or even what the correct behaviour for that combination should be.
 [2020-05-10 18:14 UTC] rowan dot collins at gmail dot com
Branch updated with an alternative implementation that makes mixing 'z' with any month or day specifiers an error.
 [2020-05-10 18:37 UTC] rowan dot collins at gmail dot com
Now also raised as a PR against timelib: https://github.com/derickr/timelib/pull/80
 
PHP Copyright © 2001-2020 The PHP Group
All rights reserved.
Last updated: Sat Jul 04 15:01:26 2020 UTC