@@ -1252,54 +1252,116 @@ def test_class_conversions(self):
1252
1252
1253
1253
def test_clamping (self ):
1254
1254
# Test clamping from below and above.
1255
- opts1 = CodecOptions (
1255
+ opts = CodecOptions (
1256
1256
datetime_conversion = DatetimeConversion .DATETIME_CLAMP ,
1257
1257
tz_aware = True ,
1258
1258
tzinfo = datetime .timezone .utc ,
1259
1259
)
1260
1260
below = encode ({"x" : DatetimeMS (_datetime_to_millis (datetime .datetime .min ) - 1 )})
1261
- dec_below = decode (below , opts1 )
1261
+ dec_below = decode (below , opts )
1262
1262
self .assertEqual (
1263
1263
dec_below ["x" ], datetime .datetime .min .replace (tzinfo = datetime .timezone .utc )
1264
1264
)
1265
1265
1266
1266
above = encode ({"x" : DatetimeMS (_datetime_to_millis (datetime .datetime .max ) + 1 )})
1267
- dec_above = decode (above , opts1 )
1267
+ dec_above = decode (above , opts )
1268
1268
self .assertEqual (
1269
1269
dec_above ["x" ],
1270
1270
datetime .datetime .max .replace (tzinfo = datetime .timezone .utc , microsecond = 999000 ),
1271
1271
)
1272
1272
1273
- def test_tz_clamping (self ):
1273
+ def test_tz_clamping_local (self ):
1274
1274
# Naive clamping to local tz.
1275
- opts1 = CodecOptions (datetime_conversion = DatetimeConversion .DATETIME_CLAMP , tz_aware = False )
1275
+ opts = CodecOptions (datetime_conversion = DatetimeConversion .DATETIME_CLAMP , tz_aware = False )
1276
1276
below = encode ({"x" : DatetimeMS (_datetime_to_millis (datetime .datetime .min ) - 24 * 60 * 60 )})
1277
1277
1278
- dec_below = decode (below , opts1 )
1278
+ dec_below = decode (below , opts )
1279
1279
self .assertEqual (dec_below ["x" ], datetime .datetime .min )
1280
1280
1281
1281
above = encode ({"x" : DatetimeMS (_datetime_to_millis (datetime .datetime .max ) + 24 * 60 * 60 )})
1282
- dec_above = decode (above , opts1 )
1282
+ dec_above = decode (above , opts )
1283
1283
self .assertEqual (
1284
1284
dec_above ["x" ],
1285
1285
datetime .datetime .max .replace (microsecond = 999000 ),
1286
1286
)
1287
1287
1288
- # Aware clamping.
1289
- opts2 = CodecOptions (datetime_conversion = DatetimeConversion .DATETIME_CLAMP , tz_aware = True )
1288
+ def test_tz_clamping_utc (self ):
1289
+ # Aware clamping default utc.
1290
+ opts = CodecOptions (datetime_conversion = DatetimeConversion .DATETIME_CLAMP , tz_aware = True )
1290
1291
below = encode ({"x" : DatetimeMS (_datetime_to_millis (datetime .datetime .min ) - 24 * 60 * 60 )})
1291
- dec_below = decode (below , opts2 )
1292
+ dec_below = decode (below , opts )
1292
1293
self .assertEqual (
1293
1294
dec_below ["x" ], datetime .datetime .min .replace (tzinfo = datetime .timezone .utc )
1294
1295
)
1295
1296
1296
1297
above = encode ({"x" : DatetimeMS (_datetime_to_millis (datetime .datetime .max ) + 24 * 60 * 60 )})
1297
- dec_above = decode (above , opts2 )
1298
+ dec_above = decode (above , opts )
1298
1299
self .assertEqual (
1299
1300
dec_above ["x" ],
1300
1301
datetime .datetime .max .replace (tzinfo = datetime .timezone .utc , microsecond = 999000 ),
1301
1302
)
1302
1303
1304
+ def test_tz_clamping_non_utc (self ):
1305
+ for tz in [FixedOffset (60 , "+1H" ), FixedOffset (- 60 , "-1H" )]:
1306
+ opts = CodecOptions (
1307
+ datetime_conversion = DatetimeConversion .DATETIME_CLAMP , tz_aware = True , tzinfo = tz
1308
+ )
1309
+ # Min/max values in this timezone which can be represented in both BSON and datetime UTC.
1310
+ try :
1311
+ min_tz = datetime .datetime .min .replace (tzinfo = utc ).astimezone (tz )
1312
+ except OverflowError :
1313
+ min_tz = datetime .datetime .min .replace (tzinfo = tz )
1314
+ try :
1315
+ max_tz = datetime .datetime .max .replace (tzinfo = utc , microsecond = 999000 ).astimezone (
1316
+ tz
1317
+ )
1318
+ except OverflowError :
1319
+ max_tz = datetime .datetime .max .replace (tzinfo = tz , microsecond = 999000 )
1320
+
1321
+ for in_range in [
1322
+ min_tz ,
1323
+ min_tz + datetime .timedelta (milliseconds = 1 ),
1324
+ max_tz - datetime .timedelta (milliseconds = 1 ),
1325
+ max_tz ,
1326
+ ]:
1327
+ doc = decode (encode ({"x" : in_range }), opts )
1328
+ self .assertEqual (doc ["x" ], in_range )
1329
+
1330
+ for too_low in [
1331
+ DatetimeMS (_datetime_to_millis (min_tz ) - 1 ),
1332
+ DatetimeMS (_datetime_to_millis (min_tz ) - 60 * 60 * 1000 ),
1333
+ DatetimeMS (_datetime_to_millis (min_tz ) - 1 - 60 * 60 * 1000 ),
1334
+ DatetimeMS (_datetime_to_millis (datetime .datetime .min ) - 1 ),
1335
+ DatetimeMS (_datetime_to_millis (datetime .datetime .min ) - 60 * 60 * 1000 ),
1336
+ DatetimeMS (_datetime_to_millis (datetime .datetime .min ) - 1 - 60 * 60 * 1000 ),
1337
+ ]:
1338
+ doc = decode (encode ({"x" : too_low }), opts )
1339
+ self .assertEqual (doc ["x" ], min_tz )
1340
+
1341
+ for too_high in [
1342
+ DatetimeMS (_datetime_to_millis (max_tz ) + 1 ),
1343
+ DatetimeMS (_datetime_to_millis (max_tz ) + 60 * 60 * 1000 ),
1344
+ DatetimeMS (_datetime_to_millis (max_tz ) + 1 + 60 * 60 * 1000 ),
1345
+ DatetimeMS (_datetime_to_millis (datetime .datetime .max ) + 1 ),
1346
+ DatetimeMS (_datetime_to_millis (datetime .datetime .max ) + 60 * 60 * 1000 ),
1347
+ DatetimeMS (_datetime_to_millis (datetime .datetime .max ) + 1 + 60 * 60 * 1000 ),
1348
+ ]:
1349
+ doc = decode (encode ({"x" : too_high }), opts )
1350
+ self .assertEqual (doc ["x" ], max_tz )
1351
+
1352
+ def test_tz_clamping_non_utc_simple (self ):
1353
+ dtm = datetime .datetime (2024 , 8 , 23 )
1354
+ encoded = encode ({"d" : dtm })
1355
+ self .assertEqual (decode (encoded )["d" ], dtm )
1356
+ for conversion in [
1357
+ DatetimeConversion .DATETIME ,
1358
+ DatetimeConversion .DATETIME_CLAMP ,
1359
+ DatetimeConversion .DATETIME_AUTO ,
1360
+ ]:
1361
+ for tz in [FixedOffset (60 , "+1H" ), FixedOffset (- 60 , "-1H" )]:
1362
+ opts = CodecOptions (datetime_conversion = conversion , tz_aware = True , tzinfo = tz )
1363
+ self .assertEqual (decode (encoded , opts )["d" ], dtm .replace (tzinfo = utc ).astimezone (tz ))
1364
+
1303
1365
def test_datetime_auto (self ):
1304
1366
# Naive auto, in range.
1305
1367
opts1 = CodecOptions (datetime_conversion = DatetimeConversion .DATETIME_AUTO )
0 commit comments