content: #
  # Copyright 2023 EA Authors.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #    https://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  #
  # Parts of this file: (c) 2022 Python Software Foundation. All right reserved.
  # - Currently does not support timezones
  # - Timedeltas use a pure-microseconds representations for efficiency, meaning they
  #   have a smaller range (+/- 292,471.2 years) but should be more than enough for
  #   all practical uses
  # License:
  #    1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and
  #    the Individual or Organization ("Licensee") accessing and otherwise using Python
  #    3.10.2 software in source or binary form and its associated documentation.
  #
  #    2. Subject to the terms and conditions of this License Agreement, PSF hereby
  #    grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
  #    analyze, test, perform and/or display publicly, prepare derivative works,
  #    distribute, and otherwise use Python 3.10.2 alone or in any derivative
  #    version, provided, however, that PSF's License Agreement and PSF's notice of
  #    copyright, i.e., "Copyright © 2001-2022 Python Software Foundation; All Rights
  #    Reserved" are retained in Python 3.10.2 alone or in any derivative version
  #    prepared by Licensee.
  #
  #    3. In the event Licensee prepares a derivative work that is based on or
  #    incorporates Python 3.10.2 or any part thereof, and wants to make the
  #    derivative work available to others as provided herein, then Licensee hereby
  #    agrees to include in any such work a brief summary of the changes made to Python
  #    3.10.2.
  #
  #    4. PSF is making Python 3.10.2 available to Licensee on an "AS IS" basis.
  #    PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED.  BY WAY OF
  #    EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR
  #    WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE
  #    USE OF PYTHON 3.10.2 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS.
  #
  #    5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 3.10.2
  #    FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF
  #    MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 3.10.2, OR ANY DERIVATIVE
  #    THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
  #
  #    6. This License Agreement will automatically terminate upon a material breach of
  #    its terms and conditions.
  #
  #    7. Nothing in this License Agreement shall be deemed to create any relationship
  #    of agency, partnership, or joint venture between PSF and Licensee.  This License
  #    Agreement does not grant permission to use PSF trademarks or trade name in a
  #    trademark sense to endorse or promote products or services of Licensee, or any
  #    third party.
  #
  #    8. By copying, installing or otherwise using Python 3.10.2, Licensee agrees
  #    to be bound by the terms and conditions of this License Agreement.
  
  from time import localtime
  from time import struct_time
  
  #############
  # constants #
  #############
  
  MINYEAR = 1
  MAXYEAR = 9999
  MAXORDINAL = 3652059
  MAX_DELTA_DAYS = 999999999
  
  _DI4Y = 1461
  _DI100Y = 36524
  _DI400Y = 146097
  
  _ROUND_HALF_EVEN = 0
  _ROUND_CEILING = 1
  _ROUND_FLOOR = 2
  _ROUND_UP = 3
  
  #############
  # utilities #
  #############
  
  def _signed_add_overflowed(result: int, i: int, j: int) -> bool:
      return ((result ^ i) & (result ^ j)) < 0
  
  def _divmod(x: int, y: int) -> Tuple[int, int]:
      # assert y > 0
      quo = x // y
      r = x - quo * y
      if r < 0:
          quo -= 1
          r += y
      # assert 0 <= r < y
      return quo, r
  
  def _divide_nearest(m: int, n: int) -> int:
      return m // n  # TODO
  
  def _days_in_monthx(i: int) -> int:
      return (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)[i]
  
  def _days_before_monthx(i: int) -> int:
      return (0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334)[i]
  
  def _is_leap(year: int) -> bool:
      return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
  
  def _days_in_month(year: int, month: int) -> int:
      # assert 1 <= month <= 12
      if month == 2 and _is_leap(year):
          return 29
      else:
          return _days_in_monthx(month)
  
  def _days_before_month(year: int, month: int) -> int:
      # assert 1 <= month <= 12
      days = _days_before_monthx(month)
      if month > 2 and _is_leap(year):
          days += 1
      return days
  
  def _days_before_year(year: int) -> int:
      y = year - 1
      # assert year >= 1
      return y * 365 + y // 4 - y // 100 + y // 400
  
  def _ord_to_ymd(ordinal: int) -> Tuple[int, int, int]:
      ordinal -= 1
      n400 = ordinal // _DI400Y
      n = ordinal % _DI400Y
      year = n400 * 400 + 1
  
      n100 = n // _DI100Y
      n = n % _DI100Y
  
      n4 = n // _DI4Y
      n = n % _DI4Y
  
      n1 = n // 365
      n = n % 365
  
      year += n100 * 100 + n4 * 4 + n1
      if n1 == 4 or n100 == 4:
          # assert n == 0
          year -= 1
          return (year, 12, 31)
  
      leapyear = (n1 == 3) and (n4 != 24 or n100 == 3)
      # assert leapyear == is_leap(year)
      month = (n + 50) >> 5
      preceding = _days_before_monthx(month) + int(month > 2 and leapyear)
      if preceding > n:
          month -= 1
          preceding -= _days_in_month(year, month)
      n -= preceding
      # assert 0 <= n
      # assert n < _days_in_month(year, month)
      day = n + 1
      return (year, month, day)
  
  def _ymd_to_ord(year: int, month: int, day: int) -> int:
      return _days_before_year(year) + _days_before_month(year, month) + day
  
  def _weekday(year: int, month: int, day: int) -> int:
      return (_ymd_to_ord(year, month, day) + 6) % 7
  
  def _iso_week1_monday(year: int) -> int:
      first_day = _ymd_to_ord(year, 1, 1)
      first_weekday = (first_day + 6) % 7
      week1_monday = first_day - first_weekday
      if first_weekday > 3:
          week1_monday += 7
      return week1_monday
  
  def _check_delta_day_range(days: int):
      if not (-MAX_DELTA_DAYS <= days <= MAX_DELTA_DAYS):
          raise OverflowError(f"days={days}; must have magnitude <= {MAX_DELTA_DAYS}")
  
  def _check_date_args(year: int, month: int, day: int):
      if not (MINYEAR <= year <= MAXYEAR):
          raise ValueError(f"year {year} is out of range")
      if not (1 <= month <= 12):
          raise ValueError("month must be in 1..12")
      if not (1 <= day <= _days_in_month(year, month)):
          raise ValueError("day is out of range for month")
  
  def _check_time_args(hour: int, minute: int, second: int, microsecond: int):
      if not (0 <= hour <= 23):
          raise ValueError("hour must be in 0..23")
      if not (0 <= minute <= 59):
          raise ValueError("minute must be in 0..59")
      if not (0 <= second <= 59):
          raise ValueError("second must be in 0..59")
      if not (0 <= microsecond <= 999999):
          raise ValueError("microsecond must be in 0..999999")
  
  def _normalize_pair(hi: int, lo: int, factor: int) -> Tuple[int, int]:
      # assert factor > 0
      if lo < 0 or lo >= factor:
          num_hi, lo = _divmod(lo, factor)
          new_hi = hi + num_hi
          # assert not _signed_add_overflowed(new_hi, hi, num_hi)
          hi = new_hi
      # assert 0 <= lo < factor
      return hi, lo
  
  def _normalize_d_s_us(d: int, s: int, us: int) -> Tuple[int, int, int]:
      if us < 0 or us >= 1000000:
          s, us = _normalize_pair(s, us, 1000000)
      if s < 0 or s >= 24 * 3600:
          d, s = _normalize_pair(d, s, 24 * 3600)
      # assert 0 <= s < 24*3600
      # assert 0 <= us < 1000000
      return d, s, us
  
  def _normalize_y_m_d(y: int, m: int, d: int) -> Tuple[int, int, int]:
      def error():
          raise OverflowError("date value out of range")
  
      # assert 1 <= m <= 12
      dim = _days_in_month(y, m)
      if d < 1 or d > dim:
          if d == 0:
              m -= 1
              if m > 0:
                  d = _days_in_month(y, m)
              else:
                  y -= 1
                  m = 12
                  d = 31
          elif d == dim + 1:
              m += 1
              d = 1
              if m > 12:
                  m = 1
                  y += 1
          else:
              ordinal = _ymd_to_ord(y, m, 1) + d - 1
              if ordinal < 1 or ordinal > MAXORDINAL:
                  error()
              else:
                  return _ord_to_ymd(ordinal)
      # assert m > 0
      # assert d > 0
      if not (MINYEAR <= y <= MAXYEAR):
          error()
      return y, m, d
  
  def _normalize_date(year: int, month: int, day: int) -> Tuple[int, int, int]:
      return _normalize_y_m_d(year, month, day)
  
  def _normalize_datetime(
      year: int,
      month: int,
      day: int,
      hour: int,
      minute: int,
      second: int,
      microsecond: int,
  ) -> Tuple[int, int, int, int, int, int, int]:
      second, microsecond = _normalize_pair(second, microsecond, 1000000)
      minute, second = _normalize_pair(minute, second, 60)
      hour, minute = _normalize_pair(hour, minute, 60)
      day, hour = _normalize_pair(day, hour, 24)
      year, month, day = _normalize_date(year, month, day)
      return year, month, day, hour, minute, second, microsecond
  
  
  def _parse_digits(digits: str, num_digits: int) -> Tuple[str, int]:
      if len(digits) < num_digits:
          return "", -1
      p = digits.ptr
      var = 0
      for i in range(num_digits):
          tmp = int(p[0]) - 48  # 48 == '0'
          if not (0 <= tmp <= 9):
              return "", -1
          var *= 10
          var += tmp
          p += 1
      return str(p, len(digits) - num_digits), var
  
  def _isoformat_error(s: str):
      raise ValueError(f"Invalid isoformat string: {s}")
  
  def _parse_isoformat_date(dtstr: str) -> Tuple[int, int, int]:
      p = dtstr
      p, year = _parse_digits(p, 4)
      if year < 0:
          _isoformat_error(dtstr)
  
      if not p or p[0] != "-":
          _isoformat_error(dtstr)
      p = p[1:]
  
      p, month = _parse_digits(p, 2)
      if month < 0:
          _isoformat_error(dtstr)
  
      if not p or p[0] != "-":
          _isoformat_error(dtstr)
      p = p[1:]
  
      p, day = _parse_digits(p, 2)
      if day < 0 or p:
          _isoformat_error(dtstr)
  
      return year, month, day
  
  def _parse_hh_mm_ss_ff(tstr: str) -> Tuple[int, int, int, int]:
      hour, minute, second, microsecond = 0, 0, 0, 0
  
      p = tstr
      for i in range(3):
          p, val = _parse_digits(p, 2)
          if val < 0:
              _isoformat_error(tstr)
  
          if i == 0:
              hour = val
          if i == 1:
              minute = val
          if i == 2:
              second = val
  
          if not p:
              return hour, minute, second, microsecond
          c = p[0]
          p = p[1:]
          if c == ":":
              continue
          elif c == ".":
              break
          else:
              _isoformat_error(tstr)
  
      len_remains = len(p)
      if not (len_remains == 6 or len_remains == 3):
          _isoformat_error(tstr)
  
      p, microsecond = _parse_digits(p, len_remains)
      if microsecond < 0:
          _isoformat_error(tstr)
  
      if len_remains == 3:
          microsecond *= 1000
  
      return hour, minute, second, microsecond
  
  def _parse_isoformat_time(dtstr: str) -> Tuple[int, int, int, int, int, int]:
      n = len(dtstr)
      tzinfo_pos = 0
      tzsign = 0
      while tzinfo_pos < n:
          c = dtstr[tzinfo_pos]
          if c == "+":
              tzsign = 1
              break
          if c == "-":
              tzsign = -1
              break
          tzinfo_pos += 1
  
      hour, minute, second, microsecond = _parse_hh_mm_ss_ff(dtstr[:tzinfo_pos])
      if tzinfo_pos == n:
          return hour, minute, second, microsecond, 0, 0
  
      tzlen = n - tzinfo_pos
      if not (tzlen == 6 or tzlen == 9 or tzlen == 16):
          _isoformat_error(dtstr)
  
      tzhour, tzminute, tzsecond, tzmicrosecond = _parse_hh_mm_ss_ff(
          dtstr[tzinfo_pos + 1 :]
      )
      tzoffset = tzsign * ((tzhour * 3600) + (tzminute * 60) + tzsecond)
      tzmicrosecond *= tzsign
      return hour, minute, second, microsecond, tzoffset, tzmicrosecond
  
  def _format_ctime(
      year: int, month: int, day: int, hours: int, minutes: int, seconds: int
  ) -> str:
      DAY_NAMES = ("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")
      MONTH_NAMES = (
          "Jan",
          "Feb",
          "Mar",
          "Apr",
          "May",
          "Jun",
          "Jul",
          "Aug",
          "Sep",
          "Oct",
          "Nov",
          "Dec",
      )
      wday = _weekday(year, month, day)
      return f"{DAY_NAMES[wday]} {MONTH_NAMES[month - 1]} {str(day).rjust(2)} {str(hours).zfill(2)}:{str(minutes).zfill(2)}:{str(seconds).zfill(2)} {str(year).zfill(4)}"
  
  def _utc_to_seconds(
      year: int, month: int, day: int, hour: int, minute: int, second: int
  ) -> int:
      if year < MINYEAR or year > MAXYEAR:
          raise ValueError(f"year {year} is out of range")
      ordinal = _ymd_to_ord(year, month, day)
      return ((ordinal * 24 + hour) * 60 + minute) * 60 + second
  
  def _round_half_even(x: float) -> float:
      from math import fabs
  
      rounded = x.__round__()
      if fabs(x - rounded) == 0.5:
          rounded = 2.0 * (x / 2.0).__round__()
      return rounded
  
  ################
  # core classes #
  ################
  
  @tuple
  class timedelta:
      min: ClassVar[timedelta] = timedelta._new(-9223372036854775808)
      max: ClassVar[timedelta] = timedelta._new(9223372036854775807)
      resolution: ClassVar[timedelta] = timedelta(microseconds=1)
  
      _microseconds: int
  
      def _new(microseconds: int) -> timedelta:
          return (microseconds,)
  
      @inline
      def _accum(sofar: int, leftover: float, num: int, factor: int) -> Tuple[int, float]:
          sofar += num * factor
          return sofar, leftover
  
      @inline
      def _accum(
          sofar: int, leftover: float, num: float, factor: int
      ) -> Tuple[int, float]:
          from math import modf
  
          fracpart, intpart = modf(num)
          prod = int(intpart) * factor
          s = sofar + prod
  
          if fracpart == 0.0:
              return s, leftover
          dnum = factor * fracpart
          fracpart, intpart = modf(dnum)
          y = s + int(intpart)
          return y, leftover + fracpart
  
      # override default constructor
      def __new__(days: int) -> timedelta:
          return timedelta(days, 0)
  
      def __new__(
          days: float = 0,
          seconds: float = 0,
          microseconds: float = 0,
          milliseconds: float = 0,
          minutes: float = 0,
          hours: float = 0,
          weeks: float = 0,
      ) -> timedelta:
          us = 0
          leftover = 0.0
  
          us, leftover = timedelta._accum(us, leftover, days, 24 * 60 * 60 * 1000000)
          us, leftover = timedelta._accum(us, leftover, seconds, 1000000)
          us, leftover = timedelta._accum(us, leftover, microseconds, 1)
          us, leftover = timedelta._accum(us, leftover, milliseconds, 1000)
          us, leftover = timedelta._accum(us, leftover, minutes, 60 * 1000000)
          us, leftover = timedelta._accum(us, leftover, hours, 60 * 60 * 1000000)
          us, leftover = timedelta._accum(us, leftover, weeks, 7 * 24 * 60 * 60 * 1000000)
  
          if leftover:
              from math import fabs
  
              whole_us = leftover.__round__()
              if fabs(whole_us - leftover) == 0.5:
                  is_odd = us & 1
                  whole_us = 2.0 * ((leftover + is_odd) * 0.5).__round__() - is_odd
              us += int(whole_us)
  
          return (us,)
  
      @property
      def days(self) -> int:
          days, seconds, microseconds = _normalize_d_s_us(0, 0, self._microseconds)
          return days
  
      @property
      def seconds(self) -> int:
          days, seconds, microseconds = _normalize_d_s_us(0, 0, self._microseconds)
          return seconds
  
      @property
      def microseconds(self) -> int:
          days, seconds, microseconds = _normalize_d_s_us(0, 0, self._microseconds)
          return microseconds
  
      def __repr__(self) -> str:
          days, seconds, microseconds = _normalize_d_s_us(0, 0, self._microseconds)
          if days == 0 and seconds == 0 and microseconds == 0:
              return "timedelta(0)"
          v = []
          if days:
              v.append(f"days={days}")
          if seconds:
              v.append(f"seconds={seconds}")
          if microseconds:
              v.append(f"microseconds={microseconds}")
          return f"timedelta({', '.join(v)})"
  
      def __str__(self) -> str:
          days, seconds, us = _normalize_d_s_us(0, 0, self._microseconds)
          minutes, seconds = _divmod(seconds, 60)
          hours, minutes = _divmod(minutes, 60)
  
          if days:
              if us:
                  return f"{days} day{'' if days == 1 or days == -1 else 's'}, {hours}:{str(minutes).zfill(2)}:{str(seconds).zfill(2)}.{str(us).zfill(6)}"
              else:
                  return f"{days} day{'' if days == 1 or days == -1 else 's'}, {hours}:{str(minutes).zfill(2)}:{str(seconds).zfill(2)}"
          else:
              if us:
                  return f"{hours}:{str(minutes).zfill(2)}:{str(seconds).zfill(2)}.{str(us).zfill(6)}"
              else:
                  return f"{hours}:{str(minutes).zfill(2)}:{str(seconds).zfill(2)}"
  
      def __add__(self, other: timedelta) -> timedelta:
          return timedelta._new(self._microseconds + other._microseconds)
  
      def __sub__(self, other: timedelta) -> timedelta:
          return timedelta._new(self._microseconds - other._microseconds)
  
      def __mul__(self, other: int) -> timedelta:
          return timedelta._new(self._microseconds * other)
  
      def __rmul__(self, other: int) -> timedelta:
          return self * other
  
      def __mul__(self, other: float) -> timedelta:
          return timedelta._new(int(_round_half_even(self._microseconds * other)))
  
      def __rmul__(self, other: float) -> timedelta:
          return self * other
  
      def __truediv__(self, other: timedelta) -> float:
          return self._microseconds / other._microseconds
  
      def __truediv__(self, other: float) -> timedelta:
          return timedelta._new(int(_round_half_even(self._microseconds / other)))
  
      def __truediv__(self, other: int) -> timedelta:
          return self / float(other)
  
      def __floordiv__(self, other: timedelta) -> int:
          return int((self._microseconds / other._microseconds).__floor__())
  
      def __floordiv__(self, other: int) -> timedelta:
          return timedelta._new(self._microseconds // other)
  
      def __mod__(self, other: timedelta) -> timedelta:
          n = self._microseconds
          M = other._microseconds
          m = self._microseconds % other._microseconds
          return timedelta._new(((n % M) + M) % M)
  
      def __divmod__(self, other: timedelta) -> Tuple[int, timedelta]:
          return self // other, self % other
  
      def __pos__(self) -> timedelta:
          return self
  
      def __neg__(self) -> timedelta:
          return timedelta._new(-self._microseconds)
  
      def __abs__(self) -> timedelta:
          return timedelta._new(abs(self._microseconds))
  
      def __eq__(self, other: timedelta) -> bool:
          return self._microseconds == other._microseconds
  
      def __ne__(self, other: timedelta) -> bool:
          return self._microseconds != other._microseconds
  
      def __lt__(self, other: timedelta) -> bool:
          return self._microseconds < other._microseconds
  
      def __le__(self, other: timedelta) -> bool:
          return self._microseconds <= other._microseconds
  
      def __gt__(self, other: timedelta) -> bool:
          return self._microseconds > other._microseconds
  
      def __ge__(self, other: timedelta) -> bool:
          return self._microseconds >= other._microseconds
  
      def __bool__(self) -> bool:
          return bool(self._microseconds)
  
      def total_seconds(self) -> float:
          return self._microseconds / 1e6
  
  @tuple
  class IsoCalendarDate:
      year: int
      week: int
      weekday: int
  
      def __repr__(self) -> str:
          return f"IsoCalendarDate(year={self.year}, week={self.week}, weekday={self.weekday})"
  
  @tuple
  class date:
      min: ClassVar[date] = date(MINYEAR, 1, 1)
      max: ClassVar[date] = date(MAXYEAR, 12, 31)
      resolution: ClassVar[timedelta] = timedelta(days=1)
  
      _value: UInt[32]
  
      def __new__(year: int, month: int, day: int) -> date:
          _check_date_args(year, month, day)
          v = (year << 16) | (month << 8) | day
          return date(UInt[32](v))
  
      @property
      def year(self) -> int:
          v = int(self._value)
          return v >> 16
  
      @property
      def month(self) -> int:
          v = int(self._value)
          return (v >> 8) & 0xFF
  
      @property
      def day(self) -> int:
          v = int(self._value)
          return v & 0xFF
  
      def __repr__(self) -> str:
          return f"date(year={self.year}, month={self.month}, day={self.day})"
  
      def today() -> date:
          from time import time as ttime
  
          return date.fromtimestamp(ttime())
  
      def fromtimestamp(timestamp) -> date:
          ts = int(timestamp)
          tm = localtime(ts)
          return date(tm.tm_year, tm.tm_mon, tm.tm_mday)
  
      def fromordinal(ordinal: int) -> date:
          return date(*_ord_to_ymd(ordinal))
  
      def fromisoformat(date_string: str) -> date:
          return date(*_parse_isoformat_date(date_string))
  
      def fromisocalendar(year, week, day) -> date:
          if year < MINYEAR or year > MAXYEAR:
              raise ValueError(f"Year is out of range: {year}")
  
          if week <= 0 or week >= 53:
              out_of_range = True
              if week == 53:
                  first_weekday = _weekday(year, 1, 1)
                  if first_weekday == 3 or (first_weekday == 2 and _is_leap(year)):
                      out_of_range = False
  
              if out_of_range:
                  raise ValueError(f"Invalid week: {week}")
  
          if day <= 0 or day >= 8:
              raise ValueError(f"Invalid day: {day} (range is [1, 7])")
  
          day_1 = _iso_week1_monday(year)
          month = week
          day_offset = (month - 1) * 7 + day - 1
          return date(*_ord_to_ymd(day_1 + day_offset))
  
      def __add__(self, other: timedelta) -> date:
          days, seconds, microseconds = _normalize_d_s_us(0, 0, other._microseconds)
          day = self.day + days
          return date(*_normalize_date(self.year, self.month, day))
  
      def __sub__(self, other: timedelta) -> date:
          days, seconds, microseconds = _normalize_d_s_us(0, 0, other._microseconds)
          day = self.day - days
          return date(*_normalize_date(self.year, self.month, day))
  
      def __sub__(self, other: date) -> timedelta:
          left_ord = _ymd_to_ord(self.year, self.month, self.day)
          right_ord = _ymd_to_ord(other.year, other.month, other.day)
          return timedelta(days=left_ord - right_ord)
  
      def __eq__(self, other: date) -> bool:
          return self._value == other._value
  
      def __ne__(self, other: date) -> bool:
          return self._value != other._value
  
      def __lt__(self, other: date) -> bool:
          return self._value < other._value
  
      def __le__(self, other: date) -> bool:
          return self._value <= other._value
  
      def __gt__(self, other: date) -> bool:
          return self._value > other._value
  
      def __ge__(self, other: date) -> bool:
          return self._value >= other._value
  
      def __bool__(self) -> bool:
          return True
  
      def replace(self, year: int = -1, month: int = -1, day: int = -1) -> date:
          if year == -1:
              year = self.year
          if month == -1:
              month = self.month
          if day == -1:
              day = self.day
          return date(year, month, day)
  
      def timetuple(self) -> struct_time:
          yday = self.toordinal() - date(self.year, 1, 1).toordinal() + 1
          return struct_time(
              self.year, self.month, self.day, 0, 0, 0, self.weekday(), yday, -1
          )
  
      def toordinal(self) -> int:
          return _ymd_to_ord(self.year, self.month, self.day)
  
      def weekday(self) -> int:
          return _weekday(self.year, self.month, self.day)
  
      def isoweekday(self) -> int:
          return self.weekday() + 1
  
      def isocalendar(self) -> IsoCalendarDate:
          year = self.year
          week1_monday = _iso_week1_monday(year)
          today = _ymd_to_ord(year, self.month, self.day)
          week, day = _divmod(today - week1_monday, 7)
          if week < 0:
              year -= 1
              week1_monday = _iso_week1_monday(year)
              week, day = _divmod(today - week1_monday, 7)
          elif week >= 52 and today >= _iso_week1_monday(year + 1):
              year += 1
              week = 0
          return IsoCalendarDate(year, week + 1, day + 1)
  
      def isoformat(self) -> str:
          return f"{str(self.year).zfill(4)}-{str(self.month).zfill(2)}-{str(self.day).zfill(2)}"
  
      def __str__(self) -> str:
          return self.isoformat()
  
      def ctime(self) -> str:
          return _format_ctime(self.year, self.month, self.day, 0, 0, 0)
  
      # strftime() / __format__() not supported
  
  @tuple
  class time:
      min: ClassVar[time] = time(0, 0, 0, 0)
      max: ClassVar[time] = time(23, 59, 59, 999999)
      resolution: ClassVar[timedelta] = timedelta(microseconds=1)
  
      _value: int
  
      def __new__(
          hour: int = 0, minute: int = 0, second: int = 0, microsecond: int = 0
      ) -> time:
          _check_time_args(hour, minute, second, microsecond)
          v = (hour << 40) | (minute << 32) | (second << 24) | microsecond
          return (v,)
  
      @property
      def hour(self) -> int:
          v = self._value
          return v >> 40
  
      @property
      def minute(self) -> int:
          v = self._value
          return (v >> 32) & 0xFF
  
      @property
      def second(self) -> int:
          v = self._value
          return (v >> 24) & 0xFF
  
      @property
      def microsecond(self) -> int:
          v = self._value
          return v & 0xFFFFFF
  
      def __repr__(self) -> str:
          h, m, s, us = self.hour, self.minute, self.second, self.microsecond
          v = []
          v.append(f"hour={h}")
          v.append(f"minute={m}")
          if s or us:
              v.append(f"second={s}")
          if us:
              v.append(f"microsecond={us}")
          return f"time({', '.join(v)})"
  
      def __str__(self) -> str:
          return self.isoformat()
  
      def __bool__(self) -> bool:
          return True
  
      def fromisoformat(time_string: str) -> time:
          (
              hour,
              minute,
              second,
              microsecond,
              tzoffset,
              tzmicrosecond,
          ) = _parse_isoformat_time(time_string)
          # TODO: deal with timezone
          return time(hour, minute, second, microsecond)
  
      def replace(
          self, hour: int = -1, minute: int = -1, second: int = -1, microsecond: int = -1
      ) -> time:
          if hour == -1:
              hour = self.hour
          if second == -1:
              second = self.second
          if minute == -1:
              minute = self.minute
          if microsecond == -1:
              microsecond = self.microsecond
          return time(hour, minute, second, microsecond)
  
      def isoformat(self, timespec: Static[str] = "auto") -> str:
          hh = str(self.hour).zfill(2)
          mm = str(self.minute).zfill(2)
          ss = str(self.second).zfill(2)
          us = str(self.microsecond).zfill(6)
          ms = str(self.microsecond // 1000).zfill(3)
  
          if timespec == "auto":
              if self.microsecond:
                  return f"{hh}:{mm}:{ss}.{us}"
              else:
                  return f"{hh}:{mm}:{ss}"
          elif timespec == "hours":
              return hh
          elif timespec == "minutes":
              return f"{hh}:{mm}"
          elif timespec == "seconds":
              return f"{hh}:{mm}:{ss}"
          elif timespec == "milliseconds":
              return f"{hh}:{mm}:{ss}.{ms}"
          elif timespec == "microseconds":
              return f"{hh}:{mm}:{ss}.{us}"
          else:
              compile_error(
                  "invalid timespec; valid ones are 'auto', 'hours', 'minutes', 'seconds', 'milliseconds' and 'microseconds'"
              )
  
  @tuple
  class datetime:
      min: ClassVar[datetime] = datetime(MINYEAR, 1, 1)
      max: ClassVar[datetime] = datetime(MAXYEAR, 12, 31, 23, 59, 59, 999999)
      resolution: ClassVar[timedelta] = timedelta(microseconds=1)
  
      _time: time
      _date: date
  
      def __new__(
          year: int,
          month: int,
          day: int,
          hour: int = 0,
          minute: int = 0,
          second: int = 0,
          microsecond: int = 0,
      ) -> datetime:
          return datetime(time(hour, minute, second, microsecond), date(year, month, day))
  
      def date(self) -> date:
          return self._date
  
      def time(self) -> time:
          return self._time
  
      @property
      def year(self) -> int:
          return self.date().year
  
      @property
      def month(self) -> int:
          return self.date().month
  
      @property
      def day(self) -> int:
          return self.date().day
  
      @property
      def hour(self) -> int:
          return self.time().hour
  
      @property
      def minute(self) -> int:
          return self.time().minute
  
      @property
      def second(self) -> int:
          return self.time().second
  
      @property
      def microsecond(self) -> int:
          return self.time().microsecond
  
      def __repr__(self) -> str:
          return f"datetime(year={self.year}, month={self.month}, day={self.day}, hour={self.hour}, minute={self.minute}, second={self.second}, microsecond={self.microsecond})"
  
      def __str__(self) -> str:
          return self.isoformat(sep=" ")
  
      def _from_timet_and_us(timet, us) -> datetime:
          tm = localtime(timet)
          year = tm.tm_year
          month = tm.tm_mon
          day = tm.tm_mday
          hour = tm.tm_hour
          minute = tm.tm_min
          second = min(59, tm.tm_sec)
          # TODO: timezone adjustments
          return datetime(year, month, day, hour, minute, second, us)
  
      def today() -> datetime:
          from time import time as ttime
  
          return datetime.fromtimestamp(ttime())
  
      # TODO: support timezone
      def now() -> datetime:
          return datetime.today()
  
      def utcnow() -> datetime:
          return datetime.now()
  
      # TODO: support timezone
      def fromtimestamp(timestamp) -> datetime:
          from time import _time_to_timeval, _ROUND_HALF_EVEN
  
          timet, us = _time_to_timeval(float(timestamp), _ROUND_HALF_EVEN)
          return datetime._from_timet_and_us(timet, us)
  
      def utcfromtimestamp(timestamp) -> datetime:
          return datetime.fromtimestamp(timestamp)
  
      def fromordinal(ordinal: int) -> datetime:
          return datetime.combine(date.fromordinal(ordinal), time())
  
      # TODO: support timezone
      def combine(date: date, time: time) -> datetime:
          return datetime(time, date)
  
      def fromisoformat(date_string: str) -> datetime:
          time_string = "" if len(date_string) < 10 else date_string[:10]
          year, month, day = _parse_isoformat_date(time_string)
          if len(date_string) == 10:
              return datetime(year=year, month=month, day=day)
          date_string = "" if len(date_string) < 12 else date_string[11:]
          hour, minute, second, microsecond = _parse_hh_mm_ss_ff(date_string)
          return datetime(
              year=year,
              month=month,
              day=day,
              hour=hour,
              minute=minute,
              second=second,
              microsecond=microsecond,
          )
  
      def fromisocalendar(year: int, week: int, day: int) -> datetime:
          return datetime.combine(date.fromisocalendar(year, week, day), time())
  
      def __add__(self, other: timedelta) -> datetime:
          td_days, td_seconds, td_microseconds = _normalize_d_s_us(
              0, 0, other._microseconds
          )
          year = self.year
          month = self.month
          day = self.day + td_days
          hour = self.hour
          minute = self.minute
          second = self.second + td_seconds
          microsecond = self.microsecond + td_microseconds
          return datetime(
              *_normalize_datetime(year, month, day, hour, minute, second, microsecond)
          )
  
      def __sub__(self, other: timedelta) -> datetime:
          td_days, td_seconds, td_microseconds = _normalize_d_s_us(
              0, 0, other._microseconds
          )
          year = self.year
          month = self.month
          day = self.day - td_days
          hour = self.hour
          minute = self.minute
          second = self.second - td_seconds
          microsecond = self.microsecond - td_microseconds
          return datetime(
              *_normalize_datetime(year, month, day, hour, minute, second, microsecond)
          )
  
      def __sub__(self, other: datetime) -> timedelta:
          delta_d = _ymd_to_ord(self.year, self.month, self.day) - _ymd_to_ord(
              other.year, other.month, other.day
          )
          delta_s = (
              (self.hour - other.hour) * 3600
              + (self.minute - other.minute) * 60
              + (self.second - other.second)
          )
          delta_us = self.microsecond - other.microsecond
          return timedelta(days=delta_d, seconds=delta_s, microseconds=delta_us)
  
      def __eq__(self, other: datetime) -> bool:
          return self.date() == other.date() and self.time() == other.time()
  
      def __ne__(self, other: datetime) -> bool:
          return not (self == other)
  
      def __lt__(self, other: datetime) -> bool:
          return (self.date(), self.time()) < (other.date(), other.time())
  
      def __le__(self, other: datetime) -> bool:
          return (self.date(), self.time()) <= (other.date(), other.time())
  
      def __gt__(self, other: datetime) -> bool:
          return (self.date(), self.time()) > (other.date(), other.time())
  
      def __ge__(self, other: datetime) -> bool:
          return (self.date(), self.time()) >= (other.date(), other.time())
  
      def __bool__(self) -> bool:
          return True
  
      def replace(
          self,
          year: int = -1,
          month: int = -1,
          day: int = -1,
          hour: int = -1,
          minute: int = -1,
          second: int = -1,
          microsecond: int = -1,
      ) -> datetime:
          return datetime(
              self.time().replace(hour, minute, second, microsecond),
              self.date().replace(year, month, day),
          )
  
      def timetuple(self) -> struct_time:
          yday = self.toordinal() - date(self.year, 1, 1).toordinal() + 1
          return struct_time(
              self.year,
              self.month,
              self.day,
              self.hour,
              self.minute,
              self.second,
              self.weekday(),
              yday,
              -1,
          )
  
      def utctimetuple(self) -> struct_time:
          return self.timetuple()
  
      def toordinal(self) -> int:
          return self.date().toordinal()
  
      def timestamp(self) -> float:
          return (self - datetime(1970, 1, 1)).total_seconds()
  
      def weekday(self) -> int:
          return self.date().weekday()
  
      def isoweekday(self) -> int:
          return self.date().isoweekday()
  
      def isocalendar(self) -> IsoCalendarDate:
          return self.date().isocalendar()
  
      def isoformat(self, sep: str = "T", timespec: Static[str] = "auto") -> str:
          date_part = str(self.date())
          time_part = self.time().isoformat(timespec=timespec)
          return f"{date_part}{sep}{time_part}"
  
      def ctime(self) -> str:
          date = self.date()
          time = self.time()
          return _format_ctime(
              date.year, date.month, date.day, time.hour, time.minute, time.second
          )
  
  @extend
  class timedelta:
      def __add__(self, other: date) -> date:
          return other + self
  
      def __add__(self, other: datetime) -> datetime:
          return other + self
  