test_times.py 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. from datetime import datetime, timedelta
  2. import pytest
  3. import pytz
  4. import isodate
  5. from flexmeasures.data.schemas.times import DurationField, DurationValidationError
  6. @pytest.mark.parametrize(
  7. "duration_input, exp_deserialization",
  8. [
  9. ("PT1H", timedelta(hours=1)),
  10. ("PT6M", timedelta(minutes=6)),
  11. ("PT6H", timedelta(hours=6)),
  12. ("P2DT1H", timedelta(hours=49)), # https://github.com/gweis/isodate/issues/74
  13. ],
  14. )
  15. def test_duration_field_straightforward(duration_input, exp_deserialization):
  16. """Testing straightforward cases"""
  17. df = DurationField()
  18. deser = df.deserialize(duration_input, None, None)
  19. assert deser == exp_deserialization
  20. assert df.serialize("duration", {"duration": deser}) == duration_input
  21. @pytest.mark.parametrize(
  22. "duration_input, exp_deserialization, grounded_timedelta",
  23. [
  24. ("P1M", isodate.Duration(months=1), timedelta(days=29)),
  25. ("PT24H", isodate.Duration(hours=24), timedelta(hours=24)),
  26. ("P2D", isodate.Duration(hours=48), timedelta(hours=48)),
  27. # following are calendar periods including a transition to daylight saving time (DST)
  28. ("P2M", isodate.Duration(months=2), timedelta(days=60) - timedelta(hours=1)),
  29. # ("P8W", isodate.Duration(days=7*8), timedelta(weeks=8) - timedelta(hours=1)),
  30. # ("P100D", isodate.Duration(days=100), timedelta(days=100) - timedelta(hours=1)),
  31. # following is a calendar period with transitions to DST and back again
  32. ("P1Y", isodate.Duration(years=1), timedelta(days=366)),
  33. ],
  34. )
  35. def test_duration_field_nominal_grounded(
  36. duration_input, exp_deserialization, grounded_timedelta
  37. ):
  38. """Nominal durations are tricky:
  39. https://en.wikipedia.org/wiki/Talk:ISO_8601/Archive_2#Definition_of_Duration_is_incorrect
  40. We want to test if we can ground them as expected.
  41. We use a particular datetime to ground, in a leap year February.
  42. For the Europe/Amsterdam timezone, daylight saving time started on March 29th 2020.
  43. # todo: the commented out tests would work if isodate.parse_duration would have the option to stop coercing ISO 8601 days into datetime.timedelta days
  44. """
  45. df = DurationField()
  46. deser = df.deserialize(duration_input, None, None)
  47. assert deser == exp_deserialization
  48. dummy_time = pytz.timezone("Europe/Amsterdam").localize(
  49. datetime(2020, 2, 22, 18, 7)
  50. )
  51. grounded = DurationField.ground_from(deser, dummy_time)
  52. assert grounded == grounded_timedelta
  53. @pytest.mark.parametrize(
  54. "duration_input, error_msg",
  55. [
  56. ("", "Unable to parse duration string"),
  57. ("1H", "Unable to parse duration string"),
  58. ("PP1M", "time designator 'T' missing"),
  59. ("PT2D", "Unrecognised ISO 8601 date format"),
  60. ("PT40S", "FlexMeasures only support multiples of 1 minute."),
  61. ],
  62. )
  63. def test_duration_field_invalid(duration_input, error_msg):
  64. df = DurationField()
  65. with pytest.raises(DurationValidationError) as ve:
  66. df.deserialize(duration_input, None, None)
  67. assert error_msg in str(ve)