Skip to content

Commit bfc9885

Browse files
Fixed GH-14383: Fixed usec was carry up (#14463)
If round to the fractional part of a timestamp, a carry will occur in cases such as 999 999 9. In that case, set usec to 0 and add/sub 1 to sec.
1 parent 5433f02 commit bfc9885

File tree

2 files changed

+66
-0
lines changed

2 files changed

+66
-0
lines changed

ext/date/php_date.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2537,6 +2537,11 @@ PHPAPI bool php_date_initialize_from_ts_double(php_date_obj *dateobj, double ts)
25372537
sec = (zend_long)sec_dval;
25382538
usec = (int) round(fmod(ts, 1) * 1000000);
25392539

2540+
if (UNEXPECTED(abs(usec) == 1000000)) {
2541+
sec += usec > 0 ? 1 : -1;
2542+
usec = 0;
2543+
}
2544+
25402545
if (UNEXPECTED(usec < 0)) {
25412546
if (UNEXPECTED(sec == TIMELIB_LONG_MIN)) {
25422547
zend_argument_error(

ext/date/tests/gh14383.phpt

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
--TEST--
2+
Bug GH-14383 (DateTime::createFromTimestamp overflowed microseconds value)
3+
--INI--
4+
date.timezone=UTC
5+
--FILE--
6+
<?php
7+
$cases = [
8+
[0.999_999_0, '0.999_999_0'],
9+
[0.999_999_1, '0.999_999_1'],
10+
[0.999_999_8, '0.999_999_8'],
11+
[0.999_999_9, '0.999_999_9'],
12+
[1.000_000_0, '1.000_000_0'],
13+
[1.000_000_1, '1.000_000_1'],
14+
[1.000_000_8, '1.000_000_8'],
15+
[1.000_000_9, '1.000_000_9'],
16+
[1.000_001_0, '1.000_001_0'],
17+
[1.000_001_1, '1.000_001_1'],
18+
[1.000_001_8, '1.000_001_8'],
19+
[1.000_001_9, '1.000_001_9'],
20+
];
21+
22+
echo "plus:\n";
23+
foreach ($cases as [$usec, $label]) {
24+
echo "{$label}: ";
25+
echo DateTime::createFromTimestamp($usec)->format('s.u'), "\n";
26+
}
27+
28+
echo "\nminus:\n";
29+
foreach ($cases as [$usec, $label]) {
30+
echo "-{$label}: ";
31+
echo DateTime::createFromTimestamp(-$usec)->format('s.u'), "\n";
32+
}
33+
?>
34+
--EXPECT--
35+
plus:
36+
0.999_999_0: 00.999999
37+
0.999_999_1: 00.999999
38+
0.999_999_8: 01.000000
39+
0.999_999_9: 01.000000
40+
1.000_000_0: 01.000000
41+
1.000_000_1: 01.000000
42+
1.000_000_8: 01.000001
43+
1.000_000_9: 01.000001
44+
1.000_001_0: 01.000001
45+
1.000_001_1: 01.000001
46+
1.000_001_8: 01.000002
47+
1.000_001_9: 01.000002
48+
49+
minus:
50+
-0.999_999_0: 59.000001
51+
-0.999_999_1: 59.000001
52+
-0.999_999_8: 59.000000
53+
-0.999_999_9: 59.000000
54+
-1.000_000_0: 59.000000
55+
-1.000_000_1: 59.000000
56+
-1.000_000_8: 58.999999
57+
-1.000_000_9: 58.999999
58+
-1.000_001_0: 58.999999
59+
-1.000_001_1: 58.999999
60+
-1.000_001_8: 58.999998
61+
-1.000_001_9: 58.999998

0 commit comments

Comments
 (0)