@@ -52,6 +52,9 @@ struct module_state {
52
52
PyObject * BSONInt64 ;
53
53
PyObject * Decimal128 ;
54
54
PyObject * Mapping ;
55
+ PyObject * DatetimeMS ;
56
+ PyObject * _min_datetime_ms ;
57
+ PyObject * _max_datetime_ms ;
55
58
};
56
59
57
60
#define GETSTATE (m ) ((struct module_state*)PyModule_GetState(m))
@@ -72,6 +75,12 @@ struct module_state {
72
75
/* The smallest possible BSON document, i.e. "{}" */
73
76
#define BSON_MIN_SIZE 5
74
77
78
+ /* Datetime codec options */
79
+ #define DATETIME 1
80
+ #define DATETIME_CLAMP 2
81
+ #define DATETIME_MS 3
82
+ #define DATETIME_AUTO 4
83
+
75
84
/* Get an error class from the bson.errors module.
76
85
*
77
86
* Returns a new ref */
@@ -179,6 +188,45 @@ static long long millis_from_datetime(PyObject* datetime) {
179
188
return millis ;
180
189
}
181
190
191
+ /* Extended-range datetime, returns a DatetimeMS object with millis */
192
+ static PyObject * datetime_ms_from_millis (PyObject * self , long long millis ){
193
+ // Allocate a new DatetimeMS object.
194
+ struct module_state * state = GETSTATE (self );
195
+
196
+ PyObject * dt ;
197
+ PyObject * ll_millis ;
198
+
199
+ if (!(ll_millis = PyLong_FromLongLong (millis ))){
200
+ return NULL ;
201
+ }
202
+ dt = PyObject_CallFunctionObjArgs (state -> DatetimeMS , ll_millis , NULL );
203
+ Py_DECREF (ll_millis );
204
+ return dt ;
205
+ }
206
+
207
+ /* Extended-range datetime, takes a DatetimeMS object and extracts the long long value. */
208
+ static int millis_from_datetime_ms (PyObject * dt , long long * out ){
209
+ PyObject * ll_millis ;
210
+ long long millis ;
211
+
212
+ if (!(ll_millis = PyNumber_Long (dt ))){
213
+ if (PyErr_Occurred ()) { // TypeError
214
+ return 0 ;
215
+ }
216
+ }
217
+
218
+ if ((millis = PyLong_AsLongLong (ll_millis )) == -1 ){
219
+ if (PyErr_Occurred ()) { /* Overflow */
220
+ PyErr_SetString (PyExc_OverflowError ,
221
+ "MongoDB datetimes can only handle up to 8-byte ints" );
222
+ return 0 ;
223
+ }
224
+ }
225
+ Py_DECREF (ll_millis );
226
+ * out = millis ;
227
+ return 1 ;
228
+ }
229
+
182
230
/* Just make this compatible w/ the old API. */
183
231
int buffer_write_bytes (buffer_t buffer , const char * data , int size ) {
184
232
if (pymongo_buffer_write (buffer , data , size )) {
@@ -342,7 +390,10 @@ static int _load_python_objects(PyObject* module) {
342
390
_load_object (& state -> BSONInt64 , "bson.int64" , "Int64" ) ||
343
391
_load_object (& state -> Decimal128 , "bson.decimal128" , "Decimal128" ) ||
344
392
_load_object (& state -> UUID , "uuid" , "UUID" ) ||
345
- _load_object (& state -> Mapping , "collections.abc" , "Mapping" )) {
393
+ _load_object (& state -> Mapping , "collections.abc" , "Mapping" ) ||
394
+ _load_object (& state -> DatetimeMS , "bson.datetime_ms" , "DatetimeMS" ) ||
395
+ _load_object (& state -> _min_datetime_ms , "bson.datetime_ms" , "_min_datetime_ms" ) ||
396
+ _load_object (& state -> _max_datetime_ms , "bson.datetime_ms" , "_max_datetime_ms" )) {
346
397
return 1 ;
347
398
}
348
399
/* Reload our REType hack too. */
@@ -466,13 +517,14 @@ int convert_codec_options(PyObject* options_obj, void* p) {
466
517
467
518
options -> unicode_decode_error_handler = NULL ;
468
519
469
- if (!PyArg_ParseTuple (options_obj , "ObbzOO " ,
520
+ if (!PyArg_ParseTuple (options_obj , "ObbzOOb " ,
470
521
& options -> document_class ,
471
522
& options -> tz_aware ,
472
523
& options -> uuid_rep ,
473
524
& options -> unicode_decode_error_handler ,
474
525
& options -> tzinfo ,
475
- & type_registry_obj ))
526
+ & type_registry_obj ,
527
+ & options -> datetime_conversion ))
476
528
return 0 ;
477
529
478
530
type_marker = _type_marker (options -> document_class );
@@ -1049,6 +1101,13 @@ static int _write_element_to_buffer(PyObject* self, buffer_t buffer,
1049
1101
}
1050
1102
* (pymongo_buffer_get_buffer (buffer ) + type_byte ) = 0x09 ;
1051
1103
return buffer_write_int64 (buffer , (int64_t )millis );
1104
+ } else if (PyObject_TypeCheck (value , (PyTypeObject * ) state -> DatetimeMS )) {
1105
+ long long millis ;
1106
+ if (!millis_from_datetime_ms (value , & millis )) {
1107
+ return 0 ;
1108
+ }
1109
+ * (pymongo_buffer_get_buffer (buffer ) + type_byte ) = 0x09 ;
1110
+ return buffer_write_int64 (buffer , (int64_t )millis );
1052
1111
} else if (PyObject_TypeCheck (value , state -> REType )) {
1053
1112
return _write_regex_to_buffer (buffer , type_byte , value );
1054
1113
}
@@ -1854,8 +1913,79 @@ static PyObject* get_value(PyObject* self, PyObject* name, const char* buffer,
1854
1913
}
1855
1914
memcpy (& millis , buffer + * position , 8 );
1856
1915
millis = (int64_t )BSON_UINT64_FROM_LE (millis );
1857
- naive = datetime_from_millis (millis );
1858
1916
* position += 8 ;
1917
+
1918
+ if (options -> datetime_conversion == DATETIME_MS ){
1919
+ value = datetime_ms_from_millis (self , millis );
1920
+ break ;
1921
+ }
1922
+
1923
+ int dt_clamp = options -> datetime_conversion == DATETIME_CLAMP ;
1924
+ int dt_auto = options -> datetime_conversion == DATETIME_AUTO ;
1925
+
1926
+
1927
+ if (dt_clamp || dt_auto ){
1928
+ PyObject * min_millis_fn = _get_object (state -> _min_datetime_ms , "bson.datetime_ms" , "_min_datetime_ms" );
1929
+ PyObject * max_millis_fn = _get_object (state -> _max_datetime_ms , "bson.datetime_ms" , "_max_datetime_ms" );
1930
+ PyObject * min_millis_fn_res ;
1931
+ PyObject * max_millis_fn_res ;
1932
+ int64_t min_millis ;
1933
+ int64_t max_millis ;
1934
+
1935
+ if (min_millis_fn == NULL || max_millis_fn == NULL ) {
1936
+ Py_XDECREF (min_millis_fn );
1937
+ Py_XDECREF (max_millis_fn );
1938
+ goto invalid ;
1939
+ }
1940
+
1941
+ if (options -> tz_aware ){
1942
+ PyObject * tzinfo = options -> tzinfo ;
1943
+ if (tzinfo == Py_None ) {
1944
+ // Default to UTC.
1945
+ utc_type = _get_object (state -> UTC , "bson.tz_util" , "utc" );
1946
+ tzinfo = utc_type ;
1947
+ }
1948
+ min_millis_fn_res = PyObject_CallFunctionObjArgs (min_millis_fn , tzinfo , NULL );
1949
+ max_millis_fn_res = PyObject_CallFunctionObjArgs (max_millis_fn , tzinfo , NULL );
1950
+ } else {
1951
+ min_millis_fn_res = PyObject_CallObject (min_millis_fn , NULL );
1952
+ max_millis_fn_res = PyObject_CallObject (max_millis_fn , NULL );
1953
+ }
1954
+
1955
+ Py_DECREF (min_millis_fn );
1956
+ Py_DECREF (max_millis_fn );
1957
+
1958
+ if (!min_millis_fn_res || !max_millis_fn_res ){
1959
+ Py_XDECREF (min_millis_fn_res );
1960
+ Py_XDECREF (max_millis_fn_res );
1961
+ goto invalid ;
1962
+ }
1963
+
1964
+ min_millis = PyLong_AsLongLong (min_millis_fn_res );
1965
+ max_millis = PyLong_AsLongLong (max_millis_fn_res );
1966
+
1967
+ if ((min_millis == -1 || max_millis == -1 ) && PyErr_Occurred ())
1968
+ {
1969
+ // min/max_millis check
1970
+ goto invalid ;
1971
+ }
1972
+
1973
+ if (dt_clamp ) {
1974
+ if (millis < min_millis ) {
1975
+ millis = min_millis ;
1976
+ } else if (millis > max_millis ) {
1977
+ millis = max_millis ;
1978
+ }
1979
+ // Continues from here to return a datetime.
1980
+ } else if (dt_auto ) {
1981
+ if (millis < min_millis || millis > max_millis ){
1982
+ value = datetime_ms_from_millis (self , millis );
1983
+ break ; // Out-of-range so done.
1984
+ }
1985
+ }
1986
+ }
1987
+
1988
+ naive = datetime_from_millis (millis );
1859
1989
if (!options -> tz_aware ) { /* In the naive case, we're done here. */
1860
1990
value = naive ;
1861
1991
break ;
0 commit comments