|  | # SPDX-License-Identifier: MIT | 
|  | # SPDX-FileCopyrightText: 2021 Taneli Hukkinen | 
|  | # Licensed to PSF under a Contributor Agreement. | 
|  |  | 
|  | from __future__ import annotations | 
|  |  | 
|  | from datetime import date, datetime, time, timedelta, timezone, tzinfo | 
|  | from functools import lru_cache | 
|  | import re | 
|  | from typing import Any | 
|  |  | 
|  | from ._types import ParseFloat | 
|  |  | 
|  | # E.g. | 
|  | # - 00:32:00.999999 | 
|  | # - 00:32:00 | 
|  | _TIME_RE_STR = r"([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])(?:\.([0-9]{1,6})[0-9]*)?" | 
|  |  | 
|  | RE_NUMBER = re.compile( | 
|  | r""" | 
|  | 0 | 
|  | (?: | 
|  | x[0-9A-Fa-f](?:_?[0-9A-Fa-f])*   # hex | 
|  | | | 
|  | b[01](?:_?[01])*                 # bin | 
|  | | | 
|  | o[0-7](?:_?[0-7])*               # oct | 
|  | ) | 
|  | | | 
|  | [+-]?(?:0|[1-9](?:_?[0-9])*)         # dec, integer part | 
|  | (?P<floatpart> | 
|  | (?:\.[0-9](?:_?[0-9])*)?         # optional fractional part | 
|  | (?:[eE][+-]?[0-9](?:_?[0-9])*)?  # optional exponent part | 
|  | ) | 
|  | """, | 
|  | flags=re.VERBOSE, | 
|  | ) | 
|  | RE_LOCALTIME = re.compile(_TIME_RE_STR) | 
|  | RE_DATETIME = 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 | 
|  | (?: | 
|  | [Tt ] | 
|  | {_TIME_RE_STR} | 
|  | (?:([Zz])|([+-])([01][0-9]|2[0-3]):([0-5][0-9]))?  # optional time offset | 
|  | )? | 
|  | """, | 
|  | flags=re.VERBOSE, | 
|  | ) | 
|  |  | 
|  |  | 
|  | def match_to_datetime(match: re.Match) -> datetime | date: | 
|  | """Convert a `RE_DATETIME` match to `datetime.datetime` or `datetime.date`. | 
|  |  | 
|  | Raises ValueError if the match does not correspond to a valid date | 
|  | or datetime. | 
|  | """ | 
|  | ( | 
|  | year_str, | 
|  | month_str, | 
|  | day_str, | 
|  | hour_str, | 
|  | minute_str, | 
|  | sec_str, | 
|  | micros_str, | 
|  | zulu_time, | 
|  | offset_sign_str, | 
|  | offset_hour_str, | 
|  | offset_minute_str, | 
|  | ) = match.groups() | 
|  | 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) | 
|  | micros = int(micros_str.ljust(6, "0")) if micros_str else 0 | 
|  | if offset_sign_str: | 
|  | tz: tzinfo | None = cached_tz( | 
|  | offset_hour_str, offset_minute_str, offset_sign_str | 
|  | ) | 
|  | elif zulu_time: | 
|  | tz = timezone.utc | 
|  | else:  # local date-time | 
|  | tz = None | 
|  | return datetime(year, month, day, hour, minute, sec, micros, tzinfo=tz) | 
|  |  | 
|  |  | 
|  | @lru_cache(maxsize=None) | 
|  | def cached_tz(hour_str: str, minute_str: str, sign_str: str) -> timezone: | 
|  | sign = 1 if sign_str == "+" else -1 | 
|  | return timezone( | 
|  | timedelta( | 
|  | hours=sign * int(hour_str), | 
|  | minutes=sign * int(minute_str), | 
|  | ) | 
|  | ) | 
|  |  | 
|  |  | 
|  | def match_to_localtime(match: re.Match) -> time: | 
|  | hour_str, minute_str, sec_str, micros_str = match.groups() | 
|  | micros = int(micros_str.ljust(6, "0")) if micros_str else 0 | 
|  | return time(int(hour_str), int(minute_str), int(sec_str), micros) | 
|  |  | 
|  |  | 
|  | def match_to_number(match: re.Match, parse_float: ParseFloat) -> Any: | 
|  | if match.group("floatpart"): | 
|  | return parse_float(match.group()) | 
|  | return int(match.group(), 0) |