|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
|
_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: |
|
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) |
|
|