from datetime import date, datetime, time, timedelta, timezone, tzinfo import re from typing import TYPE_CHECKING, Any, Optional, Union if TYPE_CHECKING: from re import Match from pip._vendor.tomli._parser 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]+)?" RE_HEX = re.compile(r"[0-9A-Fa-f](?:_?[0-9A-Fa-f])*") RE_BIN = re.compile(r"[01](?:_?[01])*") RE_OCT = re.compile(r"[0-7](?:_?[0-7])*") RE_NUMBER = re.compile( r"[+-]?(?:0|[1-9](?:_?[0-9])*)" # integer + r"(?:\.[0-9](?:_?[0-9])*)?" # optional fractional part + r"(?:[eE][+-]?[0-9](?:_?[0-9])*)?" # optional exponent part ) RE_LOCALTIME = re.compile(_TIME_RE_STR) RE_DATETIME = re.compile( r"([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|1[0-9]|2[0-9]|3[01])" # date, e.g. 1988-10-27 + r"(?:" + r"[T ]" + _TIME_RE_STR + r"(?:(Z)|([+-])([01][0-9]|2[0-3]):([0-5][0-9]))?" # time offset + r")?" ) def match_to_datetime(match: "Match") -> Union[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_dir_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[1:].ljust(6, "0")[:6]) if micros_str else 0 if offset_dir_str: offset_dir = 1 if offset_dir_str == "+" else -1 tz: Optional[tzinfo] = timezone( timedelta( hours=offset_dir * int(offset_hour_str), minutes=offset_dir * int(offset_minute_str), ) ) elif zulu_time: tz = timezone.utc else: # local date-time tz = None return datetime(year, month, day, hour, minute, sec, micros, tzinfo=tz) def match_to_localtime(match: "Match") -> time: hour_str, minute_str, sec_str, micros_str = match.groups() micros = int(micros_str[1:].ljust(6, "0")[:6]) if micros_str else 0 return time(int(hour_str), int(minute_str), int(sec_str), micros) def match_to_number(match: "Match", parse_float: "ParseFloat") -> Any: match_str = match.group() if "." in match_str or "e" in match_str or "E" in match_str: return parse_float(match_str) return int(match_str)