50761
|
1 |
from datetime import date, datetime, time, timedelta, timezone, tzinfo
|
|
2 |
from functools import lru_cache
|
|
3 |
import re
|
|
4 |
from typing import Any, Optional, Union
|
|
5 |
|
|
6 |
from ._types import ParseFloat
|
|
7 |
|
|
8 |
# E.g.
|
|
9 |
# - 00:32:00.999999
|
|
10 |
# - 00:32:00
|
|
11 |
_TIME_RE_STR = r"([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])(?:\.([0-9]{1,6})[0-9]*)?"
|
|
12 |
|
|
13 |
RE_NUMBER = re.compile(
|
|
14 |
r"""
|
|
15 |
0
|
|
16 |
(?:
|
|
17 |
x[0-9A-Fa-f](?:_?[0-9A-Fa-f])* # hex
|
|
18 |
|
|
|
19 |
b[01](?:_?[01])* # bin
|
|
20 |
|
|
|
21 |
o[0-7](?:_?[0-7])* # oct
|
|
22 |
)
|
|
23 |
|
|
|
24 |
[+-]?(?:0|[1-9](?:_?[0-9])*) # dec, integer part
|
|
25 |
(?P<floatpart>
|
|
26 |
(?:\.[0-9](?:_?[0-9])*)? # optional fractional part
|
|
27 |
(?:[eE][+-]?[0-9](?:_?[0-9])*)? # optional exponent part
|
|
28 |
)
|
|
29 |
""",
|
|
30 |
flags=re.VERBOSE,
|
|
31 |
)
|
|
32 |
RE_LOCALTIME = re.compile(_TIME_RE_STR)
|
|
33 |
RE_DATETIME = re.compile(
|
|
34 |
fr"""
|
|
35 |
([0-9]{{4}})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]) # date, e.g. 1988-10-27
|
|
36 |
(?:
|
|
37 |
[Tt ]
|
|
38 |
{_TIME_RE_STR}
|
|
39 |
(?:([Zz])|([+-])([01][0-9]|2[0-3]):([0-5][0-9]))? # optional time offset
|
|
40 |
)?
|
|
41 |
""",
|
|
42 |
flags=re.VERBOSE,
|
|
43 |
)
|
|
44 |
|
|
45 |
|
|
46 |
def match_to_datetime(match: "re.Match") -> Union[datetime, date]:
|
|
47 |
"""Convert a `RE_DATETIME` match to `datetime.datetime` or `datetime.date`.
|
|
48 |
|
|
49 |
Raises ValueError if the match does not correspond to a valid date
|
|
50 |
or datetime.
|
|
51 |
"""
|
|
52 |
(
|
|
53 |
year_str,
|
|
54 |
month_str,
|
|
55 |
day_str,
|
|
56 |
hour_str,
|
|
57 |
minute_str,
|
|
58 |
sec_str,
|
|
59 |
micros_str,
|
|
60 |
zulu_time,
|
|
61 |
offset_sign_str,
|
|
62 |
offset_hour_str,
|
|
63 |
offset_minute_str,
|
|
64 |
) = match.groups()
|
|
65 |
year, month, day = int(year_str), int(month_str), int(day_str)
|
|
66 |
if hour_str is None:
|
|
67 |
return date(year, month, day)
|
|
68 |
hour, minute, sec = int(hour_str), int(minute_str), int(sec_str)
|
|
69 |
micros = int(micros_str.ljust(6, "0")) if micros_str else 0
|
|
70 |
if offset_sign_str:
|
|
71 |
tz: Optional[tzinfo] = cached_tz(
|
|
72 |
offset_hour_str, offset_minute_str, offset_sign_str
|
|
73 |
)
|
|
74 |
elif zulu_time:
|
|
75 |
tz = timezone.utc
|
|
76 |
else: # local date-time
|
|
77 |
tz = None
|
|
78 |
return datetime(year, month, day, hour, minute, sec, micros, tzinfo=tz)
|
|
79 |
|
|
80 |
|
|
81 |
@lru_cache(maxsize=None)
|
|
82 |
def cached_tz(hour_str: str, minute_str: str, sign_str: str) -> timezone:
|
|
83 |
sign = 1 if sign_str == "+" else -1
|
|
84 |
return timezone(
|
|
85 |
timedelta(
|
|
86 |
hours=sign * int(hour_str),
|
|
87 |
minutes=sign * int(minute_str),
|
|
88 |
)
|
|
89 |
)
|
|
90 |
|
|
91 |
|
|
92 |
def match_to_localtime(match: "re.Match") -> time:
|
|
93 |
hour_str, minute_str, sec_str, micros_str = match.groups()
|
|
94 |
micros = int(micros_str.ljust(6, "0")) if micros_str else 0
|
|
95 |
return time(int(hour_str), int(minute_str), int(sec_str), micros)
|
|
96 |
|
|
97 |
|
|
98 |
def match_to_number(match: "re.Match", parse_float: "ParseFloat") -> Any:
|
|
99 |
if match.group("floatpart"):
|
|
100 |
return parse_float(match.group())
|
|
101 |
return int(match.group(), 0)
|