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