|
1 | | -# Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. |
| 1 | +# Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. |
2 | 2 | # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
3 | 3 | # |
4 | 4 | # The Universal Permissive License (UPL), Version 1.0 |
@@ -1248,6 +1248,139 @@ class A(datetime.timezone): |
1248 | 1248 |
|
1249 | 1249 |
|
1250 | 1250 | class TimeDeltaTest(unittest.TestCase): |
| 1251 | + |
| 1252 | + def test_construction_types_field_overflow_and_rounding(self): |
| 1253 | + td = datetime.timedelta |
| 1254 | + |
| 1255 | + # Accepts bool, int, float, subclasses and mixes |
| 1256 | + class IntSubclass(int): pass |
| 1257 | + class FloatSubclass(float): pass |
| 1258 | + |
| 1259 | + self.assertEqual(td(days=True), td(days=1)) |
| 1260 | + self.assertEqual(td(days=2.0), td(days=2)) |
| 1261 | + self.assertEqual(td(days=IntSubclass(3)), td(days=3)) |
| 1262 | + self.assertEqual(td(days=FloatSubclass(4.0)), td(days=4)) |
| 1263 | + self.assertEqual(td(seconds=1.7), td(seconds=1, microseconds=700000)) |
| 1264 | + self.assertEqual(td(microseconds=5.2), td(microseconds=5)) |
| 1265 | + self.assertEqual(td(microseconds=1.8), td(microseconds=2)) |
| 1266 | + |
| 1267 | + # Overflow and normalization between fields |
| 1268 | + self.assertEqual(td(seconds=60), td(minutes=1)) |
| 1269 | + self.assertEqual(td(minutes=60), td(hours=1)) |
| 1270 | + self.assertEqual(td(hours=24), td(days=1)) |
| 1271 | + self.assertEqual(td(milliseconds=1500), td(seconds=1, milliseconds=500)) |
| 1272 | + self.assertEqual(td(microseconds=1000001), td(seconds=1, microseconds=1)) |
| 1273 | + |
| 1274 | + # Mix float/int/rounding |
| 1275 | + self.assertEqual(td(seconds=1.9, microseconds=1.7), td(seconds=1, microseconds=900002)) |
| 1276 | + # Large overflow stacks |
| 1277 | + self.assertEqual(td(seconds=3600*25), td(days=1, hours=1)) |
| 1278 | + self.assertEqual(td(milliseconds=24*60*60*1000), td(days=1)) |
| 1279 | + |
| 1280 | + # Rounding of fractional microseconds (half-to-even) |
| 1281 | + # .5 rounds to even |
| 1282 | + self.assertEqual(td(microseconds=0.5), td(microseconds=0)) |
| 1283 | + self.assertEqual(td(microseconds=1.5), td(microseconds=2)) |
| 1284 | + self.assertEqual(td(microseconds=2.5), td(microseconds=2)) |
| 1285 | + self.assertEqual(td(microseconds=3.5), td(microseconds=4)) |
| 1286 | + self.assertEqual(td(milliseconds=0.0005), td()) |
| 1287 | + self.assertEqual(td(milliseconds=0.0015), td(microseconds=2)) |
| 1288 | + # Negative |
| 1289 | + self.assertEqual(td(microseconds=-0.5), td(microseconds=0)) |
| 1290 | + self.assertEqual(td(microseconds=-1.5), td(microseconds=-2)) |
| 1291 | + self.assertEqual(td(microseconds=-2.5), td(microseconds=-2)) |
| 1292 | + self.assertEqual(td(microseconds=-3.5), td(microseconds=-4)) |
| 1293 | + |
| 1294 | + # Rejects non-numeric types (already checked elsewhere, not repeated here) |
| 1295 | + |
| 1296 | + def test_mul_div_divmod_overflow_and_rounding(self): |
| 1297 | + td = datetime.timedelta |
| 1298 | + |
| 1299 | + # Multiplication overflow (try to exceed microsecond 32b int, expect OverflowError) |
| 1300 | + big = td(days=2**26) # very big, but not yet overflow |
| 1301 | + with self.assertRaises(OverflowError): |
| 1302 | + _ = big * (2**14) |
| 1303 | + |
| 1304 | + with self.assertRaises(OverflowError): |
| 1305 | + _ = (2**14) * big |
| 1306 | + |
| 1307 | + # Negative overflow |
| 1308 | + with self.assertRaises(OverflowError): |
| 1309 | + _ = -big * (2**14) |
| 1310 | + |
| 1311 | + # Division overflow (should raise on absurdly small divisor) |
| 1312 | + huge = td(days=999999999) |
| 1313 | + with self.assertRaises(OverflowError): |
| 1314 | + _ = huge / 1e-20 |
| 1315 | + |
| 1316 | + # Divmod with float divisor is not allowed (CPython raises TypeError) |
| 1317 | + with self.assertRaises(TypeError): |
| 1318 | + divmod(huge, 1e-20) |
| 1319 | + |
| 1320 | + # Multiplication without overflow |
| 1321 | + t1 = td(days=1, microseconds=1) |
| 1322 | + self.assertEqual(t1 * 2, td(days=2, microseconds=2)) |
| 1323 | + self.assertEqual(2 * t1, td(days=2, microseconds=2)) |
| 1324 | + self.assertEqual(t1 * 0, td()) |
| 1325 | + |
| 1326 | + # Division: round-half-to-even per CPython |
| 1327 | + # Case: exactly half |
| 1328 | + half_even = td(microseconds=3) |
| 1329 | + self.assertEqual(half_even / 2, td(microseconds=2)) # 1.5 rounds to 2 (even) |
| 1330 | + self.assertEqual((-half_even) / 2, td(microseconds=-2)) # -1.5 rounds to -2 |
| 1331 | + |
| 1332 | + # Closest not half |
| 1333 | + self.assertEqual(td(microseconds=5) / 2, td(microseconds=2)) # 2.5 rounds to 2 (even) |
| 1334 | + self.assertEqual(td(microseconds=7) / 2, td(microseconds=4)) # 3.5 rounds to 4 (even) |
| 1335 | + |
| 1336 | + # .__truediv__ returns float if divisor is a timedelta (duration ratio) |
| 1337 | + self.assertEqual(td(days=3) / td(days=2), 1.5) |
| 1338 | + |
| 1339 | + # Divmod by int is not supported in CPython (raises TypeError) |
| 1340 | + with self.assertRaises(TypeError): |
| 1341 | + divmod(td(seconds=5), 2) |
| 1342 | + with self.assertRaises(TypeError): |
| 1343 | + divmod(td(microseconds=7), 2) |
| 1344 | + |
| 1345 | + # Division by zero raises |
| 1346 | + with self.assertRaises(ZeroDivisionError): |
| 1347 | + _ = t1 / 0 |
| 1348 | + with self.assertRaises(ZeroDivisionError): |
| 1349 | + _ = t1 // 0 |
| 1350 | + # divmod by zero (int) is not supported at all |
| 1351 | + with self.assertRaises(TypeError): |
| 1352 | + divmod(t1, 0) |
| 1353 | + # divmod by zero timedelta returns ZeroDivisionError |
| 1354 | + with self.assertRaises(ZeroDivisionError): |
| 1355 | + divmod(t1, td()) |
| 1356 | + |
| 1357 | + # Divmod with negative int is not supported (raises TypeError) |
| 1358 | + with self.assertRaises(TypeError): |
| 1359 | + divmod(td(seconds=3), -2) |
| 1360 | + |
| 1361 | + # Division by timedelta returns float, test for correct rounding |
| 1362 | + self.assertEqual(td(seconds=5) / td(seconds=2), 2.5) |
| 1363 | + self.assertEqual(td(seconds=3) / td(seconds=2), 1.5) |
| 1364 | + self.assertEqual(td(seconds=3) // td(seconds=2), 1.0) |
| 1365 | + with self.assertRaises(ZeroDivisionError): |
| 1366 | + _ = td(days=1) / td() |
| 1367 | + |
| 1368 | + # Edge: Divmod with timedelta as divisor, remainder sign |
| 1369 | + d, r = divmod(td(seconds=5), td(seconds=2)) |
| 1370 | + self.assertEqual(d, 2.0) |
| 1371 | + self.assertEqual(r, td(seconds=1)) |
| 1372 | + |
| 1373 | + d, r = divmod(td(seconds=5), td(seconds=-2)) |
| 1374 | + self.assertEqual(d, -3.0) |
| 1375 | + self.assertEqual(r, td(seconds=-1)) |
| 1376 | + |
| 1377 | + # Additional explicit float rounding half to even |
| 1378 | + self.assertEqual((td(microseconds=2) / 1.33333333333).microseconds, 2) |
| 1379 | + self.assertEqual((td(microseconds=3) / 1.5).microseconds, 2) |
| 1380 | + # result field normalization: negative microseconds yields +999998 with negative total_seconds |
| 1381 | + self.assertEqual((td(microseconds=-3) / 1.5), td(microseconds=-2)) |
| 1382 | + self.assertAlmostEqual((td(microseconds=-3) / 1.5).total_seconds(), -2e-6) |
| 1383 | + |
1251 | 1384 | def test_new(self): |
1252 | 1385 | # parameters type validation |
1253 | 1386 |
|
@@ -1295,6 +1428,9 @@ def test_new(self): |
1295 | 1428 | with self.assertRaisesRegex(ValueError, "cannot convert float NaN to integer"): |
1296 | 1429 | datetime.timedelta(weeks = float("nan")) |
1297 | 1430 |
|
| 1431 | + def test_total_seconds(self): |
| 1432 | + self.assertAlmostEqual(datetime.timedelta(days=106751992).total_seconds(), 9223372108800.0) |
| 1433 | + |
1298 | 1434 | class DateTimeTest(unittest.TestCase): |
1299 | 1435 |
|
1300 | 1436 | def test_strptime(self): |
|
0 commit comments