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