Skip to content

Commit a2133a3

Browse files
authored
PHPC-2429: Fix UTCDateTime with negative timestamps (#1623)
* PHPC-2429: Fix UTCDateTime with negative timestamps * Add missing include for llabs * Fix wrong version number in comment
1 parent f47915e commit a2133a3

File tree

2 files changed

+52
-5
lines changed

2 files changed

+52
-5
lines changed

src/BSON/UTCDateTime.c

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
#include <math.h>
18+
#include <inttypes.h>
1819

1920
#include <php.h>
2021
#include <zend_smart_str.h>
@@ -213,8 +214,9 @@ static PHP_METHOD(MongoDB_BSON_UTCDateTime, toDateTime)
213214
{
214215
php_phongo_utcdatetime_t* intern;
215216
php_date_obj* datetime_obj;
216-
char* sec;
217+
char* sec_str;
217218
size_t sec_len;
219+
int64_t sec, usec;
218220

219221
intern = Z_UTCDATETIME_OBJ_P(getThis());
220222

@@ -223,11 +225,23 @@ static PHP_METHOD(MongoDB_BSON_UTCDateTime, toDateTime)
223225
object_init_ex(return_value, php_date_get_date_ce());
224226
datetime_obj = Z_PHPDATE_P(return_value);
225227

226-
sec_len = spprintf(&sec, 0, "@%" PRId64, intern->milliseconds / 1000);
227-
php_date_initialize(datetime_obj, sec, sec_len, NULL, NULL, 0);
228-
efree(sec);
228+
sec = intern->milliseconds / 1000;
229+
usec = (llabs(intern->milliseconds) % 1000) * 1000;
230+
if (intern->milliseconds < 0 && usec != 0) {
231+
/* For dates before the unix epoch, we need to subtract the microseconds from the timestamp.
232+
* Since we can't directly pass microseconds when calling php_date_initialize due to a bug in PHP,
233+
* we manually decrement the timestamp and subtract the number of microseconds from a full seconds
234+
* to store in the us field. */
235+
sec--;
236+
usec = 1000000 - usec;
237+
}
238+
239+
/* TODO PHP 8.1.7+: microseconds can be included in the format string */
240+
sec_len = spprintf(&sec_str, 0, "@%" PRId64, sec);
241+
php_date_initialize(datetime_obj, sec_str, sec_len, NULL, NULL, 0);
242+
efree(sec_str);
229243

230-
datetime_obj->time->us = (intern->milliseconds % 1000) * 1000;
244+
datetime_obj->time->us = usec;
231245
}
232246

233247
static PHP_METHOD(MongoDB_BSON_UTCDateTime, jsonSerialize)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
--TEST--
2+
MongoDB\BSON\UTCDateTime::toDateTime() with dates before the Unix epoch
3+
--INI--
4+
date.timezone=UTC
5+
--FILE--
6+
<?php
7+
8+
$dates = [
9+
'1960-01-01 12:12:12.1',
10+
'1969-12-31 23:59:59.999',
11+
];
12+
13+
foreach ($dates as $date) {
14+
$dateTime = new \DateTimeImmutable($date);
15+
echo $dateTime->format(DateTimeInterface::RFC3339_EXTENDED), PHP_EOL;
16+
17+
$utcDateTime = new MongoDB\BSON\UTCDateTime($dateTime);
18+
19+
$newDate = $utcDateTime->toDateTime();
20+
echo $newDate->format(DateTimeInterface::RFC3339_EXTENDED), PHP_EOL, PHP_EOL;
21+
}
22+
23+
?>
24+
===DONE===
25+
<?php exit(0); ?>
26+
--EXPECT--
27+
1960-01-01T12:12:12.100+00:00
28+
1960-01-01T12:12:12.100+00:00
29+
30+
1969-12-31T23:59:59.999+00:00
31+
1969-12-31T23:59:59.999+00:00
32+
33+
===DONE===

0 commit comments

Comments
 (0)