From 652d938a7f1c6f664669396009f37a778d6c1136 Mon Sep 17 00:00:00 2001 From: David Regla Date: Mon, 19 May 2025 04:32:30 -0600 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=90=9B=20fix:=20serialize=20Pendulum?= =?UTF-8?q?=20zero=20Duration=20as=20valid=20ISO=208601=20'P0D'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously output 'P' which is not parsable. --- pydantic_extra_types/pendulum_dt.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pydantic_extra_types/pendulum_dt.py b/pydantic_extra_types/pendulum_dt.py index 0461c5c..0a77ea7 100644 --- a/pydantic_extra_types/pendulum_dt.py +++ b/pydantic_extra_types/pendulum_dt.py @@ -244,6 +244,9 @@ def to_iso8601_string(self) -> str: if self.total_seconds() < 0: iso_duration = '-' + iso_duration + if iso_duration == 'P': + iso_duration += 'P0D' + return iso_duration @classmethod From 8c1562286e86e7920b8aa96c3b2f5d2efca43184 Mon Sep 17 00:00:00 2001 From: David Regla Date: Mon, 19 May 2025 04:41:12 -0600 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=90=9B=20fix:=20serialize=20Pendulum?= =?UTF-8?q?=20zero=20Duration=20as=20valid=20ISO=208601=20'P0D'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pydantic_extra_types/pendulum_dt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydantic_extra_types/pendulum_dt.py b/pydantic_extra_types/pendulum_dt.py index 0a77ea7..e0fdbf2 100644 --- a/pydantic_extra_types/pendulum_dt.py +++ b/pydantic_extra_types/pendulum_dt.py @@ -245,7 +245,7 @@ def to_iso8601_string(self) -> str: iso_duration = '-' + iso_duration if iso_duration == 'P': - iso_duration += 'P0D' + iso_duration = 'P0D' return iso_duration From c36ea990a3477a4a9ca1add5e7baedb2fe7bf5db Mon Sep 17 00:00:00 2001 From: David Regla Date: Mon, 19 May 2025 05:06:08 -0600 Subject: [PATCH 3/3] =?UTF-8?q?=E2=9C=A8test:=20add=20tests=20for=20Pendul?= =?UTF-8?q?um=20zero-duration=20serialization=20and=20parsing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_pendulum_dt.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/test_pendulum_dt.py b/tests/test_pendulum_dt.py index 8b28d70..cb0ed59 100644 --- a/tests/test_pendulum_dt.py +++ b/tests/test_pendulum_dt.py @@ -414,3 +414,35 @@ def test_pendulum_duration_months_are_preserved(): m = DurationModel(delta_t=pendulum.Duration(months=1)) assert m.delta_t.months == 1 + + +@pytest.mark.parametrize( + 'zero_duration', + [ + Duration(), + Duration(years=0, months=0, days=0, hours=0, minutes=0, seconds=0), + Duration(0), + pendulum.duration(), + ], +) +def test_zero_duration_serialization_and_validation(zero_duration): + """Zero durations should serialize as 'P0D' and round-trip without error.""" + model = DurationModel(delta_t=zero_duration) + json_dump = model.model_dump_json() + assert json_dump == '{"delta_t":"P0D"}' + loaded = DurationModel.model_validate_json(json_dump) + assert loaded.delta_t == zero_duration + assert isinstance(loaded.delta_t, Duration) + + +def test_zero_duration_from_string(): + """'P0D' string should be accepted and produce a zero Duration.""" + model = DurationModel(delta_t='P0D') + assert model.delta_t == Duration() + assert isinstance(model.delta_t, Duration) + + +def test_invalid_zero_duration_string(): + """'P' is not a valid ISO 8601 duration and should raise a validation error.""" + with pytest.raises(ValidationError): + DurationModel(delta_t='P')