/ 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