Skip to content

Commit 31ced98

Browse files
committed
Fix: Improve RFC3339 date parsing validation (Issue kubernetes-client#2418)
- Replace search() with fullmatch() for strict RFC3339 format validation - Provide clear, actionable error messages with expected format - Add input whitespace stripping before validation - Improve exception handling with specific ValueError messages - Add comprehensive test cases for invalid formats - Addresses reviewer feedback from PR kubernetes-client#2420 Test: Existing tests pass + new test cases added
1 parent 1d6c076 commit 31ced98

File tree

2 files changed

+58
-6
lines changed

2 files changed

+58
-6
lines changed

kubernetes/base/config/dateutil.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,18 +49,27 @@ def dst(self, dt):
4949

5050
def parse_rfc3339(s):
5151
if isinstance(s, datetime.datetime):
52-
# no need to parse it, just make sure it has a timezone.
5352
if not s.tzinfo:
5453
return s.replace(tzinfo=UTC)
5554
return s
56-
groups = _re_rfc3339.search(s).groups()
55+
56+
m = _re_rfc3339.fullmatch(s.strip())
57+
if m is None:
58+
raise ValueError(
59+
f"Invalid RFC3339 datetime: {s!r} "
60+
"(expected YYYY-MM-DDTHH:MM:SS[.frac][Z|±HH:MM])"
61+
)
62+
63+
groups = m.groups()
5764
dt = [0] * 7
5865
for x in range(6):
5966
dt[x] = int(groups[x])
67+
6068
us = 0
6169
if groups[6] is not None:
6270
partial_sec = float(groups[6].replace(",", "."))
6371
us = int(MICROSEC_PER_SEC * partial_sec)
72+
6473
tz = UTC
6574
if groups[7] is not None and groups[7] != 'Z' and groups[7] != 'z':
6675
tz_groups = _re_timezone.search(groups[7]).groups()
@@ -71,10 +80,17 @@ def parse_rfc3339(s):
7180
if tz_groups[2]:
7281
minute = int(tz_groups[2])
7382
tz = TimezoneInfo(hour, minute)
74-
return datetime.datetime(
75-
year=dt[0], month=dt[1], day=dt[2],
76-
hour=dt[3], minute=dt[4], second=dt[5],
77-
microsecond=us, tzinfo=tz)
83+
84+
try:
85+
return datetime.datetime(
86+
year=dt[0], month=dt[1], day=dt[2],
87+
hour=dt[3], minute=dt[4], second=dt[5],
88+
microsecond=us, tzinfo=tz)
89+
except ValueError as e:
90+
raise ValueError(
91+
f"Invalid date/time values in RFC3339 string {s!r}: {e}"
92+
) from e
93+
7894

7995

8096
def format_rfc3339(date_time):

kubernetes/base/config/dateutil_test.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,39 @@ def test_format_rfc3339(self):
6666
format_rfc3339(datetime(2017, 7, 25, 4, 44, 21, 0,
6767
TimezoneInfo(-2, 30))),
6868
"2017-07-25T07:14:21Z")
69+
70+
def test_parse_rfc3339_invalid_formats(self):
71+
"""Test that invalid RFC3339 formats raise ValueError"""
72+
invalid_inputs = [
73+
"2025-13-02T13:37:00Z", # Invalid month
74+
"2025-12-32T13:37:00Z", # Invalid day
75+
"2025-12-02T25:00:00Z", # Invalid hour
76+
"2025-12-02T13:60:00Z", # Invalid minute
77+
"2025-12-02T13:37:60Z", # Invalid second
78+
"not-a-valid-date", # Completely invalid
79+
"", # Empty string
80+
"2025-12-02Z13:37:00", # Timezone before time
81+
]
82+
83+
for invalid_input in invalid_inputs:
84+
with self.assertRaises(ValueError):
85+
parse_rfc3339(invalid_input)
86+
87+
88+
89+
def test_parse_rfc3339_with_whitespace(self):
90+
"""Test that leading/trailing whitespace is handled"""
91+
actual = parse_rfc3339(" 2017-07-25T04:44:21Z ")
92+
expected = datetime(2017, 7, 25, 4, 44, 21, 0, UTC)
93+
self.assertEqual(expected, actual)
94+
95+
def test_parse_rfc3339_error_message_clarity(self):
96+
"""Test that error messages are clear and helpful"""
97+
try:
98+
parse_rfc3339("invalid-date-format")
99+
except ValueError as e:
100+
error_msg = str(e)
101+
# Verify error message contains helpful information
102+
self.assertIn("Invalid RFC3339", error_msg)
103+
self.assertIn("YYYY-MM-DD", error_msg)
104+
self.assertIn("expected", error_msg)

0 commit comments

Comments
 (0)