@@ -1118,6 +1118,223 @@ def test_assertion_rewriting_works_with_approx_on_lhs(
11181118 ]
11191119
11201120
1121+ class TestApproxDatetime :
1122+ """Tests for datetime/timedelta support in approx (issue #8395)."""
1123+
1124+ def test_datetime_exactly_equal (self ):
1125+ from datetime import datetime
1126+ from datetime import timedelta
1127+
1128+ dt = datetime (2024 , 1 , 1 , 12 , 0 , 0 )
1129+ assert dt == approx (dt , abs = timedelta (seconds = 1 ))
1130+
1131+ def test_datetime_within_tolerance (self ):
1132+ from datetime import datetime
1133+ from datetime import timedelta
1134+
1135+ dt1 = datetime (2024 , 1 , 1 , 12 , 0 , 0 )
1136+ dt2 = datetime (2024 , 1 , 1 , 12 , 0 , 0 , 500000 ) # +0.5s
1137+ assert dt1 == approx (dt2 , abs = timedelta (seconds = 1 ))
1138+
1139+ def test_datetime_outside_tolerance (self ):
1140+ from datetime import datetime
1141+ from datetime import timedelta
1142+
1143+ dt1 = datetime (2024 , 1 , 1 , 12 , 0 , 0 )
1144+ dt2 = datetime (2024 , 1 , 1 , 12 , 0 , 2 ) # +2s
1145+ assert dt1 != approx (dt2 , abs = timedelta (seconds = 1 ))
1146+
1147+ def test_datetime_negative_difference (self ):
1148+ from datetime import datetime
1149+ from datetime import timedelta
1150+
1151+ dt1 = datetime (2024 , 1 , 1 , 12 , 0 , 1 )
1152+ dt2 = datetime (2024 , 1 , 1 , 12 , 0 , 0 ) # dt2 < dt1
1153+ assert dt1 == approx (dt2 , abs = timedelta (seconds = 2 ))
1154+ assert dt1 != approx (dt2 , abs = timedelta (milliseconds = 500 ))
1155+
1156+ def test_timedelta_within_tolerance (self ):
1157+ from datetime import timedelta
1158+
1159+ td1 = timedelta (seconds = 100 )
1160+ td2 = timedelta (seconds = 100.5 )
1161+ assert td1 == approx (td2 , abs = timedelta (seconds = 1 ))
1162+
1163+ def test_timedelta_outside_tolerance (self ):
1164+ from datetime import timedelta
1165+
1166+ td1 = timedelta (seconds = 100 )
1167+ td2 = timedelta (seconds = 102 )
1168+ assert td1 != approx (td2 , abs = timedelta (seconds = 1 ))
1169+
1170+ def test_timedelta_rel_within_tolerance (self ):
1171+ from datetime import timedelta
1172+
1173+ td1 = timedelta (seconds = 100 )
1174+ td2 = timedelta (seconds = 100.5 )
1175+ assert td1 == approx (td2 , rel = timedelta (seconds = 1 ))
1176+
1177+ def test_timedelta_rel_outside_tolerance (self ):
1178+ from datetime import timedelta
1179+
1180+ td1 = timedelta (seconds = 100 )
1181+ td2 = timedelta (seconds = 102 )
1182+ assert td1 != approx (td2 , rel = timedelta (seconds = 1 ))
1183+
1184+ def test_requires_tolerance (self ):
1185+ from datetime import datetime
1186+
1187+ with pytest .raises (TypeError , match = "requires an explicit tolerance" ):
1188+ approx (datetime (2024 , 1 , 1 ))
1189+
1190+ def test_datetime_rejects_rel (self ):
1191+ from datetime import datetime
1192+ from datetime import timedelta
1193+
1194+ with pytest .raises (TypeError , match = "does not support relative tolerance" ):
1195+ approx (datetime (2024 , 1 , 1 ), rel = 0.1 , abs = timedelta (seconds = 1 ))
1196+
1197+ with pytest .raises (TypeError , match = "does not support relative tolerance" ):
1198+ approx (datetime (2024 , 1 , 1 ), rel = timedelta (seconds = 1 ))
1199+
1200+ def test_abs_must_be_timedelta (self ):
1201+ from datetime import datetime
1202+
1203+ with pytest .raises (TypeError , match = "must be a timedelta" ):
1204+ approx (datetime (2024 , 1 , 1 ), abs = 1.0 )
1205+
1206+ def test_timedelta_rel_must_be_timedelta (self ):
1207+ from datetime import timedelta
1208+
1209+ with pytest .raises (TypeError , match = "must be a timedelta" ):
1210+ approx (timedelta (seconds = 1 ), rel = 0.1 )
1211+
1212+ def test_rejects_nan_ok (self ):
1213+ from datetime import datetime
1214+ from datetime import timedelta
1215+
1216+ with pytest .raises (TypeError , match = "does not support nan_ok" ):
1217+ approx (datetime (2024 , 1 , 1 ), abs = timedelta (seconds = 1 ), nan_ok = True )
1218+
1219+ def test_datetime_repr (self ):
1220+ from datetime import datetime
1221+ from datetime import timedelta
1222+
1223+ dt = datetime (2024 , 1 , 1 , 12 , 0 , 0 )
1224+ result = repr (approx (dt , abs = timedelta (seconds = 1 )))
1225+ assert "2024-01-01 12:00:00" in result
1226+ assert "0:00:01" in result
1227+
1228+ def test_timedelta_repr (self ):
1229+ from datetime import timedelta
1230+
1231+ td = timedelta (seconds = 100 )
1232+ result = repr (approx (td , abs = timedelta (seconds = 1 )))
1233+ assert "0:01:40" in result # 100 seconds
1234+ assert "0:00:01" in result # 1 second tolerance
1235+
1236+ def test_datetime_symmetry (self ):
1237+ """Approx comparison should work on both sides of ==."""
1238+ from datetime import datetime
1239+ from datetime import timedelta
1240+
1241+ dt1 = datetime (2024 , 1 , 1 , 12 , 0 , 0 )
1242+ dt2 = datetime (2024 , 1 , 1 , 12 , 0 , 0 , 500000 )
1243+ tol = timedelta (seconds = 1 )
1244+ assert dt1 == approx (dt2 , abs = tol )
1245+ assert approx (dt2 , abs = tol ) == dt1
1246+
1247+ def test_datetime_ne_operator (self ):
1248+ from datetime import datetime
1249+ from datetime import timedelta
1250+
1251+ dt1 = datetime (2024 , 1 , 1 , 12 , 0 , 0 )
1252+ dt2 = datetime (2024 , 1 , 1 , 12 , 0 , 5 )
1253+ tol = timedelta (seconds = 1 )
1254+ assert dt1 != approx (dt2 , abs = tol )
1255+ assert not (dt1 == approx (dt2 , abs = tol ))
1256+
1257+ def test_datetime_with_timezone (self ):
1258+ from datetime import datetime
1259+ from datetime import timedelta
1260+ from datetime import timezone
1261+
1262+ tz = timezone .utc
1263+ dt1 = datetime (2024 , 1 , 1 , 12 , 0 , 0 , tzinfo = tz )
1264+ dt2 = datetime (2024 , 1 , 1 , 12 , 0 , 0 , 500000 , tzinfo = tz )
1265+ assert dt1 == approx (dt2 , abs = timedelta (seconds = 1 ))
1266+
1267+ def test_datetime_error_message (self ):
1268+ from datetime import datetime
1269+ from datetime import timedelta
1270+
1271+ dt1 = datetime (2024 , 1 , 1 , 12 , 0 , 0 )
1272+ dt2 = datetime (2024 , 1 , 1 , 12 , 0 , 5 ) # 5 seconds off
1273+ with pytest .raises (AssertionError , match = "comparison failed" ):
1274+ assert dt1 == approx (dt2 , abs = timedelta (seconds = 1 ))
1275+
1276+ def test_timedelta_zero (self ):
1277+ from datetime import timedelta
1278+
1279+ td1 = timedelta (seconds = 0 )
1280+ td2 = timedelta (seconds = 0 )
1281+ assert td1 == approx (td2 , abs = timedelta (seconds = 1 ))
1282+
1283+ def test_datetime_boundary_exact (self ):
1284+ """Test that values exactly at the tolerance boundary are equal."""
1285+ from datetime import datetime
1286+ from datetime import timedelta
1287+
1288+ dt1 = datetime (2024 , 1 , 1 , 12 , 0 , 0 )
1289+ dt2 = datetime (2024 , 1 , 1 , 12 , 0 , 1 ) # exactly 1 second
1290+ assert dt1 == approx (dt2 , abs = timedelta (seconds = 1 ))
1291+
1292+ def test_datetime_microsecond_tolerance (self ):
1293+ from datetime import datetime
1294+ from datetime import timedelta
1295+
1296+ dt1 = datetime (2024 , 1 , 1 , 12 , 0 , 0 , 0 )
1297+ dt2 = datetime (2024 , 1 , 1 , 12 , 0 , 0 , 100 ) # +100 microseconds
1298+ assert dt1 == approx (dt2 , abs = timedelta (microseconds = 200 ))
1299+ assert dt1 != approx (dt2 , abs = timedelta (microseconds = 50 ))
1300+
1301+ def test_bool_context_raises (self ):
1302+ from datetime import datetime
1303+ from datetime import timedelta
1304+
1305+ with pytest .raises (AssertionError , match = "boolean context" ):
1306+ bool (approx (datetime (2024 , 1 , 1 ), abs = timedelta (seconds = 1 )))
1307+
1308+ def test_wrong_type_comparison (self ):
1309+ """Comparing a datetime approx with a non-datetime should return False."""
1310+ from datetime import datetime
1311+ from datetime import timedelta
1312+
1313+ assert 42 != approx (datetime (2024 , 1 , 1 ), abs = timedelta (seconds = 1 ))
1314+ assert "string" != approx (datetime (2024 , 1 , 1 ), abs = timedelta (seconds = 1 ))
1315+
1316+ def test_yield_comparisons (self ):
1317+ """Test that _yield_comparisons yields (actual, expected) pairs."""
1318+ from datetime import datetime
1319+ from datetime import timedelta
1320+
1321+ dt = datetime (2024 , 1 , 1 , 12 , 0 , 0 )
1322+ a = approx (dt , abs = timedelta (seconds = 1 ))
1323+ actual = datetime (2024 , 1 , 1 , 12 , 0 , 0 , 500000 )
1324+ pairs = list (a ._yield_comparisons (actual ))
1325+ assert pairs == [(actual , dt )]
1326+
1327+ def test_repr_compare_with_incompatible_type (self ):
1328+ """_repr_compare handles TypeError when actual is not a datetime."""
1329+ from datetime import datetime
1330+ from datetime import timedelta
1331+
1332+ a = approx (datetime (2024 , 1 , 1 ), abs = timedelta (seconds = 1 ))
1333+ result = a ._repr_compare ("not a datetime" )
1334+ assert "comparison failed" in result [0 ]
1335+ assert "N/A" in result [3 ]
1336+
1337+
11211338class MyVec3 : # incomplete
11221339 """sequence like"""
11231340
0 commit comments