From 7053199611d51cf1cc906a7df3840d59cb1b23f7 Mon Sep 17 00:00:00 2001 From: Ayush Kumar Date: Thu, 19 Mar 2026 06:02:37 +0530 Subject: [PATCH 01/18] Add parse_datetime() --- src/pendulum/parser.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/pendulum/parser.py b/src/pendulum/parser.py index 833bae3c..b57824a4 100644 --- a/src/pendulum/parser.py +++ b/src/pendulum/parser.py @@ -134,3 +134,15 @@ def _parse( ) raise NotImplementedError + + +def parse_datetime(text: str, **options: t.Any) -> DateTime: + """ + Parses a string into a DateTime. + + :param text: The string to parse. + """ + result = parse(text, **options) + if not isinstance(result, DateTime): + raise ValueError(f"Invalid datetime string: {text}") + return result From 2e811b75a9476171de828a790114be29d35ce636 Mon Sep 17 00:00:00 2001 From: Ayush Kumar Date: Thu, 19 Mar 2026 06:10:31 +0530 Subject: [PATCH 02/18] Add parse_date() --- src/pendulum/parser.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/pendulum/parser.py b/src/pendulum/parser.py index b57824a4..ffe27f1b 100644 --- a/src/pendulum/parser.py +++ b/src/pendulum/parser.py @@ -146,3 +146,15 @@ def parse_datetime(text: str, **options: t.Any) -> DateTime: if not isinstance(result, DateTime): raise ValueError(f"Invalid datetime string: {text}") return result + + +def parse_date(text: str, **options: t.Any) -> Date: + """ + Parses a string into a Date. + + :param text: The string to parse. + """ + result = parse(text, **options) + if not isinstance(result, Date): + raise ValueError(f"Invalid date string: {text}") + return result From 804b1fac9e4f6ec284f0c16098616a7cc0b0b5c5 Mon Sep 17 00:00:00 2001 From: Ayush Kumar Date: Thu, 19 Mar 2026 06:14:26 +0530 Subject: [PATCH 03/18] Add parse_time() --- src/pendulum/parser.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/pendulum/parser.py b/src/pendulum/parser.py index ffe27f1b..de420c3d 100644 --- a/src/pendulum/parser.py +++ b/src/pendulum/parser.py @@ -158,3 +158,15 @@ def parse_date(text: str, **options: t.Any) -> Date: if not isinstance(result, Date): raise ValueError(f"Invalid date string: {text}") return result + + +def parse_time(text: str, **options: t.Any) -> Time: + """ + Parses a string into a Time. + + :param text: The string to parse. + """ + result = parse(text, **options) + if not isinstance(result, Time): + raise ValueError(f"Invalid time string: {text}") + return result From fa89c942181a5bd7f8ac18388ebbbd136e9ea8b8 Mon Sep 17 00:00:00 2001 From: Ayush Kumar Date: Thu, 19 Mar 2026 06:18:30 +0530 Subject: [PATCH 04/18] Add parse_duration() --- src/pendulum/parser.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/pendulum/parser.py b/src/pendulum/parser.py index de420c3d..08052a8f 100644 --- a/src/pendulum/parser.py +++ b/src/pendulum/parser.py @@ -6,6 +6,7 @@ import pendulum +from pendulum._pendulum import is_leap from pendulum.duration import Duration from pendulum.parsing import _Interval from pendulum.parsing import parse as base_parse @@ -170,3 +171,15 @@ def parse_time(text: str, **options: t.Any) -> Time: if not isinstance(result, Time): raise ValueError(f"Invalid time string: {text}") return result + + +def parse_duration(text: str, **options: t.Any) -> Duration: + """ + Parses a string into a Duration. + + :param text: The string to parse. + """ + result = parse(text, **options) + if not isinstance(result, Duration): + raise ValueError(f"Invalid duration string: {text}") + return result From 78d9ecc9e28baa0a72360bca419200ebce5b822d Mon Sep 17 00:00:00 2001 From: Ayush Kumar Date: Thu, 19 Mar 2026 17:03:39 +0530 Subject: [PATCH 05/18] Change variable name to improve consistency. --- src/pendulum/parser.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/pendulum/parser.py b/src/pendulum/parser.py index 08052a8f..351e98c6 100644 --- a/src/pendulum/parser.py +++ b/src/pendulum/parser.py @@ -143,10 +143,10 @@ def parse_datetime(text: str, **options: t.Any) -> DateTime: :param text: The string to parse. """ - result = parse(text, **options) - if not isinstance(result, DateTime): + parsed = parse(text, **options) + if not isinstance(parsed, DateTime): raise ValueError(f"Invalid datetime string: {text}") - return result + return parsed def parse_date(text: str, **options: t.Any) -> Date: @@ -155,10 +155,10 @@ def parse_date(text: str, **options: t.Any) -> Date: :param text: The string to parse. """ - result = parse(text, **options) - if not isinstance(result, Date): + parsed = parse(text, **options) + if not isinstance(parsed, Date): raise ValueError(f"Invalid date string: {text}") - return result + return parsed def parse_time(text: str, **options: t.Any) -> Time: @@ -167,10 +167,10 @@ def parse_time(text: str, **options: t.Any) -> Time: :param text: The string to parse. """ - result = parse(text, **options) - if not isinstance(result, Time): + parsed = parse(text, **options) + if not isinstance(parsed, Time): raise ValueError(f"Invalid time string: {text}") - return result + return parsed def parse_duration(text: str, **options: t.Any) -> Duration: @@ -179,7 +179,7 @@ def parse_duration(text: str, **options: t.Any) -> Duration: :param text: The string to parse. """ - result = parse(text, **options) - if not isinstance(result, Duration): + parsed = parse(text, **options) + if not isinstance(parsed, Duration): raise ValueError(f"Invalid duration string: {text}") - return result + return parsed From 75b2b117c7d4e20740a72000d5cb6076e16504b5 Mon Sep 17 00:00:00 2001 From: Ayush Kumar Date: Thu, 19 Mar 2026 17:14:51 +0530 Subject: [PATCH 06/18] Create tests for parse_datetime() --- tests/parsing/test_strict_parse.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 tests/parsing/test_strict_parse.py diff --git a/tests/parsing/test_strict_parse.py b/tests/parsing/test_strict_parse.py new file mode 100644 index 00000000..ed53c32c --- /dev/null +++ b/tests/parsing/test_strict_parse.py @@ -0,0 +1,20 @@ +import pytest + +from pendulum.parser import ( + parse_datetime, + parse_date, + parse_time, + parse_duration, +) + +def test_parse_datetime_valid() -> None: + dt = parse_datetime("2026-03-19T11:28:37") + assert dt.year == 2026 + +def test_parse_datetime_rejects_date() -> None: + with pytest.raises(ValueError): + dt = parse_datetime("2026-03-19") + +def test_parse_datetime_rejects_interval() -> None: + with pytest.raises(ValueError): + dt = parse_datetime("2026-03-19T12:00:00/2026-03-19T13:00:00") From b6ef48d437809b4b7a498241fa1f99ebb53ecb0f Mon Sep 17 00:00:00 2001 From: Ayush Kumar Date: Thu, 19 Mar 2026 17:19:40 +0530 Subject: [PATCH 07/18] Create tests for parse_date() --- tests/parsing/test_strict_parse.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/parsing/test_strict_parse.py b/tests/parsing/test_strict_parse.py index ed53c32c..fdd47a91 100644 --- a/tests/parsing/test_strict_parse.py +++ b/tests/parsing/test_strict_parse.py @@ -7,6 +7,8 @@ parse_duration, ) +# tests for parse_datetime + def test_parse_datetime_valid() -> None: dt = parse_datetime("2026-03-19T11:28:37") assert dt.year == 2026 @@ -18,3 +20,13 @@ def test_parse_datetime_rejects_date() -> None: def test_parse_datetime_rejects_interval() -> None: with pytest.raises(ValueError): dt = parse_datetime("2026-03-19T12:00:00/2026-03-19T13:00:00") + +# tests for parse_date + +def test_parse_date_valid() -> None: + dt = parse_date("2026-03-19") + assert dt.year == 2026 + +def test_parse_date_rejects_datetime() -> None: + with pytest.raises(ValueError): + dt = parse_date("2026-03-19T11:28:37") From 2bade2bc24140a4ee8e64d6540344c09f9a09988 Mon Sep 17 00:00:00 2001 From: Ayush Kumar Date: Thu, 19 Mar 2026 17:25:52 +0530 Subject: [PATCH 08/18] Create tests for parse_time --- tests/parsing/test_strict_parse.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/tests/parsing/test_strict_parse.py b/tests/parsing/test_strict_parse.py index fdd47a91..e47751e2 100644 --- a/tests/parsing/test_strict_parse.py +++ b/tests/parsing/test_strict_parse.py @@ -15,18 +15,28 @@ def test_parse_datetime_valid() -> None: def test_parse_datetime_rejects_date() -> None: with pytest.raises(ValueError): - dt = parse_datetime("2026-03-19") + parse_datetime("2026-03-19") def test_parse_datetime_rejects_interval() -> None: with pytest.raises(ValueError): - dt = parse_datetime("2026-03-19T12:00:00/2026-03-19T13:00:00") + parse_datetime("2026-03-19T12:00:00/2026-03-19T13:00:00") # tests for parse_date def test_parse_date_valid() -> None: - dt = parse_date("2026-03-19") - assert dt.year == 2026 + d = parse_date("2026-03-19") + assert d.day == 19 def test_parse_date_rejects_datetime() -> None: with pytest.raises(ValueError): - dt = parse_date("2026-03-19T11:28:37") + parse_date("2026-03-19T11:28:37") + +# tests for parse_time + +def test_parse_time_valid() -> None: + t = parse_time("11:28:37") + assert t.hour == 11 + +def test_parse_time_rejects_datetime() -> None: + with pytest.raises(ValueError): + parse_time("2026-03-19T11:28:37") From aeaad342591c03f67af3f93341c3674db3cecb81 Mon Sep 17 00:00:00 2001 From: Ayush Kumar Date: Thu, 19 Mar 2026 17:31:08 +0530 Subject: [PATCH 09/18] Create tests for parse_duration --- tests/parsing/test_strict_parse.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/parsing/test_strict_parse.py b/tests/parsing/test_strict_parse.py index e47751e2..67e17df5 100644 --- a/tests/parsing/test_strict_parse.py +++ b/tests/parsing/test_strict_parse.py @@ -40,3 +40,13 @@ def test_parse_time_valid() -> None: def test_parse_time_rejects_datetime() -> None: with pytest.raises(ValueError): parse_time("2026-03-19T11:28:37") + +# tests for parse_duration + +def test_parse_duration_valid() -> None: + dur = parse_duration("PT2H") + assert dur.hours == 2 + +def test_parse_duration_rejects_datetime() -> None: + with pytest.raises(ValueError): + parse_duration("2026-03-19T11:28:37") From 07f904dabcf0e11674e0a86edaf5ff40eb52e99b Mon Sep 17 00:00:00 2001 From: Ayush Kumar Date: Thu, 19 Mar 2026 17:41:20 +0530 Subject: [PATCH 10/18] Create edge case test for parse_datetime and imports in parser --- src/pendulum/parser.py | 3 +++ tests/parsing/test_strict_parse.py | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/src/pendulum/parser.py b/src/pendulum/parser.py index 351e98c6..a2a9f0ca 100644 --- a/src/pendulum/parser.py +++ b/src/pendulum/parser.py @@ -7,6 +7,9 @@ import pendulum from pendulum._pendulum import is_leap +from pendulum.datetime import DateTime +from pendulum.date import Date +from pendulum.time import Time from pendulum.duration import Duration from pendulum.parsing import _Interval from pendulum.parsing import parse as base_parse diff --git a/tests/parsing/test_strict_parse.py b/tests/parsing/test_strict_parse.py index 67e17df5..bb49aead 100644 --- a/tests/parsing/test_strict_parse.py +++ b/tests/parsing/test_strict_parse.py @@ -21,6 +21,10 @@ def test_parse_datetime_rejects_interval() -> None: with pytest.raises(ValueError): parse_datetime("2026-03-19T12:00:00/2026-03-19T13:00:00") +def test_parse_datetime_accepts_only_year() -> None: + dt = parse_datetime("2026") + assert dt.year == 2026 + # tests for parse_date def test_parse_date_valid() -> None: From 9d6d48349e33e50a72ba7b990987df020bf971df Mon Sep 17 00:00:00 2001 From: Ayush Kumar Date: Thu, 19 Mar 2026 18:25:00 +0530 Subject: [PATCH 11/18] Enable strict checks using exact = True --- src/pendulum/parser.py | 15 +++++++++++++++ tests/parsing/test_strict_parse.py | 19 ++++++++++++++++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/pendulum/parser.py b/src/pendulum/parser.py index a2a9f0ca..aace00ee 100644 --- a/src/pendulum/parser.py +++ b/src/pendulum/parser.py @@ -146,9 +146,14 @@ def parse_datetime(text: str, **options: t.Any) -> DateTime: :param text: The string to parse. """ + if "T" not in text and " " not in text: + raise ValueError(f"Invalid datetime string: {text}") + parsed = parse(text, **options) + if not isinstance(parsed, DateTime): raise ValueError(f"Invalid datetime string: {text}") + return parsed @@ -158,9 +163,15 @@ def parse_date(text: str, **options: t.Any) -> Date: :param text: The string to parse. """ + if "T" in text or ":" in text: + raise ValueError(f"Invalid date string: {text}") + parsed = parse(text, **options) if not isinstance(parsed, Date): + if isinstance(parsed, DateTime): + return parsed.date() raise ValueError(f"Invalid date string: {text}") + return parsed @@ -170,8 +181,12 @@ def parse_time(text: str, **options: t.Any) -> Time: :param text: The string to parse. """ + if ":" not in text or "-" in text: + raise ValueError(f"Invalid time string: {text}") parsed = parse(text, **options) if not isinstance(parsed, Time): + if isinstance(parsed, DateTime): + return parsed.time() raise ValueError(f"Invalid time string: {text}") return parsed diff --git a/tests/parsing/test_strict_parse.py b/tests/parsing/test_strict_parse.py index bb49aead..26b17c05 100644 --- a/tests/parsing/test_strict_parse.py +++ b/tests/parsing/test_strict_parse.py @@ -9,48 +9,61 @@ # tests for parse_datetime + def test_parse_datetime_valid() -> None: dt = parse_datetime("2026-03-19T11:28:37") assert dt.year == 2026 -def test_parse_datetime_rejects_date() -> None: - with pytest.raises(ValueError): - parse_datetime("2026-03-19") + +def test_parse_datetime_accepts_date() -> None: + dt = parse_datetime("2026-03-19") + assert dt.hour == 0 + def test_parse_datetime_rejects_interval() -> None: with pytest.raises(ValueError): parse_datetime("2026-03-19T12:00:00/2026-03-19T13:00:00") + def test_parse_datetime_accepts_only_year() -> None: dt = parse_datetime("2026") assert dt.year == 2026 + # tests for parse_date + def test_parse_date_valid() -> None: d = parse_date("2026-03-19") assert d.day == 19 + def test_parse_date_rejects_datetime() -> None: with pytest.raises(ValueError): parse_date("2026-03-19T11:28:37") + # tests for parse_time + def test_parse_time_valid() -> None: t = parse_time("11:28:37") assert t.hour == 11 + def test_parse_time_rejects_datetime() -> None: with pytest.raises(ValueError): parse_time("2026-03-19T11:28:37") + # tests for parse_duration + def test_parse_duration_valid() -> None: dur = parse_duration("PT2H") assert dur.hours == 2 + def test_parse_duration_rejects_datetime() -> None: with pytest.raises(ValueError): parse_duration("2026-03-19T11:28:37") From f90daaef95dc1e5e3686f47f64bf9fba9dd12238 Mon Sep 17 00:00:00 2001 From: Ayush Kumar Date: Thu, 19 Mar 2026 18:27:20 +0530 Subject: [PATCH 12/18] Testing --- src/pendulum/parser.py | 15 ++------------- tests/parsing/test_strict_parse.py | 4 ++-- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/pendulum/parser.py b/src/pendulum/parser.py index aace00ee..c407124c 100644 --- a/src/pendulum/parser.py +++ b/src/pendulum/parser.py @@ -146,14 +146,9 @@ def parse_datetime(text: str, **options: t.Any) -> DateTime: :param text: The string to parse. """ - if "T" not in text and " " not in text: - raise ValueError(f"Invalid datetime string: {text}") - parsed = parse(text, **options) - if not isinstance(parsed, DateTime): raise ValueError(f"Invalid datetime string: {text}") - return parsed @@ -163,15 +158,11 @@ def parse_date(text: str, **options: t.Any) -> Date: :param text: The string to parse. """ - if "T" in text or ":" in text: - raise ValueError(f"Invalid date string: {text}") - - parsed = parse(text, **options) + parsed = parse(text, exact = True, **options) if not isinstance(parsed, Date): if isinstance(parsed, DateTime): return parsed.date() raise ValueError(f"Invalid date string: {text}") - return parsed @@ -181,9 +172,7 @@ def parse_time(text: str, **options: t.Any) -> Time: :param text: The string to parse. """ - if ":" not in text or "-" in text: - raise ValueError(f"Invalid time string: {text}") - parsed = parse(text, **options) + parsed = parse(text, exact = True, **options) if not isinstance(parsed, Time): if isinstance(parsed, DateTime): return parsed.time() diff --git a/tests/parsing/test_strict_parse.py b/tests/parsing/test_strict_parse.py index 26b17c05..dbb180e4 100644 --- a/tests/parsing/test_strict_parse.py +++ b/tests/parsing/test_strict_parse.py @@ -38,7 +38,7 @@ def test_parse_date_valid() -> None: assert d.day == 19 -def test_parse_date_rejects_datetime() -> None: +def test_parse_date_accepts_datetime() -> None: with pytest.raises(ValueError): parse_date("2026-03-19T11:28:37") @@ -51,7 +51,7 @@ def test_parse_time_valid() -> None: assert t.hour == 11 -def test_parse_time_rejects_datetime() -> None: +def test_parse_time_accepts_datetime() -> None: with pytest.raises(ValueError): parse_time("2026-03-19T11:28:37") From 76902106910775efdf3a39ef854e350c82923130 Mon Sep 17 00:00:00 2001 From: Ayush Kumar Date: Thu, 19 Mar 2026 18:30:56 +0530 Subject: [PATCH 13/18] Fix test bugs --- tests/parsing/test_strict_parse.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/parsing/test_strict_parse.py b/tests/parsing/test_strict_parse.py index dbb180e4..779d4d98 100644 --- a/tests/parsing/test_strict_parse.py +++ b/tests/parsing/test_strict_parse.py @@ -39,8 +39,8 @@ def test_parse_date_valid() -> None: def test_parse_date_accepts_datetime() -> None: - with pytest.raises(ValueError): - parse_date("2026-03-19T11:28:37") + d = parse_date("2026-03-19T11:28:37") + assert d.day == 19 # tests for parse_time @@ -52,8 +52,8 @@ def test_parse_time_valid() -> None: def test_parse_time_accepts_datetime() -> None: - with pytest.raises(ValueError): - parse_time("2026-03-19T11:28:37") + t = parse_time("2026-03-19T11:28:37") + assert t.minute == 28 # tests for parse_duration From bd50665983534ac2cc56e9faa458e15f778b5826 Mon Sep 17 00:00:00 2001 From: Ayush Kumar Date: Thu, 19 Mar 2026 18:41:05 +0530 Subject: [PATCH 14/18] Add more edge cases tests --- tests/parsing/test_strict_parse.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/parsing/test_strict_parse.py b/tests/parsing/test_strict_parse.py index 779d4d98..0489ab37 100644 --- a/tests/parsing/test_strict_parse.py +++ b/tests/parsing/test_strict_parse.py @@ -42,6 +42,10 @@ def test_parse_date_accepts_datetime() -> None: d = parse_date("2026-03-19T11:28:37") assert d.day == 19 +def test_parse_date_rejects_time() -> None: + with pytest.raises(ValueError): + parse_date("11:28:37") + # tests for parse_time @@ -55,6 +59,9 @@ def test_parse_time_accepts_datetime() -> None: t = parse_time("2026-03-19T11:28:37") assert t.minute == 28 +def test_parse_time_rejects_date() -> None: + with pytest.raises(ValueError): + parse_time("2026-03-19") # tests for parse_duration From 0f28ae9db09a7b415fb258b01af064dfb6652f37 Mon Sep 17 00:00:00 2001 From: Ayush Kumar Date: Thu, 19 Mar 2026 19:24:06 +0530 Subject: [PATCH 15/18] Add more tests --- tests/parsing/test_strict_parse.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/parsing/test_strict_parse.py b/tests/parsing/test_strict_parse.py index 0489ab37..e977bc9d 100644 --- a/tests/parsing/test_strict_parse.py +++ b/tests/parsing/test_strict_parse.py @@ -19,6 +19,9 @@ def test_parse_datetime_accepts_date() -> None: dt = parse_datetime("2026-03-19") assert dt.hour == 0 +def test_parse_datetime_rejects_duration() -> None: + with pytest.raises(ValueError): + parse_datetime("PT2H") def test_parse_datetime_rejects_interval() -> None: with pytest.raises(ValueError): @@ -46,6 +49,14 @@ def test_parse_date_rejects_time() -> None: with pytest.raises(ValueError): parse_date("11:28:37") +def test_parse_date_rejects_duration() -> None: + with pytest.raises(ValueError): + parse_date("PT2H") + +def test_parse_date_rejects_intervals() -> None: + with pytest.raises(ValueError): + parse_date("2026-03-19T12:00:00/2026-03-19T13:00:00") + # tests for parse_time @@ -63,6 +74,14 @@ def test_parse_time_rejects_date() -> None: with pytest.raises(ValueError): parse_time("2026-03-19") +def test_parse_time_rejects_duration() -> None: + with pytest.raises(ValueError): + parse_time("PT2H") + +def test_parse_time_rejects_interval() -> None: + with pytest.raises(ValueError): + parse_time("2026-03-19T12:00:00/2026-03-19T13:00:00") + # tests for parse_duration From 7f841b13a4190042046a9e7bc995214755179389 Mon Sep 17 00:00:00 2001 From: Ayush Kumar Date: Thu, 19 Mar 2026 19:29:36 +0530 Subject: [PATCH 16/18] Update __init___.py and format tests --- src/pendulum/__init__.py | 4 ++++ tests/parsing/test_strict_parse.py | 11 ++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/pendulum/__init__.py b/src/pendulum/__init__.py index 16ae0865..60b6df72 100644 --- a/src/pendulum/__init__.py +++ b/src/pendulum/__init__.py @@ -31,6 +31,10 @@ from pendulum.helpers import week_starts_at from pendulum.interval import Interval from pendulum.parser import parse as parse +from pendulum.parser import parse_datetime as parse_datetime +from pendulum.parser import parse_time as parse_time +from pendulum.parser import parse_date as parse_date +from pendulum.parser import parse_duration as parse_duration from pendulum.time import Time from pendulum.tz import UTC from pendulum.tz import fixed_timezone diff --git a/tests/parsing/test_strict_parse.py b/tests/parsing/test_strict_parse.py index e977bc9d..1986baf1 100644 --- a/tests/parsing/test_strict_parse.py +++ b/tests/parsing/test_strict_parse.py @@ -19,10 +19,12 @@ def test_parse_datetime_accepts_date() -> None: dt = parse_datetime("2026-03-19") assert dt.hour == 0 + def test_parse_datetime_rejects_duration() -> None: with pytest.raises(ValueError): parse_datetime("PT2H") + def test_parse_datetime_rejects_interval() -> None: with pytest.raises(ValueError): parse_datetime("2026-03-19T12:00:00/2026-03-19T13:00:00") @@ -45,14 +47,17 @@ def test_parse_date_accepts_datetime() -> None: d = parse_date("2026-03-19T11:28:37") assert d.day == 19 + def test_parse_date_rejects_time() -> None: with pytest.raises(ValueError): parse_date("11:28:37") + def test_parse_date_rejects_duration() -> None: with pytest.raises(ValueError): parse_date("PT2H") + def test_parse_date_rejects_intervals() -> None: with pytest.raises(ValueError): parse_date("2026-03-19T12:00:00/2026-03-19T13:00:00") @@ -68,20 +73,24 @@ def test_parse_time_valid() -> None: def test_parse_time_accepts_datetime() -> None: t = parse_time("2026-03-19T11:28:37") - assert t.minute == 28 + assert t.minute == 28 + def test_parse_time_rejects_date() -> None: with pytest.raises(ValueError): parse_time("2026-03-19") + def test_parse_time_rejects_duration() -> None: with pytest.raises(ValueError): parse_time("PT2H") + def test_parse_time_rejects_interval() -> None: with pytest.raises(ValueError): parse_time("2026-03-19T12:00:00/2026-03-19T13:00:00") + # tests for parse_duration From c830d50dc4d4d125d74c6d5f1fc9ec5c5608bb4c Mon Sep 17 00:00:00 2001 From: Ayush Kumar Date: Thu, 19 Mar 2026 19:44:14 +0530 Subject: [PATCH 17/18] Ruff checks passed --- src/pendulum/__init__.py | 4 ++-- src/pendulum/parser.py | 8 ++------ tests/parsing/test_strict_parse.py | 13 +++++++------ 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/pendulum/__init__.py b/src/pendulum/__init__.py index 60b6df72..5e70790b 100644 --- a/src/pendulum/__init__.py +++ b/src/pendulum/__init__.py @@ -31,10 +31,10 @@ from pendulum.helpers import week_starts_at from pendulum.interval import Interval from pendulum.parser import parse as parse -from pendulum.parser import parse_datetime as parse_datetime -from pendulum.parser import parse_time as parse_time from pendulum.parser import parse_date as parse_date +from pendulum.parser import parse_datetime as parse_datetime from pendulum.parser import parse_duration as parse_duration +from pendulum.parser import parse_time as parse_time from pendulum.time import Time from pendulum.tz import UTC from pendulum.tz import fixed_timezone diff --git a/src/pendulum/parser.py b/src/pendulum/parser.py index c407124c..d8475586 100644 --- a/src/pendulum/parser.py +++ b/src/pendulum/parser.py @@ -6,21 +6,17 @@ import pendulum -from pendulum._pendulum import is_leap -from pendulum.datetime import DateTime from pendulum.date import Date -from pendulum.time import Time +from pendulum.datetime import DateTime from pendulum.duration import Duration from pendulum.parsing import _Interval from pendulum.parsing import parse as base_parse +from pendulum.time import Time from pendulum.tz.timezone import UTC if t.TYPE_CHECKING: - from pendulum.date import Date - from pendulum.datetime import DateTime from pendulum.interval import Interval - from pendulum.time import Time with_extensions = os.getenv("PENDULUM_EXTENSIONS", "1") == "1" diff --git a/tests/parsing/test_strict_parse.py b/tests/parsing/test_strict_parse.py index 1986baf1..67e149b7 100644 --- a/tests/parsing/test_strict_parse.py +++ b/tests/parsing/test_strict_parse.py @@ -1,11 +1,12 @@ +from __future__ import annotations + import pytest -from pendulum.parser import ( - parse_datetime, - parse_date, - parse_time, - parse_duration, -) +from pendulum.parser import parse_date +from pendulum.parser import parse_datetime +from pendulum.parser import parse_duration +from pendulum.parser import parse_time + # tests for parse_datetime From 23a7a456e7bd72148bb495077b5d13fc1a019c23 Mon Sep 17 00:00:00 2001 From: Ayush <164005761+sudoscrawl@users.noreply.github.com> Date: Thu, 19 Mar 2026 19:54:16 +0530 Subject: [PATCH 18/18] Document strict parsing helpers in parsing.md Added strict parsing helpers for datetime, date, time, and duration. --- docs/docs/parsing.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/docs/docs/parsing.md b/docs/docs/parsing.md index dd78fd45..d08f7083 100644 --- a/docs/docs/parsing.md +++ b/docs/docs/parsing.md @@ -7,7 +7,7 @@ The library natively supports the RFC 3339 format, most ISO 8601 formats and som >>> dt = pendulum.parse('1975-05-21T22:00:00') >>> print(dt) -'1975-05-21T22:00:00+00:00 +'1975-05-21T22:00:00+00:00' # You can pass a tz keyword to specify the timezone >>> dt = pendulum.parse('1975-05-21T22:00:00', tz='Europe/Paris') @@ -112,3 +112,21 @@ When passing only time information the date will default to today. >>> pendulum.parse('12:04:23', exact=True) Time(12, 04, 23) ``` +### Strict parsing helpers + +Pendulum also provides helper functions for strict parsing of specific types: + +```python +>>> import pendulum + +>>> pendulum.parse_datetime("2016-10-16T12:34:56") +DateTime(...) + +>>> pendulum.parse_date("2016-10-16") +Date(2016, 10, 16) + +>>> pendulum.parse_time("12:34:56") +Time(12, 34, 56) + +>>> pendulum.parse_duration("PT2H") +Duration(...)