diff --git a/ext/date/php_date.c b/ext/date/php_date.c index bbcaf27a4542f..de617101fdd1f 100644 --- a/ext/date/php_date.c +++ b/ext/date/php_date.c @@ -2540,6 +2540,11 @@ PHPAPI bool php_date_initialize_from_ts_double(php_date_obj *dateobj, double ts) sec = (zend_long)sec_dval; usec = (int) round(fmod(ts, 1) * 1000000); + if (UNEXPECTED(abs(usec) == 1000000)) { + sec += usec > 0 ? 1 : -1; + usec = 0; + } + if (UNEXPECTED(usec < 0)) { if (UNEXPECTED(sec == TIMELIB_LONG_MIN)) { zend_argument_error( diff --git a/ext/date/tests/gh14383.phpt b/ext/date/tests/gh14383.phpt new file mode 100644 index 0000000000000..dd145da9a24d7 --- /dev/null +++ b/ext/date/tests/gh14383.phpt @@ -0,0 +1,61 @@ +--TEST-- +Bug GH-14383 (DateTime::createFromTimestamp overflowed microseconds value) +--INI-- +date.timezone=UTC +--FILE-- +format('s.u'), "\n"; +} + +echo "\nminus:\n"; +foreach ($cases as [$usec, $label]) { + echo "-{$label}: "; + echo DateTime::createFromTimestamp(-$usec)->format('s.u'), "\n"; +} +?> +--EXPECT-- +plus: +0.999_999_0: 00.999999 +0.999_999_1: 00.999999 +0.999_999_8: 01.000000 +0.999_999_9: 01.000000 +1.000_000_0: 01.000000 +1.000_000_1: 01.000000 +1.000_000_8: 01.000001 +1.000_000_9: 01.000001 +1.000_001_0: 01.000001 +1.000_001_1: 01.000001 +1.000_001_8: 01.000002 +1.000_001_9: 01.000002 + +minus: +-0.999_999_0: 59.000001 +-0.999_999_1: 59.000001 +-0.999_999_8: 59.000000 +-0.999_999_9: 59.000000 +-1.000_000_0: 59.000000 +-1.000_000_1: 59.000000 +-1.000_000_8: 58.999999 +-1.000_000_9: 58.999999 +-1.000_001_0: 58.999999 +-1.000_001_1: 58.999999 +-1.000_001_8: 58.999998 +-1.000_001_9: 58.999998