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
233247static PHP_METHOD (MongoDB_BSON_UTCDateTime , jsonSerialize )
0 commit comments