Skip to content

Commit 304c327

Browse files
feat(mdu): support yyyymmdd for startDateTime/stopDateTime (#953)
feat(mdu): support `yyyymmdd` for startDateTime/stopDateTime (#953) - Refactored datetime validator to be more robust and validate formats in a loop - Added tests for `startDateTime=yyyymmdd` and for invalid formats (expect ValueError) - Updated tests to use `Time` model and added unit tests for `validate_datetime_string` ref: #942
1 parent 494d00c commit 304c327

File tree

3 files changed

+130
-5
lines changed

3 files changed

+130
-5
lines changed

hydrolib/core/dflowfm/ini/util.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,9 @@ def validate_conditionally(
271271
def validate_datetime_string(
272272
field_value: Optional[str], field: ModelField
273273
) -> Optional[str]:
274-
"""Validate that a field value matches the YYYYmmddHHMMSS datetime format.
274+
"""Validate that a field value matches the expected datetime format.
275+
276+
The validation checks that the date formats conform to either 'YYYYmmddHHMMSS' or 'YYYYmmdd'.
275277
276278
Args:
277279
field_value (Optional[str]): value of a Pydantic field, may be optional.
@@ -289,11 +291,19 @@ def validate_datetime_string(
289291
and len(field_value.strip()) > 0
290292
and field_value != "yyyymmddhhmmss"
291293
):
292-
try:
293-
_ = datetime.strptime(field_value, r"%Y%m%d%H%M%S")
294-
except ValueError:
294+
formats = {14: "%Y%m%d%H%M%S", 8: "%Y%m%d"}
295+
result = False
296+
format_length = len(field_value)
297+
if format_length in formats:
298+
try:
299+
datetime.strptime(field_value, formats[format_length])
300+
result = True
301+
except ValueError:
302+
pass
303+
304+
if not result:
295305
raise ValueError(
296-
f"Invalid datetime string for {field.alias}: '{field_value}', expecting 'YYYYmmddHHMMSS'."
306+
f"Invalid datetime string for {field.alias}: '{field_value}', expecting 'YYYYmmddHHMMSS' or 'YYYYmmdd'."
297307
)
298308

299309
return field_value # this is the value written to the class field

tests/dflowfm/ini/test_util.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
get_from_subclass_defaults,
1616
get_type_based_on_subclass_default_value,
1717
rename_keys_for_backwards_compatibility,
18+
validate_datetime_string,
1819
validate_location_specification,
1920
)
2021

@@ -481,3 +482,59 @@ def test_keyword_given_known_as_excluded_field_does_not_throw_exception(self):
481482
)
482483
except ValueError:
483484
pytest.fail("Exception is thrown, no exception is expected for this test.")
485+
486+
487+
class TestDateTimeValidator:
488+
@pytest.mark.parametrize(
489+
"date_value",
490+
[
491+
("20230101"),
492+
("20231231"),
493+
("20230101000000"),
494+
("20231231235959"),
495+
],
496+
ids=[
497+
"Date start of year",
498+
"Date end of year",
499+
"DateTime start of year",
500+
"DateTime end of year",
501+
],
502+
)
503+
def test_mdu_datetime_valid_format_returns_value(self, date_value):
504+
field = Mock(spec=ModelField)
505+
field.name = "timefield"
506+
field.alias = "timeField"
507+
508+
returned_value = validate_datetime_string(date_value, field)
509+
510+
assert returned_value == date_value
511+
512+
@pytest.mark.parametrize(
513+
"date_value",
514+
[
515+
("invalid"),
516+
("2023010"),
517+
("202312311"),
518+
("2023010100000"),
519+
("202301010000000"),
520+
],
521+
ids=[
522+
"unknown format",
523+
"Date too short",
524+
"Date too long",
525+
"DateTime too short",
526+
"DateTime too long",
527+
],
528+
)
529+
def test_mdu_datetime_invalid_format_raises_valueerror(self, date_value):
530+
field = Mock(spec=ModelField)
531+
field.name = "timefield"
532+
field.alias = "timeField"
533+
534+
with pytest.raises(ValueError) as exc_err:
535+
validate_datetime_string(date_value, field)
536+
537+
assert (
538+
f"Invalid datetime string for {field.alias}: '{date_value}', expecting 'YYYYmmddHHMMSS' or 'YYYYmmdd'."
539+
in str(exc_err.value)
540+
)

tests/dflowfm/test_mdu.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
InfiltrationMethod,
1010
ParticlesThreeDType,
1111
ProcessFluxIntegration,
12+
Time,
1213
VegetationModelNr,
1314
)
1415
from hydrolib.core.dflowfm.net.models import Network
@@ -472,3 +473,60 @@ def test_mdu_unknown_keywords_loading_thrown_valueerror_for_unknown_keyword_does
472473
assert len(excluded_fields) > 0
473474
for excluded_field in excluded_fields:
474475
assert excluded_field not in str(exc_err.value)
476+
477+
478+
class TestTime:
479+
@pytest.mark.parametrize(
480+
"start_date_time, stop_date_time",
481+
[
482+
("20230101", "20231231"),
483+
("20230101000000", "20231231235959"),
484+
],
485+
ids=["YYYYmmdd", "YYYYmmddHHMMSS"],
486+
)
487+
def test_mdu_datetime_yyyymmdd_format(self, start_date_time, stop_date_time):
488+
time_dict = {
489+
"startDateTime": start_date_time,
490+
"stopDateTime": stop_date_time,
491+
}
492+
time = Time(**time_dict)
493+
assert time.startdatetime == start_date_time
494+
assert time.stopdatetime == stop_date_time
495+
496+
@pytest.mark.parametrize(
497+
"start_date_time, stop_date_time, invalid_fields",
498+
[
499+
("invalid", "invalid", ["startDateTime", "stopDateTime"]),
500+
("20230101", "invalid", ["stopDateTime"]),
501+
("invalid", "20231231", ["startDateTime"]),
502+
("2023010100000", "20231231235959", ["startDateTime"]),
503+
("202301010000000", "20231231235959", ["startDateTime"]),
504+
("20230101000000", "2023123123595", ["stopDateTime"]),
505+
("20230101000000", "202312312359599", ["stopDateTime"]),
506+
],
507+
ids=[
508+
"both_invalid",
509+
"stop_invalid",
510+
"start_invalid",
511+
"start_too_short",
512+
"start_too_long",
513+
"stop_too_short",
514+
"stop_too_long",
515+
],
516+
)
517+
def test_mdu_datetime_invalid_format_raises_valueerror(
518+
self, start_date_time, stop_date_time, invalid_fields
519+
):
520+
time_dict = {
521+
"startDateTime": start_date_time,
522+
"stopDateTime": stop_date_time,
523+
}
524+
525+
with pytest.raises(ValueError) as exc_err:
526+
Time(**time_dict)
527+
528+
for invalid_field in invalid_fields:
529+
assert (
530+
f"Invalid datetime string for {invalid_field}: '{time_dict[invalid_field]}', expecting 'YYYYmmddHHMMSS' or 'YYYYmmdd'."
531+
in str(exc_err.value)
532+
)

0 commit comments

Comments
 (0)