|
51 | 51 | timezone_london = timezone("Europe/London")
|
52 | 52 | timezone_berlin = timezone("Europe/Berlin")
|
53 | 53 | timezone_utc = timezone("UTC")
|
| 54 | +timezone_utc_p2 = FixedOffset(120) |
54 | 55 |
|
55 | 56 |
|
56 | 57 | class DateTime(_DateTime):
|
@@ -274,6 +275,211 @@ def test_subtract_native_datetime_2(self, seconds_args):
|
274 | 275 | t = dt1 - dt2
|
275 | 276 | assert t == timedelta(days=65, hours=23, seconds=17.914390409)
|
276 | 277 |
|
| 278 | + @pytest.mark.parametrize( |
| 279 | + ("dt_early", "delta", "dt_late"), |
| 280 | + ( |
| 281 | + ( |
| 282 | + DateTime(2024, 3, 31, 0, 30, 0), |
| 283 | + Duration(nanoseconds=1), |
| 284 | + DateTime(2024, 3, 31, 0, 30, 0, 1), |
| 285 | + ), |
| 286 | + ( |
| 287 | + DateTime(2024, 3, 31, 0, 30, 0), |
| 288 | + Duration(hours=24), |
| 289 | + DateTime(2024, 4, 1, 0, 30, 0), |
| 290 | + ), |
| 291 | + ( |
| 292 | + DateTime(2024, 3, 31, 0, 30, 0), |
| 293 | + timedelta(microseconds=1), |
| 294 | + DateTime(2024, 3, 31, 0, 30, 0, 1000), |
| 295 | + ), |
| 296 | + ( |
| 297 | + DateTime(2024, 3, 31, 0, 30, 0), |
| 298 | + timedelta(hours=24), |
| 299 | + DateTime(2024, 4, 1, 0, 30, 0), |
| 300 | + ), |
| 301 | + ), |
| 302 | + ) |
| 303 | + @pytest.mark.parametrize( |
| 304 | + "tz", |
| 305 | + (None, timezone_utc, timezone_utc_p2, timezone_berlin), |
| 306 | + ) |
| 307 | + def test_add_duration(self, dt_early, delta, dt_late, tz): |
| 308 | + if tz is not None: |
| 309 | + dt_early = timezone_utc.localize(dt_early).astimezone(tz) |
| 310 | + dt_late = timezone_utc.localize(dt_late).astimezone(tz) |
| 311 | + assert dt_early + delta == dt_late |
| 312 | + |
| 313 | + @pytest.mark.parametrize( |
| 314 | + ("datetime_cls", "delta_cls"), |
| 315 | + ( |
| 316 | + (datetime, timedelta), # baseline (what Python's datetime does) |
| 317 | + (DateTime, Duration), |
| 318 | + (DateTime, timedelta), |
| 319 | + ), |
| 320 | + ) |
| 321 | + def test_transition_to_summertime(self, datetime_cls, delta_cls): |
| 322 | + dt = datetime_cls(2022, 3, 27, 1, 30) |
| 323 | + dt = timezone_berlin.localize(dt) |
| 324 | + assert dt.utcoffset() == timedelta(hours=1) |
| 325 | + assert isinstance(dt, datetime_cls) |
| 326 | + time = dt.time() |
| 327 | + assert (time.hour, time.minute) == (1, 30) |
| 328 | + |
| 329 | + dt += delta_cls(hours=1) |
| 330 | + |
| 331 | + # The native datetime object treats timedelta addition as wall time |
| 332 | + # addition. This is imo silly, but what Python decided to do. So want |
| 333 | + # our implementation to match that. See also: |
| 334 | + # https://stackoverflow.com/questions/76583100/is-pytz-deprecated-now-or-in-the-future-in-python |
| 335 | + assert dt.utcoffset() == timedelta(hours=1) |
| 336 | + assert isinstance(dt, datetime_cls) |
| 337 | + time = dt.time() |
| 338 | + assert (time.hour, time.minute) == (2, 30) |
| 339 | + |
| 340 | + @pytest.mark.parametrize( |
| 341 | + ("datetime_cls", "delta_cls"), |
| 342 | + ( |
| 343 | + (datetime, timedelta), # baseline (what Python's datetime does) |
| 344 | + (DateTime, Duration), |
| 345 | + (DateTime, timedelta), |
| 346 | + ), |
| 347 | + ) |
| 348 | + def test_transition_from_summertime(self, datetime_cls, delta_cls): |
| 349 | + dt = datetime_cls(2022, 10, 30, 2, 30) |
| 350 | + dt = timezone_berlin.localize(dt, is_dst=True) |
| 351 | + assert dt.utcoffset() == timedelta(hours=2) |
| 352 | + assert isinstance(dt, datetime_cls) |
| 353 | + time = dt.time() |
| 354 | + assert (time.hour, time.minute) == (2, 30) |
| 355 | + |
| 356 | + dt += delta_cls(hours=1) |
| 357 | + |
| 358 | + # The native datetime object treats timedelta addition as wall time |
| 359 | + # addition. This is imo silly, but what Python decided to do. So want |
| 360 | + # our implementation to match that. See also: |
| 361 | + # https://stackoverflow.com/questions/76583100/is-pytz-deprecated-now-or-in-the-future-in-python |
| 362 | + assert dt.utcoffset() == timedelta(hours=2) |
| 363 | + assert isinstance(dt, datetime_cls) |
| 364 | + time = dt.time() |
| 365 | + assert (time.hour, time.minute) == (3, 30) |
| 366 | + |
| 367 | + @pytest.mark.parametrize( |
| 368 | + ("dt1", "dt2"), |
| 369 | + ( |
| 370 | + ( |
| 371 | + DateTime(2018, 4, 27, 23, 0, 17, 914390409), |
| 372 | + DateTime(2018, 4, 27, 23, 0, 17, 914390409), |
| 373 | + ), |
| 374 | + ( |
| 375 | + utc.localize(DateTime(2018, 4, 27, 23, 0, 17, 914390409)), |
| 376 | + utc.localize(DateTime(2018, 4, 27, 23, 0, 17, 914390409)), |
| 377 | + ), |
| 378 | + ( |
| 379 | + utc.localize(DateTime(2018, 4, 27, 23, 0, 17, 914390409)), |
| 380 | + utc.localize( |
| 381 | + DateTime(2018, 4, 27, 23, 0, 17, 914390409) |
| 382 | + ).astimezone(timezone_berlin), |
| 383 | + ), |
| 384 | + ), |
| 385 | + ) |
| 386 | + @pytest.mark.parametrize("native", (True, False)) |
| 387 | + def test_eq( self, dt1, dt2, native): |
| 388 | + assert isinstance(dt1, DateTime) |
| 389 | + assert isinstance(dt2, DateTime) |
| 390 | + if native: |
| 391 | + dt1 = dt1.replace(nanosecond=dt1.nanosecond // 1000 * 1000) |
| 392 | + dt2 = dt2.to_native() |
| 393 | + assert dt1 == dt2 |
| 394 | + assert dt2 == dt1 |
| 395 | + # explicitly test that `not !=` is `==` (different code paths) |
| 396 | + assert not dt1 != dt2 |
| 397 | + assert not dt2 != dt1 |
| 398 | + |
| 399 | + @pytest.mark.parametrize( |
| 400 | + ("dt1", "dt2", "native"), |
| 401 | + ( |
| 402 | + # nanosecond difference |
| 403 | + ( |
| 404 | + DateTime(2018, 4, 27, 23, 0, 17, 914390408), |
| 405 | + DateTime(2018, 4, 27, 23, 0, 17, 914390409), |
| 406 | + False, |
| 407 | + ), |
| 408 | + *( |
| 409 | + ( |
| 410 | + dt1, |
| 411 | + DateTime(2018, 4, 27, 23, 0, 17, 914390409), |
| 412 | + native, |
| 413 | + ) |
| 414 | + for dt1 in ( |
| 415 | + DateTime(2018, 4, 27, 23, 0, 17, 914391409), |
| 416 | + DateTime(2018, 4, 27, 23, 0, 18, 914390409), |
| 417 | + DateTime(2018, 4, 27, 23, 1, 17, 914390409), |
| 418 | + DateTime(2018, 4, 27, 22, 0, 17, 914390409), |
| 419 | + DateTime(2018, 4, 26, 23, 0, 17, 914390409), |
| 420 | + DateTime(2018, 5, 27, 23, 0, 17, 914390409), |
| 421 | + DateTime(2019, 4, 27, 23, 0, 17, 914390409), |
| 422 | + ) |
| 423 | + for native in (True, False) |
| 424 | + ), |
| 425 | + *( |
| 426 | + ( |
| 427 | + # type ignore: |
| 428 | + # https://github.com/python/typeshed/issues/12715 |
| 429 | + tz1.localize(dt1, is_dst=None), # type: ignore[arg-type] |
| 430 | + tz2.localize( |
| 431 | + DateTime(2018, 4, 27, 23, 0, 17, 914390409), |
| 432 | + is_dst=None, # type: ignore[arg-type] |
| 433 | + ), |
| 434 | + native, |
| 435 | + ) |
| 436 | + for dt1 in ( |
| 437 | + DateTime(2018, 4, 27, 23, 0, 17, 914391409), |
| 438 | + DateTime(2018, 4, 27, 23, 0, 18, 914390409), |
| 439 | + DateTime(2018, 4, 27, 23, 1, 17, 914390409), |
| 440 | + DateTime(2018, 4, 27, 22, 0, 17, 914390409), |
| 441 | + DateTime(2018, 4, 26, 23, 0, 17, 914390409), |
| 442 | + DateTime(2018, 5, 27, 23, 0, 17, 914390409), |
| 443 | + DateTime(2019, 4, 27, 23, 0, 17, 914390409), |
| 444 | + ) |
| 445 | + for native in (True, False) |
| 446 | + for tz1, tz2 in itertools.combinations_with_replacement( |
| 447 | + (timezone_utc, timezone_utc_p2, timezone_berlin), 2 |
| 448 | + ) |
| 449 | + ), |
| 450 | + ), |
| 451 | + ) |
| 452 | + def test_ne(self, dt1, dt2, native): |
| 453 | + assert isinstance(dt1, DateTime) |
| 454 | + assert isinstance(dt2, DateTime) |
| 455 | + if native: |
| 456 | + dt2 = dt2.to_native() |
| 457 | + assert dt1 != dt2 |
| 458 | + assert dt2 != dt1 |
| 459 | + # explicitly test that `not ==` is `!=` (different code paths) |
| 460 | + assert not dt1 == dt2 |
| 461 | + assert not dt2 == dt1 |
| 462 | + |
| 463 | + @pytest.mark.parametrize( |
| 464 | + "other", |
| 465 | + ( |
| 466 | + object(), |
| 467 | + 1, |
| 468 | + DateTime(2018, 4, 27, 23, 0, 17, 914391409).to_clock_time(), |
| 469 | + ( |
| 470 | + DateTime(2018, 4, 27, 23, 0, 17, 914391409) |
| 471 | + - DateTime(1970, 1, 1) |
| 472 | + ), |
| 473 | + ), |
| 474 | + ) |
| 475 | + def test_ne_object(self, other): |
| 476 | + dt = DateTime(2018, 4, 27, 23, 0, 17, 914391409) |
| 477 | + assert dt != other |
| 478 | + assert other != dt |
| 479 | + # explicitly test that `not ==` is `!=` (different code paths) |
| 480 | + assert not dt == other |
| 481 | + assert not other == dt |
| 482 | + |
277 | 483 | def test_normalization(self):
|
278 | 484 | ndt1 = timezone_us_eastern.normalize(DateTime(2018, 4, 27, 23, 0, 17, tzinfo=timezone_us_eastern))
|
279 | 485 | ndt2 = timezone_us_eastern.normalize(datetime(2018, 4, 27, 23, 0, 17, tzinfo=timezone_us_eastern))
|
|
0 commit comments