/ adafruit_datetime.py
adafruit_datetime.py
   1  # SPDX-FileCopyrightText: 2001-2021 Python Software Foundation.All rights reserved.
   2  # SPDX-FileCopyrightText: 2000 BeOpen.com. All rights reserved.
   3  # SPDX-FileCopyrightText: 1995-2001 Corporation for National Research Initiatives.
   4  #                         All rights reserved.
   5  # SPDX-FileCopyrightText: 1995-2001 Corporation for National Research Initiatives.
   6  #                         All rights reserved.
   7  # SPDX-FileCopyrightText: 1991-1995 Stichting Mathematisch Centrum. All rights reserved.
   8  # SPDX-FileCopyrightText: 2017 Paul Sokolovsky
   9  # SPDX-License-Identifier: Python-2.0
  10  
  11  """
  12  `adafruit_datetime`
  13  ================================================================================
  14  Concrete date/time and related types.
  15  
  16  See http://www.iana.org/time-zones/repository/tz-link.html for
  17  time zone and DST data sources.
  18  
  19  Implementation Notes
  20  --------------------
  21  
  22  **Software and Dependencies:**
  23  
  24  * Adafruit CircuitPython firmware for the supported boards:
  25    https://github.com/adafruit/circuitpython/releases
  26  
  27  
  28  """
  29  # pylint: disable=too-many-lines
  30  import time as _time
  31  import math as _math
  32  import re as _re
  33  from micropython import const
  34  
  35  __version__ = "0.0.0-auto.0"
  36  __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DateTime.git"
  37  
  38  # Constants
  39  MINYEAR = const(1)
  40  MAXYEAR = const(9999)
  41  _MAXORDINAL = const(3652059)
  42  _DI400Y = const(146097)
  43  _DI100Y = const(36524)
  44  _DI4Y = const(1461)
  45  # https://svn.python.org/projects/sandbox/trunk/datetime/datetime.py
  46  _DAYS_IN_MONTH = (None, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
  47  _DAYS_BEFORE_MONTH = (None, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334)
  48  # Month and day names.  For localized versions, see the calendar module.
  49  _MONTHNAMES = (
  50      None,
  51      "Jan",
  52      "Feb",
  53      "Mar",
  54      "Apr",
  55      "May",
  56      "Jun",
  57      "Jul",
  58      "Aug",
  59      "Sep",
  60      "Oct",
  61      "Nov",
  62      "Dec",
  63  )
  64  _DAYNAMES = (None, "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")
  65  
  66  _INVALID_ISO_ERROR = "Invalid isoformat string: '{}'"
  67  
  68  # Utility functions - universal
  69  def _cmp(obj_x, obj_y):
  70      return 0 if obj_x == obj_y else 1 if obj_x > obj_y else -1
  71  
  72  
  73  def _cmperror(obj_x, obj_y):
  74      raise TypeError(
  75          "can't compare '%s' to '%s'" % (type(obj_x).__name__, type(obj_y).__name__)
  76      )
  77  
  78  
  79  # Utility functions - time
  80  def _check_time_fields(hour, minute, second, microsecond, fold):
  81      if not isinstance(hour, int):
  82          raise TypeError("Hour expected as int")
  83      if not 0 <= hour <= 23:
  84          raise ValueError("hour must be in 0..23", hour)
  85      if not 0 <= minute <= 59:
  86          raise ValueError("minute must be in 0..59", minute)
  87      if not 0 <= second <= 59:
  88          raise ValueError("second must be in 0..59", second)
  89      if not 0 <= microsecond <= 999999:
  90          raise ValueError("microsecond must be in 0..999999", microsecond)
  91      if fold not in (0, 1):  # from CPython API
  92          raise ValueError("fold must be either 0 or 1", fold)
  93  
  94  
  95  def _check_utc_offset(name, offset):
  96      assert name in ("utcoffset", "dst")
  97      if offset is None:
  98          return
  99      if not isinstance(offset, timedelta):
 100          raise TypeError(
 101              "tzinfo.%s() must return None "
 102              "or timedelta, not '%s'" % (name, type(offset))
 103          )
 104      if offset % timedelta(minutes=1) or offset.microseconds:
 105          raise ValueError(
 106              "tzinfo.%s() must return a whole number "
 107              "of minutes, got %s" % (name, offset)
 108          )
 109      if not -timedelta(1) < offset < timedelta(1):
 110          raise ValueError(
 111              "%s()=%s, must be must be strictly between"
 112              " -timedelta(hours=24) and timedelta(hours=24)" % (name, offset)
 113          )
 114  
 115  
 116  # pylint: disable=invalid-name
 117  def _format_offset(off):
 118      s = ""
 119      if off is not None:
 120          if off.days < 0:
 121              sign = "-"
 122              off = -off
 123          else:
 124              sign = "+"
 125          hh, mm = divmod(off, timedelta(hours=1))
 126          mm, ss = divmod(mm, timedelta(minutes=1))
 127          s += "%s%02d:%02d" % (sign, hh, mm)
 128          if ss or ss.microseconds:
 129              s += ":%02d" % ss.seconds
 130  
 131              if ss.microseconds:
 132                  s += ".%06d" % ss.microseconds
 133      return s
 134  
 135  
 136  # Utility functions - timezone
 137  def _check_tzname(name):
 138      """"Just raise TypeError if the arg isn't None or a string."""
 139      if name is not None and not isinstance(name, str):
 140          raise TypeError(
 141              "tzinfo.tzname() must return None or string, " "not '%s'" % type(name)
 142          )
 143  
 144  
 145  def _check_tzinfo_arg(time_zone):
 146      if time_zone is not None and not isinstance(time_zone, tzinfo):
 147          raise TypeError("tzinfo argument must be None or of a tzinfo subclass")
 148  
 149  
 150  # Utility functions - date
 151  def _is_leap(year):
 152      "year -> 1 if leap year, else 0."
 153      return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
 154  
 155  
 156  def _days_in_month(year, month):
 157      "year, month -> number of days in that month in that year."
 158      assert 1 <= month <= 12, month
 159      if month == 2 and _is_leap(year):
 160          return 29
 161      return _DAYS_IN_MONTH[month]
 162  
 163  
 164  def _check_date_fields(year, month, day):
 165      if not isinstance(year, int):
 166          raise TypeError("int expected")
 167      if not MINYEAR <= year <= MAXYEAR:
 168          raise ValueError("year must be in %d..%d" % (MINYEAR, MAXYEAR), year)
 169      if not 1 <= month <= 12:
 170          raise ValueError("month must be in 1..12", month)
 171      dim = _days_in_month(year, month)
 172      if not 1 <= day <= dim:
 173          raise ValueError("day must be in 1..%d" % dim, day)
 174  
 175  
 176  def _days_before_month(year, month):
 177      "year, month -> number of days in year preceding first day of month."
 178      assert 1 <= month <= 12, "month must be in 1..12"
 179      return _DAYS_BEFORE_MONTH[month] + (month > 2 and _is_leap(year))
 180  
 181  
 182  def _days_before_year(year):
 183      "year -> number of days before January 1st of year."
 184      year = year - 1
 185      return year * 365 + year // 4 - year // 100 + year // 400
 186  
 187  
 188  def _ymd2ord(year, month, day):
 189      "year, month, day -> ordinal, considering 01-Jan-0001 as day 1."
 190      assert 1 <= month <= 12, "month must be in 1..12"
 191      dim = _days_in_month(year, month)
 192      assert 1 <= day <= dim, "day must be in 1..%d" % dim
 193      return _days_before_year(year) + _days_before_month(year, month) + day
 194  
 195  
 196  # pylint: disable=too-many-arguments
 197  def _build_struct_time(tm_year, tm_month, tm_mday, tm_hour, tm_min, tm_sec, tm_isdst):
 198      tm_wday = (_ymd2ord(tm_year, tm_month, tm_mday) + 6) % 7
 199      tm_yday = _days_before_month(tm_year, tm_month) + tm_mday
 200      return _time.struct_time(
 201          (
 202              tm_year,
 203              tm_month,
 204              tm_mday,
 205              tm_hour,
 206              tm_min,
 207              tm_sec,
 208              tm_wday,
 209              tm_yday,
 210              tm_isdst,
 211          )
 212      )
 213  
 214  
 215  # pylint: disable=invalid-name
 216  def _format_time(hh, mm, ss, us, timespec="auto"):
 217      if timespec != "auto":
 218          raise NotImplementedError("Only default timespec supported")
 219      if us:
 220          spec = "{:02d}:{:02d}:{:02d}.{:06d}"
 221      else:
 222          spec = "{:02d}:{:02d}:{:02d}"
 223      fmt = spec
 224      return fmt.format(hh, mm, ss, us)
 225  
 226  
 227  # A 4-year cycle has an extra leap day over what we'd get from pasting
 228  # together 4 single years.
 229  assert _DI4Y == 4 * 365 + 1
 230  
 231  # Similarly, a 400-year cycle has an extra leap day over what we'd get from
 232  # pasting together 4 100-year cycles.
 233  assert _DI400Y == 4 * _DI100Y + 1
 234  
 235  # OTOH, a 100-year cycle has one fewer leap day than we'd get from
 236  # pasting together 25 4-year cycles.
 237  assert _DI100Y == 25 * _DI4Y - 1
 238  
 239  
 240  def _ord2ymd(n):
 241      "ordinal -> (year, month, day), considering 01-Jan-0001 as day 1."
 242  
 243      # n is a 1-based index, starting at 1-Jan-1.  The pattern of leap years
 244      # repeats exactly every 400 years.  The basic strategy is to find the
 245      # closest 400-year boundary at or before n, then work with the offset
 246      # from that boundary to n.  Life is much clearer if we subtract 1 from
 247      # n first -- then the values of n at 400-year boundaries are exactly
 248      # those divisible by _DI400Y:
 249      #
 250      #     D  M   Y            n              n-1
 251      #     -- --- ----        ----------     ----------------
 252      #     31 Dec -400        -_DI400Y       -_DI400Y -1
 253      #      1 Jan -399         -_DI400Y +1   -_DI400Y      400-year boundary
 254      #     ...
 255      #     30 Dec  000        -1             -2
 256      #     31 Dec  000         0             -1
 257      #      1 Jan  001         1              0            400-year boundary
 258      #      2 Jan  001         2              1
 259      #      3 Jan  001         3              2
 260      #     ...
 261      #     31 Dec  400         _DI400Y        _DI400Y -1
 262      #      1 Jan  401         _DI400Y +1     _DI400Y      400-year boundary
 263      n -= 1
 264      n400, n = divmod(n, _DI400Y)
 265      year = n400 * 400 + 1  # ..., -399, 1, 401, ...
 266  
 267      # Now n is the (non-negative) offset, in days, from January 1 of year, to
 268      # the desired date.  Now compute how many 100-year cycles precede n.
 269      # Note that it's possible for n100 to equal 4!  In that case 4 full
 270      # 100-year cycles precede the desired day, which implies the desired
 271      # day is December 31 at the end of a 400-year cycle.
 272      n100, n = divmod(n, _DI100Y)
 273  
 274      # Now compute how many 4-year cycles precede it.
 275      n4, n = divmod(n, _DI4Y)
 276  
 277      # And now how many single years.  Again n1 can be 4, and again meaning
 278      # that the desired day is December 31 at the end of the 4-year cycle.
 279      n1, n = divmod(n, 365)
 280  
 281      year += n100 * 100 + n4 * 4 + n1
 282      if n1 == 4 or n100 == 4:
 283          assert n == 0
 284          return year - 1, 12, 31
 285  
 286      # Now the year is correct, and n is the offset from January 1.  We find
 287      # the month via an estimate that's either exact or one too large.
 288      leapyear = n1 == 3 and (n4 != 24 or n100 == 3)
 289      assert leapyear == _is_leap(year)
 290      month = (n + 50) >> 5
 291      preceding = _DAYS_BEFORE_MONTH[month] + (month > 2 and leapyear)
 292      if preceding > n:  # estimate is too large
 293          month -= 1
 294          preceding -= _DAYS_IN_MONTH[month] + (month == 2 and leapyear)
 295      n -= preceding
 296      assert 0 <= n < _days_in_month(year, month)
 297  
 298      # Now the year and month are correct, and n is the offset from the
 299      # start of that month:  we're done!
 300      return year, month, n + 1
 301  
 302  
 303  class timedelta:
 304      """A timedelta object represents a duration, the difference between two dates or times."""
 305  
 306      # pylint: disable=too-many-arguments, too-many-locals, too-many-statements
 307      def __new__(
 308          cls,
 309          days=0,
 310          seconds=0,
 311          microseconds=0,
 312          milliseconds=0,
 313          minutes=0,
 314          hours=0,
 315          weeks=0,
 316      ):
 317  
 318          # Check that all inputs are ints or floats.
 319          if not all(
 320              isinstance(i, (int, float))
 321              for i in [days, seconds, microseconds, milliseconds, minutes, hours, weeks]
 322          ):
 323              raise TypeError("Kwargs to this function must be int or float.")
 324  
 325          # Final values, all integer.
 326          # s and us fit in 32-bit signed ints; d isn't bounded.
 327          d = s = us = 0
 328  
 329          # Normalize everything to days, seconds, microseconds.
 330          days += weeks * 7
 331          seconds += minutes * 60 + hours * 3600
 332          microseconds += milliseconds * 1000
 333  
 334          # Get rid of all fractions, and normalize s and us.
 335          if isinstance(days, float):
 336              dayfrac, days = _math.modf(days)
 337              daysecondsfrac, daysecondswhole = _math.modf(dayfrac * (24.0 * 3600.0))
 338              assert daysecondswhole == int(daysecondswhole)  # can't overflow
 339              s = int(daysecondswhole)
 340              assert days == int(days)
 341              d = int(days)
 342          else:
 343              daysecondsfrac = 0.0
 344              d = days
 345          assert isinstance(daysecondsfrac, float)
 346          assert abs(daysecondsfrac) <= 1.0
 347          assert isinstance(d, int)
 348          assert abs(s) <= 24 * 3600
 349          # days isn't referenced again before redefinition
 350  
 351          if isinstance(seconds, float):
 352              secondsfrac, seconds = _math.modf(seconds)
 353              assert seconds == int(seconds)
 354              seconds = int(seconds)
 355              secondsfrac += daysecondsfrac
 356              assert abs(secondsfrac) <= 2.0
 357          else:
 358              secondsfrac = daysecondsfrac
 359          # daysecondsfrac isn't referenced again
 360          assert isinstance(secondsfrac, float)
 361          assert abs(secondsfrac) <= 2.0
 362  
 363          assert isinstance(seconds, int)
 364          days, seconds = divmod(seconds, 24 * 3600)
 365          d += days
 366          s += int(seconds)  # can't overflow
 367          assert isinstance(s, int)
 368          assert abs(s) <= 2 * 24 * 3600
 369          # seconds isn't referenced again before redefinition
 370  
 371          usdouble = secondsfrac * 1e6
 372          assert abs(usdouble) < 2.1e6  # exact value not critical
 373          # secondsfrac isn't referenced again
 374  
 375          if isinstance(microseconds, float):
 376              microseconds = round(microseconds + usdouble)
 377              seconds, microseconds = divmod(microseconds, 1000000)
 378              days, seconds = divmod(seconds, 24 * 3600)
 379              d += days
 380              s += seconds
 381          else:
 382              microseconds = int(microseconds)
 383              seconds, microseconds = divmod(microseconds, 1000000)
 384              days, seconds = divmod(seconds, 24 * 3600)
 385              d += days
 386              s += seconds
 387              microseconds = round(microseconds + usdouble)
 388          assert isinstance(s, int)
 389          assert isinstance(microseconds, int)
 390          assert abs(s) <= 3 * 24 * 3600
 391          assert abs(microseconds) < 3.1e6
 392  
 393          # Just a little bit of carrying possible for microseconds and seconds.
 394          seconds, us = divmod(microseconds, 1000000)
 395          s += seconds
 396          days, s = divmod(s, 24 * 3600)
 397          d += days
 398  
 399          assert isinstance(d, int)
 400          assert isinstance(s, int) and 0 <= s < 24 * 3600
 401          assert isinstance(us, int) and 0 <= us < 1000000
 402  
 403          if abs(d) > 999999999:
 404              raise OverflowError("timedelta # of days is too large: %d" % d)
 405  
 406          self = object.__new__(cls)
 407          self._days = d
 408          self._seconds = s
 409          self._microseconds = us
 410          self._hashcode = -1
 411          return self
 412  
 413      # Instance attributes (read-only)
 414      @property
 415      def days(self):
 416          """Days, Between -999999999 and 999999999 inclusive"""
 417          return self._days
 418  
 419      @property
 420      def seconds(self):
 421          """Seconds, Between 0 and 86399 inclusive"""
 422          return self._seconds
 423  
 424      @property
 425      def microseconds(self):
 426          """Microseconds, Between 0 and 999999 inclusive"""
 427          return self._microseconds
 428  
 429      # Instance methods
 430      def total_seconds(self):
 431          """Return the total number of seconds contained in the duration."""
 432          # If the duration is less than a threshold duration, and microseconds
 433          # is nonzero, then the result is a float.  Otherwise, the result is a
 434          # (possibly long) integer.  This differs from standard Python where the
 435          # result is always a float, because the precision of CircuitPython
 436          # floats is considerably smaller than on standard Python.
 437          seconds = self._days * 86400 + self._seconds
 438          if self._microseconds != 0 and abs(seconds) < (1 << 21):
 439              seconds += self._microseconds / 10 ** 6
 440          return seconds
 441  
 442      def __repr__(self):
 443          args = []
 444          if self._days:
 445              args.append("days=%d" % self._days)
 446          if self._seconds:
 447              args.append("seconds=%d" % self._seconds)
 448          if self._microseconds:
 449              args.append("microseconds=%d" % self._microseconds)
 450          if not args:
 451              args.append("0")
 452          return "%s.%s(%s)" % (
 453              self.__class__.__module__,
 454              self.__class__.__qualname__,
 455              ", ".join(args),
 456          )
 457  
 458      def __str__(self):
 459          mm, ss = divmod(self._seconds, 60)
 460          hh, mm = divmod(mm, 60)
 461          s = "%d:%02d:%02d" % (hh, mm, ss)
 462          if self._days:
 463  
 464              def plural(n):
 465                  return n, abs(n) != 1 and "s" or ""
 466  
 467              s = ("%d day%s, " % plural(self._days)) + s
 468          if self._microseconds:
 469              s = s + ".%06d" % self._microseconds
 470          return s
 471  
 472      # Supported operations
 473      def __neg__(self):
 474          return timedelta(-self._days, -self._seconds, -self._microseconds)
 475  
 476      def __add__(self, other):
 477          if isinstance(other, timedelta):
 478              return timedelta(
 479                  self._days + other._days,
 480                  self._seconds + other._seconds,
 481                  self._microseconds + other._microseconds,
 482              )
 483          return NotImplemented
 484  
 485      def __sub__(self, other):
 486          if isinstance(other, timedelta):
 487              return timedelta(
 488                  self._days - other._days,
 489                  self._seconds - other._seconds,
 490                  self._microseconds - other._microseconds,
 491              )
 492          return NotImplemented
 493  
 494      def _to_microseconds(self):
 495          return (self._days * (24 * 3600) + self._seconds) * 1000000 + self._microseconds
 496  
 497      def __floordiv__(self, other):
 498          if not isinstance(other, (int, timedelta)):
 499              return NotImplemented
 500          usec = self._to_microseconds()
 501          if isinstance(other, timedelta):
 502              return usec // other._to_microseconds()
 503          return timedelta(0, 0, usec // other)
 504  
 505      def __mod__(self, other):
 506          if isinstance(other, timedelta):
 507              r = self._to_microseconds() % other._to_microseconds()
 508              return timedelta(0, 0, r)
 509          return NotImplemented
 510  
 511      def __divmod__(self, other):
 512          if isinstance(other, timedelta):
 513              q, r = divmod(self._to_microseconds(), other._to_microseconds())
 514              return q, timedelta(0, 0, r)
 515          return NotImplemented
 516  
 517      def __mul__(self, other):
 518          if isinstance(other, int):
 519              # for CPython compatibility, we cannot use
 520              # our __class__ here, but need a real timedelta
 521              return timedelta(
 522                  self._days * other, self._seconds * other, self._microseconds * other
 523              )
 524          if isinstance(other, float):
 525              # a, b = other.as_integer_ratio()
 526              # return self * a / b
 527              usec = self._to_microseconds()
 528              return timedelta(0, 0, round(usec * other))
 529          return NotImplemented
 530  
 531      __rmul__ = __mul__
 532  
 533      # Supported comparisons
 534      def __eq__(self, other):
 535          if not isinstance(other, timedelta):
 536              return False
 537          return self._cmp(other) == 0
 538  
 539      def __ne__(self, other):
 540          if not isinstance(other, timedelta):
 541              return True
 542          return self._cmp(other) != 0
 543  
 544      def __le__(self, other):
 545          if not isinstance(other, timedelta):
 546              _cmperror(self, other)
 547          return self._cmp(other) <= 0
 548  
 549      def __lt__(self, other):
 550          if not isinstance(other, timedelta):
 551              _cmperror(self, other)
 552          return self._cmp(other) < 0
 553  
 554      def __ge__(self, other):
 555          if not isinstance(other, timedelta):
 556              _cmperror(self, other)
 557          return self._cmp(other) >= 0
 558  
 559      def __gt__(self, other):
 560          if not isinstance(other, timedelta):
 561              _cmperror(self, other)
 562          return self._cmp(other) > 0
 563  
 564      # pylint: disable=no-self-use, protected-access
 565      def _cmp(self, other):
 566          assert isinstance(other, timedelta)
 567          return _cmp(self._getstate(), other._getstate())
 568  
 569      def __bool__(self):
 570          return self._days != 0 or self._seconds != 0 or self._microseconds != 0
 571  
 572      def _getstate(self):
 573          return (self._days, self._seconds, self._microseconds)
 574  
 575  
 576  # pylint: disable=no-self-use
 577  class tzinfo:
 578      """This is an abstract base class, meaning that this class should not
 579      be instantiated directly. Define a subclass of tzinfo to capture information
 580      about a particular time zone.
 581  
 582      """
 583  
 584      def utcoffset(self, dt):
 585          """Return offset of local time from UTC, as a timedelta
 586          object that is positive east of UTC.
 587  
 588          """
 589          raise NotImplementedError("tzinfo subclass must override utcoffset()")
 590  
 591      def tzname(self, dt):
 592          """Return the time zone name corresponding to the datetime object dt, as a string."""
 593          raise NotImplementedError("tzinfo subclass must override tzname()")
 594  
 595      # tzinfo is an abstract base class, disabling for self._offset
 596      # pylint: disable=no-member
 597      def fromutc(self, dt):
 598          "datetime in UTC -> datetime in local time."
 599  
 600          if not isinstance(dt, datetime):
 601              raise TypeError("fromutc() requires a datetime argument")
 602          if dt.tzinfo is not self:
 603              raise ValueError("dt.tzinfo is not self")
 604  
 605          dtoff = dt.utcoffset()
 606          if dtoff is None:
 607              raise ValueError("fromutc() requires a non-None utcoffset() " "result")
 608          return dt + self._offset
 609  
 610  
 611  class date:
 612      """A date object represents a date (year, month and day) in an idealized calendar,
 613      the current Gregorian calendar indefinitely extended in both directions.
 614      Objects of this type are always naive.
 615  
 616      """
 617  
 618      def __new__(cls, year, month, day):
 619          """Creates a new date object.
 620  
 621          :param int year: Year within range, MINYEAR <= year <= MAXYEAR
 622          :param int month: Month within range, 1 <= month <= 12
 623          :param int day: Day within range, 1 <= day <= number of days in the given month and year
 624          """
 625          _check_date_fields(year, month, day)
 626          self = object.__new__(cls)
 627          self._year = year
 628          self._month = month
 629          self._day = day
 630          self._hashcode = -1
 631          return self
 632  
 633      # Instance attributes (read-only)
 634      @property
 635      def year(self):
 636          """Between MINYEAR and MAXYEAR inclusive."""
 637          return self._year
 638  
 639      @property
 640      def month(self):
 641          """Between 1 and 12 inclusive."""
 642          return self._month
 643  
 644      @property
 645      def day(self):
 646          """Between 1 and the number of days in the given month of the given year."""
 647          return self._day
 648  
 649      # Class Methods
 650      @classmethod
 651      def fromtimestamp(cls, t):
 652          """Return the local date corresponding to the POSIX timestamp,
 653          such as is returned by time.time().
 654          """
 655          tm_struct = _time.localtime(t)
 656          return cls(tm_struct[0], tm_struct[1], tm_struct[2])
 657  
 658      @classmethod
 659      def fromordinal(cls, ordinal):
 660          """Return the date corresponding to the proleptic Gregorian ordinal,
 661          where January 1 of year 1 has ordinal 1.
 662  
 663          """
 664          if not ordinal >= 1:
 665              raise ValueError("ordinal must be >=1")
 666          y, m, d = _ord2ymd(ordinal)
 667          return cls(y, m, d)
 668  
 669      @classmethod
 670      def fromisoformat(cls, date_string):
 671          """Return a date object constructed from an ISO date format.
 672          Valid format is ``YYYY-MM-DD``
 673  
 674          """
 675          match = _re.match(
 676              r"([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$", date_string
 677          )
 678          if match:
 679              y, m, d = int(match.group(1)), int(match.group(2)), int(match.group(3))
 680              return cls(y, m, d)
 681          raise ValueError(_INVALID_ISO_ERROR.format(date_string))
 682  
 683      @classmethod
 684      def today(cls):
 685          """Return the current local date."""
 686          return cls.fromtimestamp(_time.time())
 687  
 688      # Instance Methods
 689      def replace(self, year=None, month=None, day=None):
 690          """Return a date with the same value, except for those parameters
 691          given new values by whichever keyword arguments are specified.
 692          If no keyword arguments are specified - values are obtained from
 693          datetime object.
 694  
 695          """
 696          raise NotImplementedError()
 697  
 698      def timetuple(self):
 699          """Return a time.struct_time such as returned by time.localtime().
 700          The hours, minutes and seconds are 0, and the DST flag is -1.
 701  
 702          """
 703          return _build_struct_time(self._year, self._month, self._day, 0, 0, 0, -1)
 704  
 705      def toordinal(self):
 706          """Return the proleptic Gregorian ordinal of the date, where January 1 of
 707          year 1 has ordinal 1.
 708          """
 709          return _ymd2ord(self._year, self._month, self._day)
 710  
 711      def weekday(self):
 712          """Return the day of the week as an integer, where Monday is 0 and Sunday is 6."""
 713          return (self.toordinal() + 6) % 7
 714  
 715      # ISO date
 716      def isoweekday(self):
 717          """Return the day of the week as an integer, where Monday is 1 and Sunday is 7."""
 718          return self.toordinal() % 7 or 7
 719  
 720      def isoformat(self):
 721          """Return a string representing the date in ISO 8601 format, YYYY-MM-DD:"""
 722          return "%04d-%02d-%02d" % (self._year, self._month, self._day)
 723  
 724      # For a date d, str(d) is equivalent to d.isoformat()
 725      __str__ = isoformat
 726  
 727      def __repr__(self):
 728          """Convert to formal string, for repr()."""
 729          return "%s(%d, %d, %d)" % (
 730              "datetime." + self.__class__.__name__,
 731              self._year,
 732              self._month,
 733              self._day,
 734          )
 735  
 736      # Supported comparisons
 737      def __eq__(self, other):
 738          if isinstance(other, date):
 739              return self._cmp(other) == 0
 740          return NotImplemented
 741  
 742      def __le__(self, other):
 743          if isinstance(other, date):
 744              return self._cmp(other) <= 0
 745          return NotImplemented
 746  
 747      def __lt__(self, other):
 748          if isinstance(other, date):
 749              return self._cmp(other) < 0
 750          return NotImplemented
 751  
 752      def __ge__(self, other):
 753          if isinstance(other, date):
 754              return self._cmp(other) >= 0
 755          return NotImplemented
 756  
 757      def __gt__(self, other):
 758          if isinstance(other, date):
 759              return self._cmp(other) > 0
 760          return NotImplemented
 761  
 762      def _cmp(self, other):
 763          assert isinstance(other, date)
 764          y, m, d = self._year, self._month, self._day
 765          y2, m2, d2 = other.year, other.month, other.day
 766          return _cmp((y, m, d), (y2, m2, d2))
 767  
 768      def __hash__(self):
 769          if self._hashcode == -1:
 770              self._hashcode = hash(self._getstate())
 771          return self._hashcode
 772  
 773      # Pickle support
 774      def _getstate(self):
 775          yhi, ylo = divmod(self._year, 256)
 776          return (bytes([yhi, ylo, self._month, self._day]),)
 777  
 778      def _setstate(self, string):
 779          yhi, ylo, self._month, self._day = string
 780          self._year = yhi * 256 + ylo
 781  
 782  
 783  class timezone(tzinfo):
 784      """The timezone class is a subclass of tzinfo, each instance of which represents a
 785      timezone defined by a fixed offset from UTC.
 786  
 787      Objects of this class cannot be used to represent timezone information in the locations
 788      where different offsets are used in different days of the year or where historical changes
 789      have been made to civil time.
 790  
 791      """
 792  
 793      # Sentinel value to disallow None
 794      _Omitted = object()
 795  
 796      def __new__(cls, offset, name=_Omitted):
 797          if not isinstance(offset, timedelta):
 798              raise TypeError("offset must be a timedelta")
 799          if name is cls._Omitted:
 800              if not offset:
 801                  return cls.utc
 802              name = None
 803          elif not isinstance(name, str):
 804              raise TypeError("name must be a string")
 805          if not cls.minoffset <= offset <= cls.maxoffset:
 806              raise ValueError(
 807                  "offset must be a timedelta"
 808                  " strictly between -timedelta(hours=24) and"
 809                  " timedelta(hours=24)."
 810              )
 811          if offset.microseconds != 0 or offset.seconds % 60 != 0:
 812              raise ValueError(
 813                  "offset must be a timedelta" " representing a whole number of minutes"
 814              )
 815          cls._offset = offset
 816          cls._name = name
 817          return cls._create(offset, name)
 818  
 819      # pylint: disable=protected-access, bad-super-call
 820      @classmethod
 821      def _create(cls, offset, name=None):
 822          """High-level creation for a timezone object."""
 823          self = super(tzinfo, cls).__new__(cls)
 824          self._offset = offset
 825          self._name = name
 826          return self
 827  
 828      # Instance methods
 829      def utcoffset(self, dt):
 830          if isinstance(dt, datetime) or dt is None:
 831              return self._offset
 832          raise TypeError("utcoffset() argument must be a datetime instance" " or None")
 833  
 834      def tzname(self, dt):
 835          if isinstance(dt, datetime) or dt is None:
 836              if self._name is None:
 837                  return self._name_from_offset(self._offset)
 838              return self._name
 839          raise TypeError("tzname() argument must be a datetime instance" " or None")
 840  
 841      # Comparison to other timezone objects
 842      def __eq__(self, other):
 843          if not isinstance(other, timezone):
 844              return False
 845          return self._offset == other._offset
 846  
 847      def __hash__(self):
 848          return hash(self._offset)
 849  
 850      def __repr__(self):
 851          """Convert to formal string, for repr()."""
 852          if self is self.utc:
 853              return "datetime.timezone.utc"
 854          if self._name is None:
 855              return "%s(%r)" % ("datetime." + self.__class__.__name__, self._offset)
 856          return "%s(%r, %r)" % (
 857              "datetime." + self.__class__.__name__,
 858              self._offset,
 859              self._name,
 860          )
 861  
 862      def __str__(self):
 863          return self.tzname(None)
 864  
 865      @staticmethod
 866      def _name_from_offset(delta):
 867          if delta < timedelta(0):
 868              sign = "-"
 869              delta = -delta
 870          else:
 871              sign = "+"
 872          hours, rest = divmod(delta, timedelta(hours=1))
 873          minutes = rest // timedelta(minutes=1)
 874          return "UTC{}{:02d}:{:02d}".format(sign, hours, minutes)
 875  
 876      maxoffset = timedelta(hours=23, minutes=59)
 877      minoffset = -maxoffset
 878  
 879  
 880  class time:
 881      """A time object represents a (local) time of day, independent of
 882      any particular day, and subject to adjustment via a tzinfo object.
 883  
 884      """
 885  
 886      # pylint: disable=redefined-outer-name
 887      def __new__(cls, hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, fold=0):
 888          _check_time_fields(hour, minute, second, microsecond, fold)
 889          _check_tzinfo_arg(tzinfo)
 890          self = object.__new__(cls)
 891          self._hour = hour
 892          self._minute = minute
 893          self._second = second
 894          self._microsecond = microsecond
 895          self._tzinfo = tzinfo
 896          self._fold = fold
 897          self._hashcode = -1
 898          return self
 899  
 900      # Instance attributes (read-only)
 901      @property
 902      def hour(self):
 903          """In range(24)."""
 904          return self._hour
 905  
 906      @property
 907      def minute(self):
 908          """In range(60)."""
 909          return self._minute
 910  
 911      @property
 912      def second(self):
 913          """In range(60)."""
 914          return self._second
 915  
 916      @property
 917      def microsecond(self):
 918          """In range(1000000)."""
 919          return self._microsecond
 920  
 921      @property
 922      def fold(self):
 923          """Fold."""
 924          return self._fold
 925  
 926      @property
 927      def tzinfo(self):
 928          """The object passed as the tzinfo argument to
 929          the time constructor, or None if none was passed.
 930          """
 931          return self._tzinfo
 932  
 933      @staticmethod
 934      def _parse_iso_string(string_to_parse, segments):
 935          results = []
 936  
 937          remaining_string = string_to_parse
 938          for regex in segments:
 939              match = _re.match(regex, remaining_string)
 940              if match:
 941                  for grp in range(regex.count("(")):
 942                      results.append(int(match.group(grp + 1)))
 943                  remaining_string = remaining_string[len(match.group(0)) :]
 944              elif remaining_string:  # Only raise an error if we're not done yet
 945                  raise ValueError()
 946          if remaining_string:
 947              raise ValueError()
 948          return results
 949  
 950      # pylint: disable=too-many-locals
 951      @classmethod
 952      def fromisoformat(cls, time_string):
 953          """Return a time object constructed from an ISO date format.
 954          Valid format is ``HH[:MM[:SS[.fff[fff]]]][+HH:MM[:SS[.ffffff]]]``
 955  
 956          """
 957          # Store the original string in an error message
 958          original_string = time_string
 959          match = _re.match(r"(.*)[\-\+]", time_string)
 960          offset_string = None
 961          if match:
 962              offset_string = time_string[len(match.group(1)) :]
 963              time_string = match.group(1)
 964  
 965          time_segments = (
 966              r"([0-9][0-9])",
 967              r":([0-9][0-9])",
 968              r":([0-9][0-9])",
 969              r"\.([0-9][0-9][0-9])",
 970              r"([0-9][0-9][0-9])",
 971          )
 972          offset_segments = (
 973              r"([\-\+][0-9][0-9]):([0-9][0-9])",
 974              r":([0-9][0-9])",
 975              r"\.([0-9][0-9][0-9][0-9][0-9][0-9])",
 976          )
 977  
 978          try:
 979              results = cls._parse_iso_string(time_string, time_segments)
 980              if len(results) < 1:
 981                  raise ValueError(_INVALID_ISO_ERROR.format(original_string))
 982              if len(results) < len(time_segments):
 983                  results += [None] * (len(time_segments) - len(results))
 984              if offset_string:
 985                  results += cls._parse_iso_string(offset_string, offset_segments)
 986          except ValueError as error:
 987              raise ValueError(_INVALID_ISO_ERROR.format(original_string)) from error
 988  
 989          hh = results[0]
 990          mm = results[1] if len(results) >= 2 and results[1] is not None else 0
 991          ss = results[2] if len(results) >= 3 and results[2] is not None else 0
 992          us = 0
 993          if len(results) >= 4 and results[3] is not None:
 994              us += results[3] * 1000
 995          if len(results) >= 5 and results[4] is not None:
 996              us += results[4]
 997          tz = None
 998          if len(results) >= 7:
 999              offset_hh = results[5]
1000              multiplier = -1 if offset_hh < 0 else 1
1001              offset_mm = results[6] * multiplier
1002              offset_ss = (results[7] if len(results) >= 8 else 0) * multiplier
1003              offset_us = (results[8] if len(results) >= 9 else 0) * multiplier
1004              offset = timedelta(
1005                  hours=offset_hh,
1006                  minutes=offset_mm,
1007                  seconds=offset_ss,
1008                  microseconds=offset_us,
1009              )
1010              tz = timezone(offset, name="utcoffset")
1011  
1012          result = cls(
1013              hh,
1014              mm,
1015              ss,
1016              us,
1017              tz,
1018          )
1019          return result
1020  
1021      # pylint: enable=too-many-locals
1022  
1023      # Instance methods
1024      def isoformat(self, timespec="auto"):
1025          """Return a string representing the time in ISO 8601 format, one of:
1026          HH:MM:SS.ffffff, if microsecond is not 0
1027  
1028          HH:MM:SS, if microsecond is 0
1029  
1030          HH:MM:SS.ffffff+HH:MM[:SS[.ffffff]], if utcoffset() does not return None
1031  
1032          HH:MM:SS+HH:MM[:SS[.ffffff]], if microsecond is 0 and utcoffset() does not return None
1033  
1034          """
1035          s = _format_time(
1036              self._hour, self._minute, self._second, self._microsecond, timespec
1037          )
1038          tz = self._tzstr()
1039          if tz:
1040              s += tz
1041          return s
1042  
1043      # For a time t, str(t) is equivalent to t.isoformat()
1044      __str__ = isoformat
1045  
1046      def utcoffset(self):
1047          """Return the timezone offset in minutes east of UTC (negative west of
1048          UTC)."""
1049          if self._tzinfo is None:
1050              return None
1051          offset = self._tzinfo.utcoffset(None)
1052          _check_utc_offset("utcoffset", offset)
1053          return offset
1054  
1055      def tzname(self):
1056          """Return the timezone name.
1057  
1058          Note that the name is 100% informational -- there's no requirement that
1059          it mean anything in particular. For example, "GMT", "UTC", "-500",
1060          "-5:00", "EDT", "US/Eastern", "America/New York" are all valid replies.
1061          """
1062          if self._tzinfo is None:
1063              return None
1064          name = self._tzinfo.tzname(None)
1065          _check_tzname(name)
1066          return name
1067  
1068      # Standard conversions and comparisons
1069      def __eq__(self, other):
1070          if not isinstance(other, time):
1071              return NotImplemented
1072          return self._cmp(other, allow_mixed=True) == 0
1073  
1074      def __le__(self, other):
1075          if not isinstance(other, time):
1076              return NotImplemented
1077          return self._cmp(other) <= 0
1078  
1079      def __lt__(self, other):
1080          if not isinstance(other, time):
1081              return NotImplemented
1082          return self._cmp(other) < 0
1083  
1084      def __ge__(self, other):
1085          if not isinstance(other, time):
1086              return NotImplemented
1087          return self._cmp(other) >= 0
1088  
1089      def __gt__(self, other):
1090          if not isinstance(other, time):
1091              return NotImplemented
1092          return self._cmp(other) > 0
1093  
1094      def _cmp(self, other, allow_mixed=False):
1095          assert isinstance(other, time)
1096          mytz = self._tzinfo
1097          ottz = other.tzinfo
1098          myoff = otoff = None
1099  
1100          if mytz is ottz:
1101              base_compare = True
1102          else:
1103              myoff = self.utcoffset()
1104              otoff = other.utcoffset()
1105              base_compare = myoff == otoff
1106  
1107          if base_compare:
1108              return _cmp(
1109                  (self._hour, self._minute, self._second, self._microsecond),
1110                  (other.hour, other.minute, other.second, other.microsecond),
1111              )
1112          if myoff is None or otoff is None:
1113              if not allow_mixed:
1114                  raise TypeError("cannot compare naive and aware times")
1115              return 2  # arbitrary non-zero value
1116          myhhmm = self._hour * 60 + self._minute - myoff // timedelta(minutes=1)
1117          othhmm = other.hour * 60 + other.minute - otoff // timedelta(minutes=1)
1118          return _cmp(
1119              (myhhmm, self._second, self._microsecond),
1120              (othhmm, other.second, other.microsecond),
1121          )
1122  
1123      def __hash__(self):
1124          """Hash."""
1125          if self._hashcode == -1:
1126              t = self
1127              tzoff = t.utcoffset()
1128              if not tzoff:  # zero or None
1129                  self._hashcode = hash(t._getstate()[0])
1130              else:
1131                  h, m = divmod(
1132                      timedelta(hours=self.hour, minutes=self.minute) - tzoff,
1133                      timedelta(hours=1),
1134                  )
1135                  assert not m % timedelta(minutes=1), "whole minute"
1136                  m //= timedelta(minutes=1)
1137                  if 0 <= h < 24:
1138                      self._hashcode = hash(time(h, m, self.second, self.microsecond))
1139                  else:
1140                      self._hashcode = hash((h, m, self.second, self.microsecond))
1141          return self._hashcode
1142  
1143      def _tzstr(self, sep=":"):
1144          """Return formatted timezone offset (+xx:xx) or None."""
1145          off = self.utcoffset()
1146          if off is not None:
1147              if off.days < 0:
1148                  sign = "-"
1149                  off = -1 * off
1150              else:
1151                  sign = "+"
1152              hh, mm = divmod(off, timedelta(hours=1))
1153              assert not mm % timedelta(minutes=1), "whole minute"
1154              mm //= timedelta(minutes=1)
1155              assert 0 <= hh < 24
1156              off = "%s%02d%s%02d" % (sign, hh, sep, mm)
1157          return off
1158  
1159      def __format__(self, fmt):
1160          if not isinstance(fmt, str):
1161              raise TypeError("must be str, not %s" % type(fmt).__name__)
1162          return str(self)
1163  
1164      def __repr__(self):
1165          """Convert to formal string, for repr()."""
1166          if self._microsecond != 0:
1167              s = ", %d, %d" % (self._second, self._microsecond)
1168          elif self._second != 0:
1169              s = ", %d" % self._second
1170          else:
1171              s = ""
1172          s = "%s(%d, %d%s)" % (
1173              "datetime." + self.__class__.__name__,
1174              self._hour,
1175              self._minute,
1176              s,
1177          )
1178          if self._tzinfo is not None:
1179              assert s[-1:] == ")"
1180              s = s[:-1] + ", tzinfo=%r" % self._tzinfo + ")"
1181          return s
1182  
1183      # Pickle support
1184      def _getstate(self, protocol=3):
1185          us2, us3 = divmod(self._microsecond, 256)
1186          us1, us2 = divmod(us2, 256)
1187          h = self._hour
1188          if self._fold and protocol > 3:
1189              h += 128
1190          basestate = bytes([h, self._minute, self._second, us1, us2, us3])
1191          if not self._tzinfo is None:
1192              return (basestate, self._tzinfo)
1193          return (basestate,)
1194  
1195  
1196  # pylint: disable=too-many-instance-attributes, too-many-public-methods
1197  class datetime(date):
1198      """A datetime object is a single object containing all the information
1199      from a date object and a time object. Like a date object, datetime assumes
1200      the current Gregorian calendar extended in both directions; like a time object,
1201      datetime assumes there are exactly 3600*24 seconds in every day.
1202  
1203      """
1204  
1205      # pylint: disable=redefined-outer-name
1206      def __new__(
1207          cls,
1208          year,
1209          month,
1210          day,
1211          hour=0,
1212          minute=0,
1213          second=0,
1214          microsecond=0,
1215          tzinfo=None,
1216          *,
1217          fold=0
1218      ):
1219          _check_date_fields(year, month, day)
1220          _check_time_fields(hour, minute, second, microsecond, fold)
1221          _check_tzinfo_arg(tzinfo)
1222  
1223          self = object.__new__(cls)
1224          self._year = year
1225          self._month = month
1226          self._day = day
1227          self._hour = hour
1228          self._minute = minute
1229          self._second = second
1230          self._microsecond = microsecond
1231          self._tzinfo = tzinfo
1232          self._fold = fold
1233          self._hashcode = -1
1234          return self
1235  
1236      # Read-only instance attributes
1237      @property
1238      def year(self):
1239          """Between MINYEAR and MAXYEAR inclusive."""
1240          return self._year
1241  
1242      @property
1243      def month(self):
1244          """Between 1 and 12 inclusive."""
1245          return self._month
1246  
1247      @property
1248      def day(self):
1249          """Between 1 and the number of days in the given month of the given year."""
1250          return self._day
1251  
1252      @property
1253      def hour(self):
1254          """In range(24)."""
1255          return self._hour
1256  
1257      @property
1258      def minute(self):
1259          """In range (60)"""
1260          return self._minute
1261  
1262      @property
1263      def second(self):
1264          """In range (60)"""
1265          return self._second
1266  
1267      @property
1268      def microsecond(self):
1269          """In range (1000000)"""
1270          return self._microsecond
1271  
1272      @property
1273      def tzinfo(self):
1274          """The object passed as the tzinfo argument to the datetime constructor,
1275          or None if none was passed.
1276          """
1277          return self._tzinfo
1278  
1279      @property
1280      def fold(self):
1281          """Fold."""
1282          return self._fold
1283  
1284      # Class methods
1285  
1286      # pylint: disable=protected-access
1287      @classmethod
1288      def _fromtimestamp(cls, t, utc, tz):
1289          """Construct a datetime from a POSIX timestamp (like time.time()).
1290          A timezone info object may be passed in as well.
1291          """
1292          if isinstance(t, float):
1293              frac, t = _math.modf(t)
1294              us = round(frac * 1e6)
1295              if us >= 1000000:
1296                  t += 1
1297                  us -= 1000000
1298              elif us < 0:
1299                  t -= 1
1300                  us += 1000000
1301          else:
1302              us = 0
1303  
1304          if utc:
1305              raise NotImplementedError(
1306                  "CircuitPython does not currently implement time.gmtime."
1307              )
1308          converter = _time.localtime
1309          struct_time = converter(t)
1310          ss = min(struct_time[5], 59)  # clamp out leap seconds if the platform has them
1311          result = cls(
1312              struct_time[0],
1313              struct_time[1],
1314              struct_time[2],
1315              struct_time[3],
1316              struct_time[4],
1317              ss,
1318              us,
1319              tz,
1320          )
1321          if tz is not None:
1322              result = tz.fromutc(result)
1323          return result
1324  
1325      ## pylint: disable=arguments-differ
1326      @classmethod
1327      def fromtimestamp(cls, timestamp, tz=None):
1328          return cls._fromtimestamp(timestamp, tz is not None, tz)
1329  
1330      @classmethod
1331      def fromisoformat(cls, date_string):
1332          """Return a datetime object constructed from an ISO date format.
1333          Valid format is ``YYYY-MM-DD[*HH[:MM[:SS[.fff[fff]]]][+HH:MM[:SS[.ffffff]]]]``
1334  
1335          """
1336          original_string = date_string
1337  
1338          time_string = None
1339          try:
1340              if len(date_string) > 10:
1341                  time_string = date_string[11:]
1342                  date_string = date_string[:10]
1343                  dateval = date.fromisoformat(date_string)
1344                  timeval = time.fromisoformat(time_string)
1345              else:
1346                  dateval = date.fromisoformat(date_string)
1347                  timeval = time()
1348          except ValueError as error:
1349              raise ValueError(_INVALID_ISO_ERROR.format(original_string)) from error
1350  
1351          return cls.combine(dateval, timeval)
1352  
1353      @classmethod
1354      def now(cls, timezone=None):
1355          """Return the current local date and time."""
1356          return cls.fromtimestamp(_time.time(), tz=timezone)
1357  
1358      @classmethod
1359      def utcfromtimestamp(cls, timestamp):
1360          """Return the UTC datetime corresponding to the POSIX timestamp, with tzinfo None"""
1361          return cls._fromtimestamp(timestamp, True, None)
1362  
1363      @classmethod
1364      def combine(cls, date, time, tzinfo=True):
1365          """Return a new datetime object whose date components are equal to the
1366          given date object’s, and whose time components are equal to the given time object’s.
1367  
1368          """
1369          if not isinstance(date, _date_class):
1370              raise TypeError("date argument must be a date instance")
1371          if not isinstance(time, _time_class):
1372              raise TypeError("time argument must be a time instance")
1373          if tzinfo is True:
1374              tzinfo = time.tzinfo
1375          return cls(
1376              date.year,
1377              date.month,
1378              date.day,
1379              time.hour,
1380              time.minute,
1381              time.second,
1382              time.microsecond,
1383              tzinfo,
1384              fold=time.fold,
1385          )
1386  
1387      # Instance methods
1388      def _mktime(self):
1389          """Return integer POSIX timestamp."""
1390          epoch = datetime(1970, 1, 1)
1391          max_fold_seconds = 24 * 3600
1392          t = (self - epoch) // timedelta(0, 1)
1393  
1394          def local(u):
1395              y, m, d, hh, mm, ss = _time.localtime(u)[:6]
1396              return (datetime(y, m, d, hh, mm, ss) - epoch) // timedelta(0, 1)
1397  
1398          # Our goal is to solve t = local(u) for u.
1399          a = local(t) - t
1400          u1 = t - a
1401          t1 = local(u1)
1402          if t1 == t:
1403              # We found one solution, but it may not be the one we need.
1404              # Look for an earlier solution (if `fold` is 0), or a
1405              # later one (if `fold` is 1).
1406              u2 = u1 + (-max_fold_seconds, max_fold_seconds)[self._fold]
1407              b = local(u2) - u2
1408              if a == b:
1409                  return u1
1410          else:
1411              b = t1 - u1
1412              assert a != b
1413          u2 = t - b
1414          t2 = local(u2)
1415          if t2 == t:
1416              return u2
1417          if t1 == t:
1418              return u1
1419          # We have found both offsets a and b, but neither t - a nor t - b is
1420          # a solution.  This means t is in the gap.
1421          return (max, min)[self._fold](u1, u2)
1422  
1423      def date(self):
1424          """Return date object with same year, month and day."""
1425          return _date_class(self._year, self._month, self._day)
1426  
1427      def time(self):
1428          """Return time object with same hour, minute, second, microsecond and fold.
1429          tzinfo is None. See also method timetz().
1430  
1431          """
1432          return _time_class(
1433              self._hour, self._minute, self._second, self._microsecond, fold=self._fold
1434          )
1435  
1436      def dst(self):
1437          """If tzinfo is None, returns None, else returns self.tzinfo.dst(self),
1438          and raises an exception if the latter doesn’t return None or a timedelta
1439          object with magnitude less than one day.
1440  
1441          """
1442          if self._tzinfo is None:
1443              return None
1444          offset = self._tzinfo.dst(self)
1445          _check_utc_offset("dst", offset)
1446          return offset
1447  
1448      def timetuple(self):
1449          """Return local time tuple compatible with time.localtime()."""
1450          dst = self.dst()
1451          if dst is None:
1452              dst = -1
1453          elif dst:
1454              dst = 1
1455          else:
1456              dst = 0
1457          return _build_struct_time(
1458              self.year, self.month, self.day, self.hour, self.minute, self.second, dst
1459          )
1460  
1461      def utcoffset(self):
1462          """If tzinfo is None, returns None, else returns
1463          self.tzinfo.utcoffset(self), and raises an exception
1464          if the latter doesn’t return None or a timedelta object
1465          with magnitude less than one day.
1466  
1467          """
1468          if self._tzinfo is None:
1469              return None
1470          offset = self._tzinfo.utcoffset(self)
1471          _check_utc_offset("utcoffset", offset)
1472          return offset
1473  
1474      def toordinal(self):
1475          """Return the proleptic Gregorian ordinal of the date."""
1476          return _ymd2ord(self._year, self._month, self._day)
1477  
1478      def timestamp(self):
1479          "Return POSIX timestamp as float"
1480          if not self._tzinfo is None:
1481              return (self - _EPOCH).total_seconds()
1482          s = self._mktime()
1483          return s + self.microsecond / 1e6
1484  
1485      def weekday(self):
1486          """Return the day of the week as an integer, where Monday is 0 and Sunday is 6."""
1487          return (self.toordinal() + 6) % 7
1488  
1489      def ctime(self):
1490          "Return string representing the datetime."
1491          weekday = self.toordinal() % 7 or 7
1492          return "%s %s %2d %02d:%02d:%02d %04d" % (
1493              _DAYNAMES[weekday],
1494              _MONTHNAMES[self._month],
1495              self._day,
1496              self._hour,
1497              self._minute,
1498              self._second,
1499              self._year,
1500          )
1501  
1502      def __repr__(self):
1503          """Convert to formal string, for repr()."""
1504          L = [
1505              self._year,
1506              self._month,
1507              self._day,  # These are never zero
1508              self._hour,
1509              self._minute,
1510              self._second,
1511              self._microsecond,
1512          ]
1513          if L[-1] == 0:
1514              del L[-1]
1515          if L[-1] == 0:
1516              del L[-1]
1517          s = ", ".join(map(str, L))
1518          s = "%s(%s)" % ("datetime." + self.__class__.__name__, s)
1519          if self._tzinfo is not None:
1520              assert s[-1:] == ")"
1521              s = s[:-1] + ", tzinfo=%r" % self._tzinfo + ")"
1522          return s
1523  
1524      def isoformat(self, sep="T", timespec="auto"):
1525          """Return a string representing the date and time in
1526          ISO8601 format.
1527  
1528          """
1529          s = "%04d-%02d-%02d%c" % (
1530              self._year,
1531              self._month,
1532              self._day,
1533              sep,
1534          ) + _format_time(
1535              self._hour, self._minute, self._second, self._microsecond, timespec
1536          )
1537  
1538          off = self.utcoffset()
1539          tz = _format_offset(off)
1540          if tz:
1541              s += tz
1542  
1543          return s
1544  
1545      def __str__(self):
1546          "Convert to string, for str()."
1547          return self.isoformat(sep=" ")
1548  
1549      def replace(
1550          self,
1551          year=None,
1552          month=None,
1553          day=None,
1554          hour=None,
1555          minute=None,
1556          second=None,
1557          microsecond=None,
1558          tzinfo=True,
1559          *,
1560          fold=None
1561      ):
1562          """Return a datetime with the same attributes,
1563          except for those attributes given new values by
1564          whichever keyword arguments are specified.
1565  
1566          """
1567          if year is None:
1568              year = self.year
1569          if month is None:
1570              month = self.month
1571          if day is None:
1572              day = self.day
1573          if hour is None:
1574              hour = self.hour
1575          if minute is None:
1576              minute = self.minute
1577          if second is None:
1578              second = self.second
1579          if microsecond is None:
1580              microsecond = self.microsecond
1581          if tzinfo is True:
1582              tzinfo = self.tzinfo
1583          if fold is None:
1584              fold = self._fold
1585          return type(self)(
1586              year, month, day, hour, minute, second, microsecond, tzinfo, fold=fold
1587          )
1588  
1589      # Comparisons of datetime objects.
1590      def __eq__(self, other):
1591          if not isinstance(other, datetime):
1592              return False
1593          return self._cmp(other, allow_mixed=True) == 0
1594  
1595      def __le__(self, other):
1596          if not isinstance(other, datetime):
1597              _cmperror(self, other)
1598          return self._cmp(other) <= 0
1599  
1600      def __lt__(self, other):
1601          if not isinstance(other, datetime):
1602              _cmperror(self, other)
1603          return self._cmp(other) < 0
1604  
1605      def __ge__(self, other):
1606          if not isinstance(other, datetime):
1607              _cmperror(self, other)
1608          return self._cmp(other) >= 0
1609  
1610      def __gt__(self, other):
1611          if not isinstance(other, datetime):
1612              _cmperror(self, other)
1613          return self._cmp(other) > 0
1614  
1615      def _cmp(self, other, allow_mixed=False):
1616          assert isinstance(other, datetime)
1617          mytz = self._tzinfo
1618          ottz = other.tzinfo
1619          myoff = otoff = None
1620  
1621          if mytz is ottz:
1622              base_compare = True
1623          else:
1624              myoff = self.utcoffset()
1625              otoff = other.utcoffset()
1626              # Assume that allow_mixed means that we are called from __eq__
1627              if allow_mixed:
1628                  if myoff != self.replace(fold=not self._fold).utcoffset():
1629                      return 2
1630                  if otoff != other.replace(fold=not other.fold).utcoffset():
1631                      return 2
1632              base_compare = myoff == otoff
1633  
1634          if base_compare:
1635              return _cmp(
1636                  (
1637                      self._year,
1638                      self._month,
1639                      self._day,
1640                      self._hour,
1641                      self._minute,
1642                      self._second,
1643                      self._microsecond,
1644                  ),
1645                  (
1646                      other.year,
1647                      other.month,
1648                      other.day,
1649                      other.hour,
1650                      other.minute,
1651                      other.second,
1652                      other.microsecond,
1653                  ),
1654              )
1655          if myoff is None or otoff is None:
1656              if not allow_mixed:
1657                  raise TypeError("cannot compare naive and aware datetimes")
1658              return 2  # arbitrary non-zero value
1659          diff = self - other  # this will take offsets into account
1660          if diff.days < 0:
1661              return -1
1662          return 1 if diff else 0
1663  
1664      def __add__(self, other):
1665          "Add a datetime and a timedelta."
1666          if not isinstance(other, timedelta):
1667              return NotImplemented
1668          delta = timedelta(
1669              self.toordinal(),
1670              hours=self._hour,
1671              minutes=self._minute,
1672              seconds=self._second,
1673              microseconds=self._microsecond,
1674          )
1675          delta += other
1676          hour, rem = divmod(delta._seconds, 3600)
1677          minute, second = divmod(rem, 60)
1678          if 0 < delta._days <= _MAXORDINAL:
1679              return type(self).combine(
1680                  date.fromordinal(delta._days),
1681                  time(hour, minute, second, delta._microseconds, tzinfo=self._tzinfo),
1682              )
1683          raise OverflowError("result out of range")
1684  
1685      __radd__ = __add__
1686  
1687      def __sub__(self, other):
1688          "Subtract two datetimes, or a datetime and a timedelta."
1689          if not isinstance(other, datetime):
1690              if isinstance(other, timedelta):
1691                  return self + -other
1692              return NotImplemented
1693  
1694          days1 = self.toordinal()
1695          days2 = other.toordinal()
1696          secs1 = self._second + self._minute * 60 + self._hour * 3600
1697          secs2 = other._second + other._minute * 60 + other._hour * 3600
1698          base = timedelta(
1699              days1 - days2, secs1 - secs2, self._microsecond - other._microsecond
1700          )
1701          if self._tzinfo is other._tzinfo:
1702              return base
1703          myoff = self.utcoffset()
1704          otoff = other.utcoffset()
1705          if myoff == otoff:
1706              return base
1707          if myoff is None or otoff is None:
1708              raise TypeError("cannot mix naive and timezone-aware time")
1709          return base + otoff - myoff
1710  
1711      def __hash__(self):
1712          if self._hashcode == -1:
1713              t = self
1714              tzoff = t.utcoffset()
1715              if tzoff is None:
1716                  self._hashcode = hash(t._getstate()[0])
1717              else:
1718                  days = _ymd2ord(self.year, self.month, self.day)
1719                  seconds = self.hour * 3600 + self.minute * 60 + self.second
1720                  self._hashcode = hash(
1721                      timedelta(days, seconds, self.microsecond) - tzoff
1722                  )
1723          return self._hashcode
1724  
1725      def _getstate(self):
1726          protocol = 3
1727          yhi, ylo = divmod(self._year, 256)
1728          us2, us3 = divmod(self._microsecond, 256)
1729          us1, us2 = divmod(us2, 256)
1730          m = self._month
1731          if self._fold and protocol > 3:
1732              m += 128
1733          basestate = bytes(
1734              [
1735                  yhi,
1736                  ylo,
1737                  m,
1738                  self._day,
1739                  self._hour,
1740                  self._minute,
1741                  self._second,
1742                  us1,
1743                  us2,
1744                  us3,
1745              ]
1746          )
1747          if not self._tzinfo is None:
1748              return (basestate, self._tzinfo)
1749          return (basestate,)
1750  
1751  
1752  # Module exports
1753  # pylint: disable=protected-access
1754  timezone.utc = timezone._create(timedelta(0))
1755  timezone.min = timezone._create(timezone.minoffset)
1756  timezone.max = timezone._create(timezone.maxoffset)
1757  _EPOCH = datetime(1970, 1, 1, tzinfo=timezone.utc)
1758  _date_class = date
1759  _time_class = time