|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_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])*)" |
|
|
+ r"(?:\.[0-9](?:_?[0-9])*)?" |
|
|
+ r"(?:[eE][+-]?[0-9](?:_?[0-9])*)?" |
|
|
) |
|
|
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])" |
|
|
+ r"(?:" |
|
|
+ r"[T ]" |
|
|
+ _TIME_RE_STR |
|
|
+ r"(?:(Z)|([+-])([01][0-9]|2[0-3]):([0-5][0-9]))?" |
|
|
+ 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: |
|
|
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) |
|
|
|