Skip to content

Commit 9dcb0bd

Browse files
committed
Merge remote-tracking branch 'derickr/bug75035-big-year-serialisation'
2 parents b1575f9 + 1d0e5ed commit 9dcb0bd

15 files changed

+191
-46
lines changed

NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ PHP NEWS
77
different type). (Derick)
88
. Fixed bug GH-8964 (DateTime object comparison after applying delta less
99
than 1 second). (Derick)
10+
. Fixed bug #75035 (Datetime fails to unserialize "extreme" dates).
11+
(Derick)
12+
. Fixed bug #80483 (DateTime Object with 5-digit year can't unserialized).
13+
(Derick)
1014
. Fixed bug #81263 (Wrong result from DateTimeImmutable::diff). (Derick)
1115

1216
- DBA:

ext/date/lib/parse_date.c

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Generated by re2c 0.15.3 on Sun Jun 26 17:34:01 2022 */
1+
/* Generated by re2c 0.15.3 on Fri Jul 22 15:29:55 2022 */
22
#line 1 "ext/date/lib/parse_date.re"
33
/*
44
* The MIT License (MIT)
@@ -26004,7 +26004,7 @@ timelib_time *timelib_strtotime(const char *s, size_t len, timelib_error_contain
2600426004
add_pbf_error(s, TIMELIB_ERR_UNEXPECTED_DATA, "Unexpected data found.", string, begin); \
2600526005
}
2600626006
#define TIMELIB_CHECK_SIGNED_NUMBER \
26007-
if (strchr("-0123456789", *ptr) == NULL) \
26007+
if (strchr("+-0123456789", *ptr) == NULL) \
2600826008
{ \
2600926009
add_pbf_error(s, TIMELIB_ERR_UNEXPECTED_DATA, "Unexpected data found.", string, begin); \
2601026010
}
@@ -26079,6 +26079,8 @@ static const timelib_format_specifier default_format_map[] = {
2607926079
{' ', TIMELIB_FORMAT_WHITESPACE},
2608026080
{'y', TIMELIB_FORMAT_YEAR_TWO_DIGIT},
2608126081
{'Y', TIMELIB_FORMAT_YEAR_FOUR_DIGIT},
26082+
{'x', TIMELIB_FORMAT_YEAR_EXPANDED},
26083+
{'X', TIMELIB_FORMAT_YEAR_EXPANDED},
2608226084
{'\0', TIMELIB_FORMAT_END}
2608326085
};
2608426086

@@ -26265,6 +26267,15 @@ timelib_time *timelib_parse_from_format_with_map(const char *format, const char
2626526267
break;
2626626268
}
2626726269

26270+
s->time->have_date = 1;
26271+
break;
26272+
case TIMELIB_FORMAT_YEAR_EXPANDED: /* optional symbol, followed by up to 19 digits */
26273+
TIMELIB_CHECK_SIGNED_NUMBER;
26274+
if ((s->time->y = timelib_get_signed_nr(s, &ptr, 19)) == TIMELIB_UNSET) {
26275+
add_pbf_error(s, TIMELIB_ERR_NO_FOUR_DIGIT_YEAR, "An expanded digit year could not be found", string, begin);
26276+
break;
26277+
}
26278+
2626826279
s->time->have_date = 1;
2626926280
break;
2627026281
case TIMELIB_FORMAT_HOUR_TWO_DIGIT_12_MAX: /* two digit hour, without leading zero */

ext/date/lib/parse_date.re

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2006,7 +2006,7 @@ timelib_time *timelib_strtotime(const char *s, size_t len, timelib_error_contain
20062006
add_pbf_error(s, TIMELIB_ERR_UNEXPECTED_DATA, "Unexpected data found.", string, begin); \
20072007
}
20082008
#define TIMELIB_CHECK_SIGNED_NUMBER \
2009-
if (strchr("-0123456789", *ptr) == NULL) \
2009+
if (strchr("+-0123456789", *ptr) == NULL) \
20102010
{ \
20112011
add_pbf_error(s, TIMELIB_ERR_UNEXPECTED_DATA, "Unexpected data found.", string, begin); \
20122012
}
@@ -2081,6 +2081,8 @@ static const timelib_format_specifier default_format_map[] = {
20812081
{' ', TIMELIB_FORMAT_WHITESPACE},
20822082
{'y', TIMELIB_FORMAT_YEAR_TWO_DIGIT},
20832083
{'Y', TIMELIB_FORMAT_YEAR_FOUR_DIGIT},
2084+
{'x', TIMELIB_FORMAT_YEAR_EXPANDED},
2085+
{'X', TIMELIB_FORMAT_YEAR_EXPANDED},
20842086
{'\0', TIMELIB_FORMAT_END}
20852087
};
20862088
@@ -2267,6 +2269,15 @@ timelib_time *timelib_parse_from_format_with_map(const char *format, const char
22672269
break;
22682270
}
22692271

2272+
s->time->have_date = 1;
2273+
break;
2274+
case TIMELIB_FORMAT_YEAR_EXPANDED: /* optional symbol, followed by up to 19 digits */
2275+
TIMELIB_CHECK_SIGNED_NUMBER;
2276+
if ((s->time->y = timelib_get_signed_nr(s, &ptr, 19)) == TIMELIB_UNSET) {
2277+
add_pbf_error(s, TIMELIB_ERR_NO_FOUR_DIGIT_YEAR, "An expanded digit year could not be found", string, begin);
2278+
break;
2279+
}
2280+
22702281
s->time->have_date = 1;
22712282
break;
22722283
case TIMELIB_FORMAT_HOUR_TWO_DIGIT_12_MAX: /* two digit hour, without leading zero */

ext/date/lib/parse_iso_intervals.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Generated by re2c 0.15.3 on Sun Jun 26 17:34:21 2022 */
1+
/* Generated by re2c 0.15.3 on Fri Jul 22 15:30:08 2022 */
22
#line 1 "ext/date/lib/parse_iso_intervals.re"
33
/*
44
* The MIT License (MIT)

ext/date/lib/timelib.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@
3030
# include "timelib_config.h"
3131
#endif
3232

33-
#define TIMELIB_VERSION 202115
34-
#define TIMELIB_EXTENDED_VERSION 20211501
35-
#define TIMELIB_ASCII_VERSION "2021.15"
33+
#define TIMELIB_VERSION 202201
34+
#define TIMELIB_EXTENDED_VERSION 20220101
35+
#define TIMELIB_ASCII_VERSION "2022.01"
3636

3737
#include <stdlib.h>
3838
#include <stdbool.h>
@@ -438,6 +438,7 @@ typedef enum _timelib_format_specifier_code {
438438
TIMELIB_FORMAT_WHITESPACE,
439439
TIMELIB_FORMAT_YEAR_TWO_DIGIT,
440440
TIMELIB_FORMAT_YEAR_FOUR_DIGIT,
441+
TIMELIB_FORMAT_YEAR_EXPANDED,
441442
TIMELIB_FORMAT_YEAR_ISO
442443
} timelib_format_specifier_code;
443444

ext/date/php_date.c

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ PHPAPI time_t php_time(void)
134134
* zone = (( "+" / "-" ) 4DIGIT)
135135
*/
136136
#define DATE_FORMAT_RFC2822 "D, d M Y H:i:s O"
137+
137138
/*
138139
* RFC3339, Section 5.6: http://www.ietf.org/rfc/rfc3339.txt
139140
* date-fullyear = 4DIGIT
@@ -156,8 +157,42 @@ PHPAPI time_t php_time(void)
156157
*/
157158
#define DATE_FORMAT_RFC3339 "Y-m-d\\TH:i:sP"
158159

160+
/*
161+
* This format does not technically match the ISO 8601 standard, as it does not
162+
* use : in the UTC offset format specifier. This is kept for BC reasons. The
163+
* DATE_FORMAT_ISO8601_EXPANDED format does correct this, as well as adding
164+
* support for years out side of the traditional 0000-9999 range.
165+
*/
159166
#define DATE_FORMAT_ISO8601 "Y-m-d\\TH:i:sO"
160167

168+
/* ISO 8601:2004(E)
169+
*
170+
* Section 3.5 Expansion:
171+
* By mutual agreement of the partners in information interchange, it is
172+
* permitted to expand the component identifying the calendar year, which is
173+
* otherwise limited to four digits. This enables reference to dates and times
174+
* in calendar years outside the range supported by complete representations,
175+
* i.e. before the start of the year [0000] or after the end of the year
176+
* [9999]."
177+
*
178+
* Section 4.1.2.4 Expanded representations:
179+
* If, by agreement, expanded representations are used, the formats shall be as
180+
* specified below. The interchange parties shall agree the additional number of
181+
* digits in the time element year. In the examples below it has been agreed to
182+
* expand the time element year with two digits.
183+
* Extended format: ±YYYYY-MM-DD
184+
* Example: +001985-04-12
185+
*
186+
* PHP's year expansion digits are variable.
187+
*/
188+
#define DATE_FORMAT_ISO8601_EXPANDED "X-m-d\\TH:i:sP"
189+
190+
/* Internal Only
191+
* This format only extends the year when needed, keeping the 'P' format with
192+
* colon for UTC offsets
193+
*/
194+
#define DATE_FORMAT_ISO8601_LARGE_YEAR "x-m-d\\TH:i:sP"
195+
161196
/*
162197
* RFC3339, Appendix A: http://www.ietf.org/rfc/rfc3339.txt
163198
* ISO 8601 also requires (in section 5.3.1.3) that a decimal fraction
@@ -659,6 +694,8 @@ static zend_string *date_format(const char *format, size_t format_len, timelib_t
659694
case 'L': length = slprintf(buffer, sizeof(buffer), "%d", timelib_is_leap((int) t->y)); break;
660695
case 'y': length = slprintf(buffer, sizeof(buffer), "%02d", (int) (t->y % 100)); break;
661696
case 'Y': length = slprintf(buffer, sizeof(buffer), "%s%04lld", t->y < 0 ? "-" : "", php_date_llabs((timelib_sll) t->y)); break;
697+
case 'x': length = slprintf(buffer, sizeof(buffer), "%s%04lld", t->y < 0 ? "-" : (t->y >= 10000 ? "+" : ""), php_date_llabs((timelib_sll) t->y)); break;
698+
case 'X': length = slprintf(buffer, sizeof(buffer), "%s%04lld", t->y < 0 ? "-" : "+", php_date_llabs((timelib_sll) t->y)); break;
662699

663700
/* time */
664701
case 'a': length = slprintf(buffer, sizeof(buffer), "%s", t->h >= 12 ? "pm" : "am"); break;
@@ -1810,7 +1847,7 @@ static void date_object_to_hash(php_date_obj *dateobj, HashTable *props)
18101847
zval zv;
18111848

18121849
/* first we add the date and time in ISO format */
1813-
ZVAL_STR(&zv, date_format("Y-m-d H:i:s.u", sizeof("Y-m-d H:i:s.u")-1, dateobj->time, 1));
1850+
ZVAL_STR(&zv, date_format("x-m-d H:i:s.u", sizeof("x-m-d H:i:s.u")-1, dateobj->time, 1));
18141851
zend_hash_str_update(props, "date", sizeof("date")-1, &zv);
18151852

18161853
/* then we add the timezone name (or similar) */
@@ -3800,7 +3837,7 @@ PHP_FUNCTION(timezone_transitions_get)
38003837
#define add_nominal() \
38013838
array_init(&element); \
38023839
add_assoc_long(&element, "ts", timestamp_begin); \
3803-
add_assoc_str(&element, "time", php_format_date(DATE_FORMAT_ISO8601, 13, timestamp_begin, 0)); \
3840+
add_assoc_str(&element, "time", php_format_date(DATE_FORMAT_ISO8601_LARGE_YEAR, 13, timestamp_begin, 0)); \
38043841
add_assoc_long(&element, "offset", tzobj->tzi.tz->type[0].offset); \
38053842
add_assoc_bool(&element, "isdst", tzobj->tzi.tz->type[0].isdst); \
38063843
add_assoc_string(&element, "abbr", &tzobj->tzi.tz->timezone_abbr[tzobj->tzi.tz->type[0].abbr_idx]); \
@@ -3809,7 +3846,7 @@ PHP_FUNCTION(timezone_transitions_get)
38093846
#define add(i,ts) \
38103847
array_init(&element); \
38113848
add_assoc_long(&element, "ts", ts); \
3812-
add_assoc_str(&element, "time", php_format_date(DATE_FORMAT_ISO8601, 13, ts, 0)); \
3849+
add_assoc_str(&element, "time", php_format_date(DATE_FORMAT_ISO8601_LARGE_YEAR, 13, ts, 0)); \
38133850
add_assoc_long(&element, "offset", tzobj->tzi.tz->type[tzobj->tzi.tz->trans_idx[i]].offset); \
38143851
add_assoc_bool(&element, "isdst", tzobj->tzi.tz->type[tzobj->tzi.tz->trans_idx[i]].isdst); \
38153852
add_assoc_string(&element, "abbr", &tzobj->tzi.tz->timezone_abbr[tzobj->tzi.tz->type[tzobj->tzi.tz->trans_idx[i]].abbr_idx]); \
@@ -3818,7 +3855,7 @@ PHP_FUNCTION(timezone_transitions_get)
38183855
#define add_by_index(i,ts) \
38193856
array_init(&element); \
38203857
add_assoc_long(&element, "ts", ts); \
3821-
add_assoc_str(&element, "time", php_format_date(DATE_FORMAT_ISO8601, 13, ts, 0)); \
3858+
add_assoc_str(&element, "time", php_format_date(DATE_FORMAT_ISO8601_LARGE_YEAR, 13, ts, 0)); \
38223859
add_assoc_long(&element, "offset", tzobj->tzi.tz->type[i].offset); \
38233860
add_assoc_bool(&element, "isdst", tzobj->tzi.tz->type[i].isdst); \
38243861
add_assoc_string(&element, "abbr", &tzobj->tzi.tz->timezone_abbr[tzobj->tzi.tz->type[i].abbr_idx]); \
@@ -3827,7 +3864,7 @@ PHP_FUNCTION(timezone_transitions_get)
38273864
#define add_from_tto(to,ts) \
38283865
array_init(&element); \
38293866
add_assoc_long(&element, "ts", ts); \
3830-
add_assoc_str(&element, "time", php_format_date(DATE_FORMAT_ISO8601, 13, ts, 0)); \
3867+
add_assoc_str(&element, "time", php_format_date(DATE_FORMAT_ISO8601_LARGE_YEAR, 13, ts, 0)); \
38313868
add_assoc_long(&element, "offset", (to)->offset); \
38323869
add_assoc_bool(&element, "isdst", (to)->is_dst); \
38333870
add_assoc_string(&element, "abbr", (to)->abbr); \

ext/date/php_date.stub.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@
2020
*/
2121
const DATE_ISO8601 = "Y-m-d\\TH:i:sO";
2222

23+
/**
24+
* @var string
25+
* @cvalue DATE_FORMAT_ISO8601_EXPANDED
26+
*/
27+
const DATE_ISO8601_EXPANDED = "X-m-d\\TH:i:sP";
28+
2329
/**
2430
* @var string
2531
* @cvalue DATE_FORMAT_RFC822
@@ -285,6 +291,8 @@ interface DateTimeInterface
285291
/** @var string */
286292
public const ISO8601 = DATE_ISO8601;
287293
/** @var string */
294+
public const ISO8601_EXPANDED = DATE_ISO8601_EXPANDED;
295+
/** @var string */
288296
public const RFC822 = DATE_RFC822;
289297
/** @var string */
290298
public const RFC850 = DATE_RFC850;

ext/date/php_date_arginfo.h

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/date/tests/DateTimeZone_getTransitions_basic1.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ array(5) {
3232
["ts"]=>
3333
int(-213228000)
3434
["time"]=>
35-
string(24) "1963-03-31T02:00:00+0000"
35+
string(25) "1963-03-31T02:00:00+00:00"
3636
["offset"]=>
3737
int(3600)
3838
["isdst"]=>

ext/date/tests/DateTimeZone_getTransitions_bug1.phpt

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,35 +24,35 @@ showTransitions('Europe/Paris', 1645095600); // GH Issue 8108
2424
--EXPECT--
2525
Europe/London from @1648342200-@1727398200:
2626

27-
1648342200 2022-03-27T00:50:00+0000 0 x GMT
28-
1648342800 2022-03-27T01:00:00+0000 3600 DST BST
29-
1667091600 2022-10-30T01:00:00+0000 0 x GMT
30-
1679792400 2023-03-26T01:00:00+0000 3600 DST BST
31-
1698541200 2023-10-29T01:00:00+0000 0 x GMT
32-
1711846800 2024-03-31T01:00:00+0000 3600 DST BST
27+
1648342200 2022-03-27T00:50:00+00:00 0 x GMT
28+
1648342800 2022-03-27T01:00:00+00:00 3600 DST BST
29+
1667091600 2022-10-30T01:00:00+00:00 0 x GMT
30+
1679792400 2023-03-26T01:00:00+00:00 3600 DST BST
31+
1698541200 2023-10-29T01:00:00+00:00 0 x GMT
32+
1711846800 2024-03-31T01:00:00+00:00 3600 DST BST
3333

3434
America/Los_Angeles from @1648557596-@1727613596:
3535

36-
1648557596 2022-03-29T12:39:56+0000 -25200 DST PDT
37-
1667725200 2022-11-06T09:00:00+0000 -28800 x PST
38-
1678615200 2023-03-12T10:00:00+0000 -25200 DST PDT
39-
1699174800 2023-11-05T09:00:00+0000 -28800 x PST
40-
1710064800 2024-03-10T10:00:00+0000 -25200 DST PDT
36+
1648557596 2022-03-29T12:39:56+00:00 -25200 DST PDT
37+
1667725200 2022-11-06T09:00:00+00:00 -28800 x PST
38+
1678615200 2023-03-12T10:00:00+00:00 -25200 DST PDT
39+
1699174800 2023-11-05T09:00:00+00:00 -28800 x PST
40+
1710064800 2024-03-10T10:00:00+00:00 -25200 DST PDT
4141

4242
America/Chicago from @1293861600-@1372917600:
4343

44-
1293861600 2011-01-01T06:00:00+0000 -21600 x CST
45-
1300003200 2011-03-13T08:00:00+0000 -18000 DST CDT
46-
1320562800 2011-11-06T07:00:00+0000 -21600 x CST
47-
1331452800 2012-03-11T08:00:00+0000 -18000 DST CDT
48-
1352012400 2012-11-04T07:00:00+0000 -21600 x CST
49-
1362902400 2013-03-10T08:00:00+0000 -18000 DST CDT
44+
1293861600 2011-01-01T06:00:00+00:00 -21600 x CST
45+
1300003200 2011-03-13T08:00:00+00:00 -18000 DST CDT
46+
1320562800 2011-11-06T07:00:00+00:00 -21600 x CST
47+
1331452800 2012-03-11T08:00:00+00:00 -18000 DST CDT
48+
1352012400 2012-11-04T07:00:00+00:00 -21600 x CST
49+
1362902400 2013-03-10T08:00:00+00:00 -18000 DST CDT
5050

5151
Europe/Paris from @1645095600-@1724151600:
5252

53-
1645095600 2022-02-17T11:00:00+0000 3600 x CET
54-
1648342800 2022-03-27T01:00:00+0000 7200 DST CEST
55-
1667091600 2022-10-30T01:00:00+0000 3600 x CET
56-
1679792400 2023-03-26T01:00:00+0000 7200 DST CEST
57-
1698541200 2023-10-29T01:00:00+0000 3600 x CET
58-
1711846800 2024-03-31T01:00:00+0000 7200 DST CEST
53+
1645095600 2022-02-17T11:00:00+00:00 3600 x CET
54+
1648342800 2022-03-27T01:00:00+00:00 7200 DST CEST
55+
1667091600 2022-10-30T01:00:00+00:00 3600 x CET
56+
1679792400 2023-03-26T01:00:00+00:00 7200 DST CEST
57+
1698541200 2023-10-29T01:00:00+00:00 3600 x CET
58+
1711846800 2024-03-31T01:00:00+00:00 7200 DST CEST

ext/date/tests/bug75035.phpt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
--TEST--
2+
Bug #75035 (Datetime fails to unserialize "extreme" dates)
3+
--INI--
4+
date.timezone=UTC
5+
--FILE--
6+
<?php
7+
var_dump('PHP version', PHP_VERSION);
8+
9+
foreach ([PHP_INT_MIN, PHP_INT_MAX] as $extreme) {
10+
$i = 64;
11+
while ($i --> 0) {
12+
$d = new DateTime('@' . ($extreme >> $i));
13+
$s = serialize($d);
14+
try {
15+
$u = unserialize($s);
16+
} catch (Error $e) {
17+
$u = "failed unserialization: " . $e->getMessage() . ' : ' . $s;
18+
}
19+
$original = $d->format('Y-m-d H:i:s');
20+
$serializedUnserialized = is_string($u) ? $u : $u->format('Y-m-d H:i:s');
21+
if ($original !== $serializedUnserialized) {
22+
var_dump('[' . ($extreme >> $i) . '] ' . $original . ' => ' . $serializedUnserialized);
23+
}
24+
}
25+
}
26+
?>
27+
--EXPECTF--
28+
string(11) "PHP version"
29+
string(%d) "%s"

0 commit comments

Comments
 (0)