@@ -53,8 +53,10 @@ struct module_state {
53
53
PyObject * Decimal128 ;
54
54
PyObject * Mapping ;
55
55
PyObject * DatetimeMS ;
56
- PyObject * _min_datetime_ms ;
57
- PyObject * _max_datetime_ms ;
56
+ PyObject * min_datetime ;
57
+ PyObject * max_datetime ;
58
+ PyObject * replace_args ;
59
+ PyObject * replace_kwargs ;
58
60
PyObject * _type_marker_str ;
59
61
PyObject * _flags_str ;
60
62
PyObject * _pattern_str ;
@@ -80,6 +82,8 @@ struct module_state {
80
82
PyObject * _from_uuid_str ;
81
83
PyObject * _as_uuid_str ;
82
84
PyObject * _from_bid_str ;
85
+ int64_t min_millis ;
86
+ int64_t max_millis ;
83
87
};
84
88
85
89
#define GETSTATE (m ) ((struct module_state*)PyModule_GetState(m))
@@ -253,7 +257,7 @@ static PyObject* datetime_from_millis(long long millis) {
253
257
* 2. Multiply that by 1000: 253402300799000
254
258
* 3. Add in microseconds divided by 1000 253402300799999
255
259
*
256
- * (Note: BSON doesn't support microsecond accuracy, hence the rounding .)
260
+ * (Note: BSON doesn't support microsecond accuracy, hence the truncation .)
257
261
*
258
262
* To decode we could do:
259
263
* 1. Get seconds: timestamp / 1000: 253402300799
@@ -376,6 +380,118 @@ static int millis_from_datetime_ms(PyObject* dt, long long* out){
376
380
return 1 ;
377
381
}
378
382
383
+ static PyObject * decode_datetime (PyObject * self , long long millis , const codec_options_t * options ){
384
+ PyObject * naive = NULL ;
385
+ PyObject * replace = NULL ;
386
+ PyObject * args = NULL ;
387
+ PyObject * kwargs = NULL ;
388
+ PyObject * value = NULL ;
389
+ struct module_state * state = GETSTATE (self );
390
+ if (options -> datetime_conversion == DATETIME_MS ){
391
+ return datetime_ms_from_millis (self , millis );
392
+ }
393
+
394
+ int dt_clamp = options -> datetime_conversion == DATETIME_CLAMP ;
395
+ int dt_auto = options -> datetime_conversion == DATETIME_AUTO ;
396
+
397
+ if (dt_clamp || dt_auto ){
398
+ int64_t min_millis = state -> min_millis ;
399
+ int64_t max_millis = state -> max_millis ;
400
+ int64_t min_millis_offset = 0 ;
401
+ int64_t max_millis_offset = 0 ;
402
+ if (options -> tz_aware && options -> tzinfo && options -> tzinfo != Py_None ) {
403
+ PyObject * utcoffset = PyObject_CallMethodObjArgs (options -> tzinfo , state -> _utcoffset_str , state -> min_datetime , NULL );
404
+ if (utcoffset == NULL ) {
405
+ return 0 ;
406
+ }
407
+ if (utcoffset != Py_None ) {
408
+ if (!PyDelta_Check (utcoffset )) {
409
+ PyObject * BSONError = _error ("BSONError" );
410
+ if (BSONError ) {
411
+ PyErr_SetString (BSONError , "tzinfo.utcoffset() did not return a datetime.timedelta" );
412
+ Py_DECREF (BSONError );
413
+ }
414
+ Py_DECREF (utcoffset );
415
+ return 0 ;
416
+ }
417
+ min_millis_offset = (PyDateTime_DELTA_GET_DAYS (utcoffset ) * 86400 +
418
+ PyDateTime_DELTA_GET_SECONDS (utcoffset )) * 1000 +
419
+ (PyDateTime_DELTA_GET_MICROSECONDS (utcoffset ) / 1000 );
420
+ }
421
+ Py_DECREF (utcoffset );
422
+ utcoffset = PyObject_CallMethodObjArgs (options -> tzinfo , state -> _utcoffset_str , state -> max_datetime , NULL );
423
+ if (utcoffset == NULL ) {
424
+ return 0 ;
425
+ }
426
+ if (utcoffset != Py_None ) {
427
+ if (!PyDelta_Check (utcoffset )) {
428
+ PyObject * BSONError = _error ("BSONError" );
429
+ if (BSONError ) {
430
+ PyErr_SetString (BSONError , "tzinfo.utcoffset() did not return a datetime.timedelta" );
431
+ Py_DECREF (BSONError );
432
+ }
433
+ Py_DECREF (utcoffset );
434
+ return 0 ;
435
+ }
436
+ max_millis_offset = (PyDateTime_DELTA_GET_DAYS (utcoffset ) * 86400 +
437
+ PyDateTime_DELTA_GET_SECONDS (utcoffset )) * 1000 +
438
+ (PyDateTime_DELTA_GET_MICROSECONDS (utcoffset ) / 1000 );
439
+ }
440
+ Py_DECREF (utcoffset );
441
+ }
442
+ if (min_millis_offset < 0 ) {
443
+ min_millis -= min_millis_offset ;
444
+ }
445
+
446
+ if (max_millis_offset > 0 ) {
447
+ max_millis -= max_millis_offset ;
448
+ }
449
+
450
+ if (dt_clamp ) {
451
+ if (millis < min_millis ) {
452
+ millis = min_millis ;
453
+ } else if (millis > max_millis ) {
454
+ millis = max_millis ;
455
+ }
456
+ // Continues from here to return a datetime.
457
+ } else { // dt_auto
458
+ if (millis < min_millis || millis > max_millis ){
459
+ return datetime_ms_from_millis (self , millis );
460
+ }
461
+ }
462
+ }
463
+
464
+ naive = datetime_from_millis (millis );
465
+ if (!naive ) {
466
+ goto invalid ;
467
+ }
468
+
469
+ if (!options -> tz_aware ) { /* In the naive case, we're done here. */
470
+ return naive ;
471
+ }
472
+ replace = PyObject_GetAttr (naive , state -> _replace_str );
473
+ if (!replace ) {
474
+ goto invalid ;
475
+ }
476
+ value = PyObject_Call (replace , state -> replace_args , state -> replace_kwargs );
477
+ if (!value ) {
478
+ goto invalid ;
479
+ }
480
+
481
+ /* convert to local time */
482
+ if (options -> tzinfo != Py_None ) {
483
+ PyObject * temp = PyObject_CallMethodObjArgs (value , state -> _astimezone_str , options -> tzinfo , NULL );
484
+ Py_DECREF (value );
485
+ value = temp ;
486
+ }
487
+ invalid :
488
+ Py_XDECREF (naive );
489
+ Py_XDECREF (replace );
490
+ Py_XDECREF (args );
491
+ Py_XDECREF (kwargs );
492
+ return value ;
493
+ }
494
+
379
495
/* Just make this compatible w/ the old API. */
380
496
int buffer_write_bytes (buffer_t buffer , const char * data , int size ) {
381
497
if (pymongo_buffer_write (buffer , data , size )) {
@@ -482,6 +598,8 @@ static int _load_python_objects(PyObject* module) {
482
598
PyObject * empty_string = NULL ;
483
599
PyObject * re_compile = NULL ;
484
600
PyObject * compiled = NULL ;
601
+ PyObject * min_datetime_ms = NULL ;
602
+ PyObject * max_datetime_ms = NULL ;
485
603
struct module_state * state = GETSTATE (module );
486
604
if (!state ) {
487
605
return 1 ;
@@ -530,10 +648,34 @@ static int _load_python_objects(PyObject* module) {
530
648
_load_object (& state -> UUID , "uuid" , "UUID" ) ||
531
649
_load_object (& state -> Mapping , "collections.abc" , "Mapping" ) ||
532
650
_load_object (& state -> DatetimeMS , "bson.datetime_ms" , "DatetimeMS" ) ||
533
- _load_object (& state -> _min_datetime_ms , "bson.datetime_ms" , "_min_datetime_ms" ) ||
534
- _load_object (& state -> _max_datetime_ms , "bson.datetime_ms" , "_max_datetime_ms" )) {
651
+ _load_object (& min_datetime_ms , "bson.datetime_ms" , "_MIN_UTC_MS" ) ||
652
+ _load_object (& max_datetime_ms , "bson.datetime_ms" , "_MAX_UTC_MS" ) ||
653
+ _load_object (& state -> min_datetime , "bson.datetime_ms" , "_MIN_UTC" ) ||
654
+ _load_object (& state -> max_datetime , "bson.datetime_ms" , "_MAX_UTC" )) {
655
+ return 1 ;
656
+ }
657
+
658
+ state -> min_millis = PyLong_AsLongLong (min_datetime_ms );
659
+ state -> max_millis = PyLong_AsLongLong (max_datetime_ms );
660
+ Py_DECREF (min_datetime_ms );
661
+ Py_DECREF (max_datetime_ms );
662
+ if ((state -> min_millis == -1 || state -> max_millis == -1 ) && PyErr_Occurred ()) {
663
+ return 1 ;
664
+ }
665
+
666
+ /* Speed up datetime.replace(tzinfo=utc) call */
667
+ state -> replace_args = PyTuple_New (0 );
668
+ if (!state -> replace_args ) {
669
+ return 1 ;
670
+ }
671
+ state -> replace_kwargs = PyDict_New ();
672
+ if (!state -> replace_kwargs ) {
535
673
return 1 ;
536
674
}
675
+ if (PyDict_SetItem (state -> replace_kwargs , state -> _tzinfo_str , state -> UTC ) == -1 ) {
676
+ return 1 ;
677
+ }
678
+
537
679
/* Reload our REType hack too. */
538
680
empty_string = PyBytes_FromString ("" );
539
681
if (empty_string == NULL ) {
@@ -1247,15 +1389,16 @@ static int _write_element_to_buffer(PyObject* self, buffer_t buffer,
1247
1389
return 0 ;
1248
1390
if (utcoffset != Py_None ) {
1249
1391
PyObject * result = PyNumber_Subtract (value , utcoffset );
1250
- Py_DECREF (utcoffset );
1251
1392
if (!result ) {
1393
+ Py_DECREF (utcoffset );
1252
1394
return 0 ;
1253
1395
}
1254
1396
millis = millis_from_datetime (result );
1255
1397
Py_DECREF (result );
1256
1398
} else {
1257
1399
millis = millis_from_datetime (value );
1258
1400
}
1401
+ Py_DECREF (utcoffset );
1259
1402
* (pymongo_buffer_get_buffer (buffer ) + type_byte ) = 0x09 ;
1260
1403
return buffer_write_int64 (buffer , (int64_t )millis );
1261
1404
} else if (PyObject_TypeCheck (value , state -> REType )) {
@@ -2043,11 +2186,6 @@ static PyObject* get_value(PyObject* self, PyObject* name, const char* buffer,
2043
2186
}
2044
2187
case 9 :
2045
2188
{
2046
- PyObject * naive ;
2047
- PyObject * replace ;
2048
- PyObject * args ;
2049
- PyObject * kwargs ;
2050
- PyObject * astimezone ;
2051
2189
int64_t millis ;
2052
2190
if (max < 8 ) {
2053
2191
goto invalid ;
@@ -2056,120 +2194,7 @@ static PyObject* get_value(PyObject* self, PyObject* name, const char* buffer,
2056
2194
millis = (int64_t )BSON_UINT64_FROM_LE (millis );
2057
2195
* position += 8 ;
2058
2196
2059
- if (options -> datetime_conversion == DATETIME_MS ){
2060
- value = datetime_ms_from_millis (self , millis );
2061
- break ;
2062
- }
2063
-
2064
- int dt_clamp = options -> datetime_conversion == DATETIME_CLAMP ;
2065
- int dt_auto = options -> datetime_conversion == DATETIME_AUTO ;
2066
-
2067
-
2068
- if (dt_clamp || dt_auto ){
2069
- PyObject * min_millis_fn_res ;
2070
- PyObject * max_millis_fn_res ;
2071
- int64_t min_millis ;
2072
- int64_t max_millis ;
2073
-
2074
- if (options -> tz_aware ){
2075
- PyObject * tzinfo = options -> tzinfo ;
2076
- if (tzinfo == Py_None ) {
2077
- // Default to UTC.
2078
- tzinfo = state -> UTC ;
2079
- }
2080
- min_millis_fn_res = PyObject_CallFunctionObjArgs (state -> _min_datetime_ms , tzinfo , NULL );
2081
- max_millis_fn_res = PyObject_CallFunctionObjArgs (state -> _max_datetime_ms , tzinfo , NULL );
2082
- } else {
2083
- min_millis_fn_res = PyObject_CallObject (state -> _min_datetime_ms , NULL );
2084
- max_millis_fn_res = PyObject_CallObject (state -> _max_datetime_ms , NULL );
2085
- }
2086
-
2087
- if (!min_millis_fn_res || !max_millis_fn_res ){
2088
- Py_XDECREF (min_millis_fn_res );
2089
- Py_XDECREF (max_millis_fn_res );
2090
- goto invalid ;
2091
- }
2092
-
2093
- min_millis = PyLong_AsLongLong (min_millis_fn_res );
2094
- max_millis = PyLong_AsLongLong (max_millis_fn_res );
2095
-
2096
- if ((min_millis == -1 || max_millis == -1 ) && PyErr_Occurred ())
2097
- {
2098
- // min/max_millis check
2099
- goto invalid ;
2100
- }
2101
-
2102
- if (dt_clamp ) {
2103
- if (millis < min_millis ) {
2104
- millis = min_millis ;
2105
- } else if (millis > max_millis ) {
2106
- millis = max_millis ;
2107
- }
2108
- // Continues from here to return a datetime.
2109
- } else { // dt_auto
2110
- if (millis < min_millis || millis > max_millis ){
2111
- value = datetime_ms_from_millis (self , millis );
2112
- break ; // Out-of-range so done.
2113
- }
2114
- }
2115
- }
2116
-
2117
- naive = datetime_from_millis (millis );
2118
- if (!options -> tz_aware ) { /* In the naive case, we're done here. */
2119
- value = naive ;
2120
- break ;
2121
- }
2122
-
2123
- if (!naive ) {
2124
- goto invalid ;
2125
- }
2126
- replace = PyObject_GetAttr (naive , state -> _replace_str );
2127
- Py_DECREF (naive );
2128
- if (!replace ) {
2129
- goto invalid ;
2130
- }
2131
- args = PyTuple_New (0 );
2132
- if (!args ) {
2133
- Py_DECREF (replace );
2134
- goto invalid ;
2135
- }
2136
- kwargs = PyDict_New ();
2137
- if (!kwargs ) {
2138
- Py_DECREF (replace );
2139
- Py_DECREF (args );
2140
- goto invalid ;
2141
- }
2142
- if (PyDict_SetItem (kwargs , state -> _tzinfo_str , state -> UTC ) == -1 ) {
2143
- Py_DECREF (replace );
2144
- Py_DECREF (args );
2145
- Py_DECREF (kwargs );
2146
- goto invalid ;
2147
- }
2148
- value = PyObject_Call (replace , args , kwargs );
2149
- if (!value ) {
2150
- Py_DECREF (replace );
2151
- Py_DECREF (args );
2152
- Py_DECREF (kwargs );
2153
- goto invalid ;
2154
- }
2155
-
2156
- /* convert to local time */
2157
- if (options -> tzinfo != Py_None ) {
2158
- astimezone = PyObject_GetAttr (value , state -> _astimezone_str );
2159
- Py_DECREF (value );
2160
- if (!astimezone ) {
2161
- Py_DECREF (replace );
2162
- Py_DECREF (args );
2163
- Py_DECREF (kwargs );
2164
- goto invalid ;
2165
- }
2166
- value = PyObject_CallFunctionObjArgs (astimezone , options -> tzinfo , NULL );
2167
- Py_DECREF (astimezone );
2168
- }
2169
-
2170
- Py_DECREF (replace );
2171
- Py_DECREF (args );
2172
- Py_DECREF (kwargs );
2197
+ value = decode_datetime (self , millis , options );
2173
2198
break ;
2174
2199
}
2175
2200
case 11 :
@@ -3053,6 +3078,10 @@ static int _cbson_traverse(PyObject *m, visitproc visit, void *arg) {
3053
3078
Py_VISIT (state -> _from_uuid_str );
3054
3079
Py_VISIT (state -> _as_uuid_str );
3055
3080
Py_VISIT (state -> _from_bid_str );
3081
+ Py_VISIT (state -> min_datetime );
3082
+ Py_VISIT (state -> max_datetime );
3083
+ Py_VISIT (state -> replace_args );
3084
+ Py_VISIT (state -> replace_kwargs );
3056
3085
return 0 ;
3057
3086
}
3058
3087
@@ -3097,6 +3126,10 @@ static int _cbson_clear(PyObject *m) {
3097
3126
Py_CLEAR (state -> _from_uuid_str );
3098
3127
Py_CLEAR (state -> _as_uuid_str );
3099
3128
Py_CLEAR (state -> _from_bid_str );
3129
+ Py_CLEAR (state -> min_datetime );
3130
+ Py_CLEAR (state -> max_datetime );
3131
+ Py_CLEAR (state -> replace_args );
3132
+ Py_CLEAR (state -> replace_kwargs );
3100
3133
return 0 ;
3101
3134
}
3102
3135
0 commit comments