diff --git a/src/tomli/_re.py b/src/tomli/_re.py index c159dcfa..fc374ed6 100644 --- a/src/tomli/_re.py +++ b/src/tomli/_re.py @@ -14,12 +14,14 @@ from ._types import ParseFloat -# E.g. -# - 00:32:00.999999 -# - 00:32:00 -_TIME_RE_STR: Final = ( - r"([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])(?:\.([0-9]{1,6})[0-9]*)?" -) +_TIME_RE_STR: Final = r""" +([01][0-9]|2[0-3]) # hours +:([0-5][0-9]) # minutes +(?: + :([0-5][0-9]) # optional seconds + (?:\.([0-9]{1,6})[0-9]*)? # optional fractions of a second +)? +""" RE_NUMBER: Final = re.compile( r""" @@ -40,7 +42,7 @@ """, flags=re.VERBOSE, ) -RE_LOCALTIME: Final = re.compile(_TIME_RE_STR) +RE_LOCALTIME: Final = re.compile(_TIME_RE_STR, flags=re.VERBOSE) RE_DATETIME: Final = re.compile( rf""" ([0-9]{{4}})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]) # date, e.g. 1988-10-27 @@ -76,7 +78,8 @@ def match_to_datetime(match: re.Match[str]) -> datetime | date: year, month, day = int(year_str), int(month_str), int(day_str) if hour_str is None: return date(year, month, day) - hour, minute, sec = int(hour_str), int(minute_str), int(sec_str) + hour, minute = int(hour_str), int(minute_str) + sec = int(sec_str) if sec_str else 0 micros = int(micros_str.ljust(6, "0")) if micros_str else 0 if offset_sign_str: tz: tzinfo | None = cached_tz( @@ -105,8 +108,9 @@ def cached_tz(hour_str: str, minute_str: str, sign_str: str) -> timezone: def match_to_localtime(match: re.Match[str]) -> time: hour_str, minute_str, sec_str, micros_str = match.groups() + sec = int(sec_str) if sec_str else 0 micros = int(micros_str.ljust(6, "0")) if micros_str else 0 - return time(int(hour_str), int(minute_str), int(sec_str), micros) + return time(int(hour_str), int(minute_str), sec, micros) def match_to_number(match: re.Match[str], parse_float: ParseFloat) -> Any: diff --git a/tests/data/valid/dates-and-times/datetimes.json b/tests/data/valid/dates-and-times/datetimes.json index 99aca873..09a7c083 100644 --- a/tests/data/valid/dates-and-times/datetimes.json +++ b/tests/data/valid/dates-and-times/datetimes.json @@ -1,4 +1,5 @@ { "local-dt": {"type":"datetime-local","value":"1988-10-27t01:01:01"}, + "local-dt-no-seconds": {"type":"datetime-local","value":"2025-04-18t20:05:00"}, "zulu-dt": {"type":"datetime","value":"1988-10-27t01:01:01z"} } diff --git a/tests/data/valid/dates-and-times/datetimes.toml b/tests/data/valid/dates-and-times/datetimes.toml index cf84159d..5dc4b318 100644 --- a/tests/data/valid/dates-and-times/datetimes.toml +++ b/tests/data/valid/dates-and-times/datetimes.toml @@ -1,2 +1,3 @@ local-dt=1988-10-27t01:01:01 +local-dt-no-seconds=2025-04-18T20:05 zulu-dt=1988-10-27t01:01:01z diff --git a/tests/data/valid/dates-and-times/localtime.json b/tests/data/valid/dates-and-times/localtime.json index 4d96abcb..1f66348b 100644 --- a/tests/data/valid/dates-and-times/localtime.json +++ b/tests/data/valid/dates-and-times/localtime.json @@ -1,2 +1,4 @@ {"t": - {"type":"time-local","value":"00:00:00.999999"}} + {"type":"time-local","value":"00:00:00.999999"}, +"t2": + {"type":"time-local","value":"00:00:00"}} diff --git a/tests/data/valid/dates-and-times/localtime.toml b/tests/data/valid/dates-and-times/localtime.toml index 87547c1c..6579b30c 100644 --- a/tests/data/valid/dates-and-times/localtime.toml +++ b/tests/data/valid/dates-and-times/localtime.toml @@ -1 +1,2 @@ -t=00:00:00.99999999999999 \ No newline at end of file +t=00:00:00.99999999999999 +t2=00:00 \ No newline at end of file diff --git a/tests/test_data.py b/tests/test_data.py index f268ce9f..45c451e9 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -41,19 +41,6 @@ def test_valid(self): for valid, expected in zip(VALID_FILES, VALID_FILES_EXPECTED): with self.subTest(msg=valid.stem): toml_str = valid.read_bytes().decode() - - # xfail on TOML 1.1 compatibility tests that Tomli isn't - # compatible with yet. - if valid.stem in { - "no-seconds", - "common-34", - "common-31", - "common-29", - }: - with self.assertRaises(tomllib.TOMLDecodeError): - tomllib.loads(toml_str) - continue - actual = tomllib.loads(toml_str) actual = burntsushi.convert(actual) # type: ignore[no-untyped-call] expected = burntsushi.normalize(expected)