_re.py 2.8 KB

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