From fc86070ffb8aeead50d597532567a76ad3a6a6ea Mon Sep 17 00:00:00 2001 From: Lorenzo Cappelletti Date: Sun, 26 Sep 2021 21:57:49 +0200 Subject: [PATCH] python-stdlib/datetime: Add new implementation of datetime module. This new module is a port of Python datetime providing classes for manipulating dates, times, and deltas. It completely replaces the existing unix-ffi version. Signed-off-by: Lorenzo Cappelletti --- python-stdlib/datetime/datetime.py | 879 +++++ python-stdlib/datetime/localtz.patch | 84 + python-stdlib/datetime/metadata.txt | 4 + python-stdlib/datetime/setup.py | 24 + python-stdlib/datetime/test_datetime.py | 2269 +++++++++++++ unix-ffi/datetime/datetime.py | 2276 ------------- unix-ffi/datetime/metadata.txt | 3 - unix-ffi/datetime/setup.py | 24 - unix-ffi/datetime/test_datetime.py | 3881 ----------------------- 9 files changed, 3260 insertions(+), 6184 deletions(-) create mode 100644 python-stdlib/datetime/datetime.py create mode 100644 python-stdlib/datetime/localtz.patch create mode 100644 python-stdlib/datetime/metadata.txt create mode 100644 python-stdlib/datetime/setup.py create mode 100644 python-stdlib/datetime/test_datetime.py delete mode 100644 unix-ffi/datetime/datetime.py delete mode 100644 unix-ffi/datetime/metadata.txt delete mode 100644 unix-ffi/datetime/setup.py delete mode 100644 unix-ffi/datetime/test_datetime.py diff --git a/python-stdlib/datetime/datetime.py b/python-stdlib/datetime/datetime.py new file mode 100644 index 00000000..b3cd9b94 --- /dev/null +++ b/python-stdlib/datetime/datetime.py @@ -0,0 +1,879 @@ +# datetime.py + +import time as _tmod + +__version__ = "2.0.0" + +_DBM = (0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334) +_DIM = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) +_TIME_SPEC = ("auto", "hours", "minutes", "seconds", "milliseconds", "microseconds") + + +def _leap(y): + return y % 4 == 0 and (y % 100 != 0 or y % 400 == 0) + + +def _dby(y): + # year -> number of days before January 1st of year. + Y = y - 1 + return Y * 365 + Y // 4 - Y // 100 + Y // 400 + + +def _dim(y, m): + # year, month -> number of days in that month in that year. + if m == 2 and _leap(y): + return 29 + return _DIM[m] + + +def _dbm(y, m): + # year, month -> number of days in year preceding first day of month. + return _DBM[m] + (m > 2 and _leap(y)) + + +def _ymd2o(y, m, d): + # y, month, day -> ordinal, considering 01-Jan-0001 as day 1. + return _dby(y) + _dbm(y, m) + d + + +def _o2ymd(n): + # ordinal -> (year, month, day), considering 01-Jan-0001 as day 1. + n -= 1 + n400, n = divmod(n, 146_097) + y = n400 * 400 + 1 + n100, n = divmod(n, 36_524) + n4, n = divmod(n, 1_461) + n1, n = divmod(n, 365) + y += n100 * 100 + n4 * 4 + n1 + if n1 == 4 or n100 == 4: + return y - 1, 12, 31 + m = (n + 50) >> 5 + prec = _dbm(y, m) + if prec > n: + m -= 1 + prec -= _dim(y, m) + n -= prec + return y, m, n + 1 + + +MINYEAR = 1 +MAXYEAR = 9_999 + + +class timedelta: + def __init__( + self, days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0 + ): + s = (((weeks * 7 + days) * 24 + hours) * 60 + minutes) * 60 + seconds + self._us = round((s * 1000 + milliseconds) * 1000 + microseconds) + + def __repr__(self): + return "datetime.timedelta(microseconds={})".format(self._us) + + def total_seconds(self): + return self._us / 1_000_000 + + @property + def days(self): + return self._tuple(2)[0] + + @property + def seconds(self): + return self._tuple(3)[1] + + @property + def microseconds(self): + return self._tuple(3)[2] + + def __add__(self, other): + if isinstance(other, datetime): + return other.__add__(self) + else: + us = other._us + return timedelta(0, 0, self._us + us) + + def __sub__(self, other): + return timedelta(0, 0, self._us - other._us) + + def __neg__(self): + return timedelta(0, 0, -self._us) + + def __pos__(self): + return self + + def __abs__(self): + return -self if self._us < 0 else self + + def __mul__(self, other): + return timedelta(0, 0, round(other * self._us)) + + __rmul__ = __mul__ + + def __truediv__(self, other): + if isinstance(other, timedelta): + return self._us / other._us + else: + return timedelta(0, 0, round(self._us / other)) + + def __floordiv__(self, other): + if isinstance(other, timedelta): + return self._us // other._us + else: + return timedelta(0, 0, int(self._us // other)) + + def __mod__(self, other): + return timedelta(0, 0, self._us % other._us) + + def __divmod__(self, other): + q, r = divmod(self._us, other._us) + return q, timedelta(0, 0, r) + + def __eq__(self, other): + return self._us == other._us + + def __le__(self, other): + return self._us <= other._us + + def __lt__(self, other): + return self._us < other._us + + def __ge__(self, other): + return self._us >= other._us + + def __gt__(self, other): + return self._us > other._us + + def __bool__(self): + return self._us != 0 + + def __str__(self): + return self._format(0x40) + + def __hash__(self): + if not hasattr(self, "_hash"): + self._hash = hash(self._us) + return self._hash + + def isoformat(self): + return self._format(0) + + def _format(self, spec=0): + if self._us >= 0: + td = self + g = "" + else: + td = -self + g = "-" + d, h, m, s, us = td._tuple(5) + ms, us = divmod(us, 1000) + r = "" + if spec & 0x40: + spec &= ~0x40 + hr = str(h) + else: + hr = f"{h:02d}" + if spec & 0x20: + spec &= ~0x20 + spec |= 0x10 + r += "UTC" + if spec & 0x10: + spec &= ~0x10 + if not g: + g = "+" + if d: + p = "s" if d > 1 else "" + r += f"{g}{d} day{p}, " + g = "" + if spec == 0: + spec = 5 if (ms or us) else 3 + if spec >= 1 or h: + r += f"{g}{hr}" + if spec >= 2 or m: + r += f":{m:02d}" + if spec >= 3 or s: + r += f":{s:02d}" + if spec >= 4 or ms: + r += f".{ms:03d}" + if spec >= 5 or us: + r += f"{us:03d}" + return r + + def tuple(self): + return self._tuple(5) + + def _tuple(self, n): + d, us = divmod(self._us, 86_400_000_000) + if n == 2: + return d, us + s, us = divmod(us, 1_000_000) + if n == 3: + return d, s, us + h, s = divmod(s, 3600) + m, s = divmod(s, 60) + return d, h, m, s, us + + +timedelta.min = timedelta(days=-999_999_999) +timedelta.max = timedelta(days=999_999_999, hours=23, minutes=59, seconds=59, microseconds=999_999) +timedelta.resolution = timedelta(microseconds=1) + + +class tzinfo: + # abstract class + def tzname(self, dt): + raise NotImplementedError + + def utcoffset(self, dt): + raise NotImplementedError + + def dst(self, dt): + raise NotImplementedError + + def fromutc(self, dt): + if dt._tz is not self: + raise ValueError + + # See original datetime.py for an explanation of this algorithm. + dtoff = dt.utcoffset() + dtdst = dt.dst() + delta = dtoff - dtdst + if delta: + dt += delta + dtdst = dt.dst() + return dt + dtdst + + def isoformat(self, dt): + return self.utcoffset(dt)._format(0x12) + + +class timezone(tzinfo): + def __init__(self, offset, name=None): + if not (abs(offset._us) < 86_400_000_000): + raise ValueError + self._offset = offset + self._name = name + + def __repr__(self): + return "datetime.timezone({}, {})".format(repr(self._offset), repr(self._name)) + + def __eq__(self, other): + if isinstance(other, timezone): + return self._offset == other._offset + return NotImplemented + + def __str__(self): + return self.tzname(None) + + def __hash__(self): + if not hasattr(self, "_hash"): + self._hash = hash((self._offset, self._name)) + return self._hash + + def utcoffset(self, dt): + return self._offset + + def dst(self, dt): + return None + + def tzname(self, dt): + if self._name: + return self._name + return self._offset._format(0x22) + + def fromutc(self, dt): + return dt + self._offset + + +timezone.utc = timezone(timedelta(0)) + + +def _date(y, m, d): + if MINYEAR <= y <= MAXYEAR and 1 <= m <= 12 and 1 <= d <= _dim(y, m): + return _ymd2o(y, m, d) + elif y == 0 and m == 0 and 1 <= d <= 3_652_059: + return d + else: + raise ValueError + + +def _iso2d(s): # ISO -> date + if len(s) < 10 or s[4] != "-" or s[7] != "-": + raise ValueError + return int(s[0:4]), int(s[5:7]), int(s[8:10]) + + +def _d2iso(o): # date -> ISO + return "%04d-%02d-%02d" % _o2ymd(o) + + +class date: + def __init__(self, year, month, day): + self._ord = _date(year, month, day) + + @classmethod + def fromtimestamp(cls, ts): + return cls(*_tmod.localtime(ts)[:3]) + + @classmethod + def today(cls): + return cls(*_tmod.localtime()[:3]) + + @classmethod + def fromordinal(cls, n): + return cls(0, 0, n) + + @classmethod + def fromisoformat(cls, s): + return cls(*_iso2d(s)) + + @property + def year(self): + return self.tuple()[0] + + @property + def month(self): + return self.tuple()[1] + + @property + def day(self): + return self.tuple()[2] + + def toordinal(self): + return self._ord + + def timetuple(self): + y, m, d = self.tuple() + yday = _dbm(y, m) + d + return (y, m, d, 0, 0, 0, self.weekday(), yday, -1) + + def replace(self, year=None, month=None, day=None): + year_, month_, day_ = self.tuple() + if year is None: + year = year_ + if month is None: + month = month_ + if day is None: + day = day_ + return date(year, month, day) + + def __add__(self, other): + return date.fromordinal(self._ord + other.days) + + def __sub__(self, other): + if isinstance(other, date): + return timedelta(days=self._ord - other._ord) + else: + return date.fromordinal(self._ord - other.days) + + def __eq__(self, other): + if isinstance(other, date): + return self._ord == other._ord + else: + return False + + def __le__(self, other): + return self._ord <= other._ord + + def __lt__(self, other): + return self._ord < other._ord + + def __ge__(self, other): + return self._ord >= other._ord + + def __gt__(self, other): + return self._ord > other._ord + + def weekday(self): + return (self._ord + 6) % 7 + + def isoweekday(self): + return self._ord % 7 or 7 + + def isoformat(self): + return _d2iso(self._ord) + + def __repr__(self): + return "datetime.date(0, 0, {})".format(self._ord) + + __str__ = isoformat + + def __hash__(self): + if not hasattr(self, "_hash"): + self._hash = hash(self._ord) + return self._hash + + def tuple(self): + return _o2ymd(self._ord) + + +date.min = date(MINYEAR, 1, 1) +date.max = date(MAXYEAR, 12, 31) +date.resolution = timedelta(days=1) + + +def _time(h, m, s, us, fold): + if ( + 0 <= h < 24 + and 0 <= m < 60 + and 0 <= s < 60 + and 0 <= us < 1_000_000 + and (fold == 0 or fold == 1) + ) or (h == 0 and m == 0 and s == 0 and 0 < us < 86_400_000_000): + return timedelta(0, s, us, 0, m, h) + else: + raise ValueError + + +def _iso2t(s): + hour = 0 + minute = 0 + sec = 0 + usec = 0 + tz_sign = "" + tz_hour = 0 + tz_minute = 0 + tz_sec = 0 + tz_usec = 0 + l = len(s) + i = 0 + if l < 2: + raise ValueError + i += 2 + hour = int(s[i - 2 : i]) + if l > i and s[i] == ":": + i += 3 + if l - i < 0: + raise ValueError + minute = int(s[i - 2 : i]) + if l > i and s[i] == ":": + i += 3 + if l - i < 0: + raise ValueError + sec = int(s[i - 2 : i]) + if l > i and s[i] == ".": + i += 4 + if l - i < 0: + raise ValueError + usec = 1000 * int(s[i - 3 : i]) + if l > i and s[i] != "+": + i += 3 + if l - i < 0: + raise ValueError + usec += int(s[i - 3 : i]) + if l > i: + if s[i] not in "+-": + raise ValueError + tz_sign = s[i] + i += 6 + if l - i < 0: + raise ValueError + tz_hour = int(s[i - 5 : i - 3]) + tz_minute = int(s[i - 2 : i]) + if l > i and s[i] == ":": + i += 3 + if l - i < 0: + raise ValueError + tz_sec = int(s[i - 2 : i]) + if l > i and s[i] == ".": + i += 7 + if l - i < 0: + raise ValueError + tz_usec = int(s[i - 6 : i]) + if l != i: + raise ValueError + if tz_sign: + td = timedelta(hours=tz_hour, minutes=tz_minute, seconds=tz_sec, microseconds=tz_usec) + if tz_sign == "-": + td = -td + tz = timezone(td) + else: + tz = None + return hour, minute, sec, usec, tz + + +def _t2iso(td, timespec, dt, tz): + s = td._format(_TIME_SPEC.index(timespec)) + if tz is not None: + s += tz.isoformat(dt) + return s + + +class time: + def __init__(self, hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, fold=0): + self._td = _time(hour, minute, second, microsecond, fold) + self._tz = tzinfo + self._fd = fold + + @classmethod + def fromisoformat(cls, s): + return cls(*_iso2t(s)) + + @property + def hour(self): + return self.tuple()[0] + + @property + def minute(self): + return self.tuple()[1] + + @property + def second(self): + return self.tuple()[2] + + @property + def microsecond(self): + return self.tuple()[3] + + @property + def tzinfo(self): + return self._tz + + @property + def fold(self): + return self._fd + + def replace( + self, hour=None, minute=None, second=None, microsecond=None, tzinfo=True, *, fold=None + ): + h, m, s, us, tz, fl = self.tuple() + if hour is None: + hour = h + if minute is None: + minute = m + if second is None: + second = s + if microsecond is None: + microsecond = us + if tzinfo is True: + tzinfo = tz + if fold is None: + fold = fl + return time(hour, minute, second, microsecond, tzinfo, fold=fold) + + def isoformat(self, timespec="auto"): + return _t2iso(self._td, timespec, None, self._tz) + + def __repr__(self): + return "datetime.time(microsecond={}, tzinfo={}, fold={})".format( + self._td._us, repr(self._tz), self._fd + ) + + __str__ = isoformat + + def __bool__(self): + return True + + def __eq__(self, other): + if (self._tz == None) ^ (other._tz == None): + return False + return self._sub(other) == 0 + + def __le__(self, other): + return self._sub(other) <= 0 + + def __lt__(self, other): + return self._sub(other) < 0 + + def __ge__(self, other): + return self._sub(other) >= 0 + + def __gt__(self, other): + return self._sub(other) > 0 + + def _sub(self, other): + tz1 = self._tz + if (tz1 is None) ^ (other._tz is None): + raise TypeError + us1 = self._td._us + us2 = other._td._us + if tz1 is not None: + os1 = self.utcoffset()._us + os2 = other.utcoffset()._us + if os1 != os2: + us1 -= os1 + us2 -= os2 + return us1 - us2 + + def __hash__(self): + if not hasattr(self, "_hash"): + # fold doesn't make any difference + self._hash = hash((self._td, self._tz)) + return self._hash + + def utcoffset(self): + return None if self._tz is None else self._tz.utcoffset(None) + + def dst(self): + return None if self._tz is None else self._tz.dst(None) + + def tzname(self): + return None if self._tz is None else self._tz.tzname(None) + + def tuple(self): + d, h, m, s, us = self._td.tuple() + return h, m, s, us, self._tz, self._fd + + +time.min = time(0) +time.max = time(23, 59, 59, 999_999) +time.resolution = timedelta.resolution + + +class datetime: + def __init__( + self, year, month, day, hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, fold=0 + ): + self._d = _date(year, month, day) + self._t = _time(hour, minute, second, microsecond, fold) + self._tz = tzinfo + self._fd = fold + + @classmethod + def fromtimestamp(cls, ts, tz=None): + if isinstance(ts, float): + ts, us = divmod(round(ts * 1_000_000), 1_000_000) + else: + us = 0 + if tz is None: + raise NotImplementedError + else: + dt = cls(*_tmod.gmtime(ts)[:6], microsecond=us, tzinfo=tz) + dt = tz.fromutc(dt) + return dt + + @classmethod + def now(cls, tz=None): + return cls.fromtimestamp(_tmod.time(), tz) + + @classmethod + def fromordinal(cls, n): + return cls(0, 0, n) + + @classmethod + def fromisoformat(cls, s): + d = _iso2d(s) + if len(s) <= 12: + return cls(*d) + t = _iso2t(s[11:]) + return cls(*(d + t)) + + @classmethod + def combine(cls, date, time, tzinfo=None): + return cls( + 0, 0, date.toordinal(), 0, 0, 0, time._td._us, tzinfo or time._tz, fold=time._fd + ) + + @property + def year(self): + return _o2ymd(self._d)[0] + + @property + def month(self): + return _o2ymd(self._d)[1] + + @property + def day(self): + return _o2ymd(self._d)[2] + + @property + def hour(self): + return self._t.tuple()[1] + + @property + def minute(self): + return self._t.tuple()[2] + + @property + def second(self): + return self._t.tuple()[3] + + @property + def microsecond(self): + return self._t.tuple()[4] + + @property + def tzinfo(self): + return self._tz + + @property + def fold(self): + return self._fd + + def __add__(self, other): + us = self._t._us + other._us + d, us = divmod(us, 86_400_000_000) + d += self._d + return datetime(0, 0, d, 0, 0, 0, us, self._tz) + + def __sub__(self, other): + if isinstance(other, timedelta): + return self.__add__(-other) + elif isinstance(other, datetime): + d, us = self._sub(other) + return timedelta(d, 0, us) + else: + raise TypeError + + def _sub(self, other): + # Subtract two datetime instances. + tz1 = self._tz + if (tz1 is None) ^ (other._tz is None): + raise TypeError + dt1 = self + dt2 = other + if tz1 is not None: + os1 = dt1.utcoffset() + os2 = dt2.utcoffset() + if os1 != os2: + dt1 -= os1 + dt2 -= os2 + D = dt1._d - dt2._d + us = dt1._t._us - dt2._t._us + d, us = divmod(us, 86_400_000_000) + return D + d, us + + def __eq__(self, other): + if (self._tz == None) ^ (other._tz == None): + return False + return self._cmp(other) == 0 + + def __le__(self, other): + return self._cmp(other) <= 0 + + def __lt__(self, other): + return self._cmp(other) < 0 + + def __ge__(self, other): + return self._cmp(other) >= 0 + + def __gt__(self, other): + return self._cmp(other) > 0 + + def _cmp(self, other): + # Compare two datetime instances. + d, us = self._sub(other) + if d < 0: + return -1 + if d > 0: + return 1 + + if us < 0: + return -1 + if us > 0: + return 1 + + return 0 + + def date(self): + return date.fromordinal(self._d) + + def time(self): + return time(microsecond=self._t._us, fold=self._fd) + + def timetz(self): + return time(microsecond=self._t._us, tzinfo=self._tz, fold=self._fd) + + def replace( + self, + year=None, + month=None, + day=None, + hour=None, + minute=None, + second=None, + microsecond=None, + tzinfo=True, + *, + fold=None, + ): + Y, M, D, h, m, s, us, tz, fl = self.tuple() + if year is None: + year = Y + if month is None: + month = M + if day is None: + day = D + if hour is None: + hour = h + if minute is None: + minute = m + if second is None: + second = s + if microsecond is None: + microsecond = us + if tzinfo is True: + tzinfo = tz + if fold is None: + fold = fl + return datetime(year, month, day, hour, minute, second, microsecond, tzinfo, fold=fold) + + def astimezone(self, tz=None): + if self._tz is tz: + return self + _tz = self._tz + if _tz is None: + raise NotImplementedError + else: + os = _tz.utcoffset(self) + utc = self - os + utc = utc.replace(tzinfo=tz) + return tz.fromutc(utc) + + def utcoffset(self): + return None if self._tz is None else self._tz.utcoffset(self) + + def dst(self): + return None if self._tz is None else self._tz.dst(self) + + def tzname(self): + return None if self._tz is None else self._tz.tzname(self) + + def timetuple(self): + if self._tz is None: + conv = _tmod.gmtime + epoch = datetime.EPOCH.replace(tzinfo=None) + else: + conv = _tmod.localtime + epoch = datetime.EPOCH + return conv(round((self - epoch).total_seconds())) + + def toordinal(self): + return self._d + + def timestamp(self): + if self._tz is None: + raise NotImplementedError + else: + return (self - datetime.EPOCH).total_seconds() + + def weekday(self): + return (self._d + 6) % 7 + + def isoweekday(self): + return self._d % 7 or 7 + + def isoformat(self, sep="T", timespec="auto"): + return _d2iso(self._d) + sep + _t2iso(self._t, timespec, self, self._tz) + + def __repr__(self): + Y, M, D, h, m, s, us, tz, fold = self.tuple() + tz = repr(tz) + return "datetime.datetime({}, {}, {}, {}, {}, {}, {}, {}, fold={})".format( + Y, M, D, h, m, s, us, tz, fold + ) + + def __str__(self): + return self.isoformat(" ") + + def __hash__(self): + if not hasattr(self, "_hash"): + self._hash = hash((self._d, self._t, self._tz)) + return self._hash + + def tuple(self): + d = _o2ymd(self._d) + t = self._t.tuple()[1:] + return d + t + (self._tz, self._fd) + + +datetime.EPOCH = datetime(*_tmod.gmtime(0)[:6], tzinfo=timezone.utc) diff --git a/python-stdlib/datetime/localtz.patch b/python-stdlib/datetime/localtz.patch new file mode 100644 index 00000000..7a2449d5 --- /dev/null +++ b/python-stdlib/datetime/localtz.patch @@ -0,0 +1,84 @@ +localtz.patch + +The CPython's implementation of `datetime.fromtimestamp()`, +`datetime.astimezone()` and `datetime.timestamp()` for naive datetime objects +relay on proper management of DST (daylight saving time) by `time.localtime()` +for the timezone of interest. In the Unix port of MicroPython, this is +accomplished by properly setting the TZ environment variable, e.g. +`os.putenv("TZ", "Europe/Rome")`. + +Because real boards often lack a supportive `time.localtime()`, the source code +in `datetime.py` has been removed as to save precious resources. If your board +provide a proper implementation, you can restore the support to naive datetime +objects by applying this patch, e.g. `patch -p1 < localtz.patch`. + +--- a/datetime.py ++++ b/datetime.py +@@ -635,7 +635,10 @@ class datetime: + else: + us = 0 + if tz is None: +- raise NotImplementedError ++ dt = cls(*_tmod.localtime(ts)[:6], microsecond=us, tzinfo=tz) ++ s = (dt - datetime(*_tmod.localtime(ts - 86400)[:6]))._us // 1_000_000 - 86400 ++ if s < 0 and dt == datetime(*_tmod.localtime(ts + s)[:6]): ++ dt._fd = 1 + else: + dt = cls(*_tmod.gmtime(ts)[:6], microsecond=us, tzinfo=tz) + dt = tz.fromutc(dt) +@@ -812,13 +815,45 @@ class datetime: + return self + _tz = self._tz + if _tz is None: +- raise NotImplementedError ++ ts = int(self._mktime()) ++ os = datetime(*_tmod.localtime(ts)[:6]) - datetime(*_tmod.gmtime(ts)[:6]) + else: + os = _tz.utcoffset(self) + utc = self - os + utc = utc.replace(tzinfo=tz) + return tz.fromutc(utc) + ++ def _mktime(self): ++ def local(u): ++ return (datetime(*_tmod.localtime(u)[:6]) - epoch)._us // 1_000_000 ++ ++ epoch = datetime.EPOCH.replace(tzinfo=None) ++ t, us = divmod((self - epoch)._us, 1_000_000) ++ ts = None ++ ++ a = local(t) - t ++ u1 = t - a ++ t1 = local(u1) ++ if t1 == t: ++ u2 = u1 + (86400 if self.fold else -86400) ++ b = local(u2) - u2 ++ if a == b: ++ ts = u1 ++ else: ++ b = t1 - u1 ++ if ts is None: ++ u2 = t - b ++ t2 = local(u2) ++ if t2 == t: ++ ts = u2 ++ elif t1 == t: ++ ts = u1 ++ elif self.fold: ++ ts = min(u1, u2) ++ else: ++ ts = max(u1, u2) ++ return ts + us / 1_000_000 ++ + def utcoffset(self): + return None if self._tz is None else self._tz.utcoffset(self) + +@@ -842,7 +877,7 @@ class datetime: + + def timestamp(self): + if self._tz is None: +- raise NotImplementedError ++ return self._mktime() + else: + return (self - datetime.EPOCH).total_seconds() + diff --git a/python-stdlib/datetime/metadata.txt b/python-stdlib/datetime/metadata.txt new file mode 100644 index 00000000..ee879327 --- /dev/null +++ b/python-stdlib/datetime/metadata.txt @@ -0,0 +1,4 @@ +srctype = micropython-lib +type = module +version = 4.0.0 +author = Lorenzo Cappelletti diff --git a/python-stdlib/datetime/setup.py b/python-stdlib/datetime/setup.py new file mode 100644 index 00000000..e925aa2f --- /dev/null +++ b/python-stdlib/datetime/setup.py @@ -0,0 +1,24 @@ +import sys + +# Remove current dir from sys.path, otherwise setuptools will peek up our +# module instead of system's. +sys.path.pop(0) +from setuptools import setup + +sys.path.append("..") +import sdist_upip + +setup( + name="micropython-datetime", + version="4.0.0", + description="datetime module for MicroPython", + long_description="This is a module reimplemented specifically for MicroPython standard library,\nwith efficient and lean design in mind. Note that this module is likely work\nin progress and likely supports just a subset of CPython's corresponding\nmodule. Please help with the development if you are interested in this\nmodule.", + url="https://github.com/micropython/micropython-lib", + author="micropython-lib Developers", + author_email="micro-python@googlegroups.com", + maintainer="micropython-lib Developers", + maintainer_email="micro-python@googlegroups.com", + license="MIT", + cmdclass={"sdist": sdist_upip.sdist}, + py_modules=["datetime"], +) diff --git a/python-stdlib/datetime/test_datetime.py b/python-stdlib/datetime/test_datetime.py new file mode 100644 index 00000000..372bdf3d --- /dev/null +++ b/python-stdlib/datetime/test_datetime.py @@ -0,0 +1,2269 @@ +# See https://github.com/python/cpython/blob/3.9/Lib/test/datetimetester.py +# +# This script can be run in 3 different modes: +# 1. `python3 test_datetime.py --stdlib`: checks that the tests comply to +# CPython's standard datetime library. +# 2. `python3 test_datetime.py`: runs the tests against datetime.py, using +# CPython's standard unittest (which accepts filter options, such as +# `-v TestTimeDelta -k tuple`, and provides more verbose output in case +# of failure). +# 3. `micropython test_datetime.py`: runs the tests against datetime.py +# using MicroPython's unittest library (which must be available). +# +# This script also accepts option `--reorder` which rewrites this file +# in-place by numbering tests in sequence. + +import sys + +STDLIB = False + +if __name__ == "__main__": + while len(sys.argv) > 1: + if sys.argv[1] == "--reorder": + import fileinput, re + + with fileinput.input(files=sys.argv[0], inplace=True) as f: + cases = {} + n = 0 + for line in f: + match = re.match("(\s+def\s+test_)(\w+?)(?:\d+)(\(.+\):)", line) + if match: + prefix, name, suffix = match.groups() + if name != last_name: + if name in cases[case]: + sys.exit( + f"duplicated test in {case} at line {fileinput.filelineno()}: {name}" + ) + cases[case].append(name) + last_name = name + i = 0 + print(f"{prefix}{name}{i:02d}{suffix}") + i += 1 + n += 1 + continue + + match = re.match("class\s+(Test[\w\d]+)\(", line) + if match: + case = match[1] + if case in cases: + sys.exit( + f"duplicated test case at line {fileinput.filelineno()}: {case}" + ) + cases[case] = [] + last_name = "" + + print(line, end="") + print(f"Reordered {n} tests in {len(cases)} cases") + elif sys.argv[1] == "--stdlib": + sys.path.pop(0) + STDLIB = True + else: + break + sys.argv.pop(1) + +import os +import time as mod_time +import datetime as mod_datetime +from datetime import MAXYEAR, MINYEAR, datetime, date, time, timedelta, timezone, tzinfo +import unittest + + +# See localtz.patch +try: + datetime.fromtimestamp(0) + LOCALTZ = True +except NotImplementedError: + LOCALTZ = False + + +if hasattr(datetime, "EPOCH"): + EPOCH = datetime.EPOCH +else: + EPOCH = datetime(*mod_time.gmtime(0)[:6], tzinfo=timezone.utc) + + +def eval_mod(s): + return eval(s.replace("datetime.", "mod_datetime.")) + + +### timedelta ################################################################ + +a = timedelta(hours=7) +b = timedelta(minutes=6) +c = timedelta(seconds=10) +us = timedelta(microseconds=1) +td0 = timedelta(0) +td1 = timedelta(2, 3, 4) +td2 = timedelta(2, 3, 4) +td3 = timedelta(2, 3, 5) +td4 = timedelta( + days=100, + weeks=-7, + hours=-24 * (100 - 49), + minutes=-3, + seconds=12, + microseconds=(3 * 60 - 12) * 1000000, +) # == timedelta(0) + +td1h = timedelta(hours=1) +td1hr = "datetime.timedelta(microseconds={})".format(1 * 3600 * 10**6) +td10h2m = timedelta(hours=10, minutes=2) +td10h2mr = "datetime.timedelta(microseconds={})".format((10 * 3600 + 2 * 60) * 10**6) +tdn10h2m40s = timedelta(hours=-10, minutes=2, seconds=40) +tdn10h2m40sr = "datetime.timedelta(microseconds={})".format((-10 * 3600 + 2 * 60 + 40) * 10**6) +td1h2m40s100us = timedelta(hours=1, minutes=2, seconds=40, microseconds=100) +td1h2m40s100usr = "datetime.timedelta(microseconds={})".format( + (1 * 3600 + 2 * 60 + 40) * 10**6 + 100 +) + + +class Test0TimeDelta(unittest.TestCase): + def test___init__00(self): + self.assertEqual(timedelta(), timedelta(weeks=0, days=0, hours=0, minutes=0, seconds=0)) + + def test___init__01(self): + self.assertEqual(timedelta(weeks=1), timedelta(days=7)) + + def test___init__02(self): + self.assertEqual(timedelta(days=1), timedelta(hours=24)) + + def test___init__03(self): + self.assertEqual(timedelta(hours=1), timedelta(minutes=60)) + + def test___init__04(self): + self.assertEqual(timedelta(minutes=1), timedelta(seconds=60)) + + def test___init__05(self): + self.assertEqual(timedelta(seconds=1), timedelta(milliseconds=1000)) + + def test___init__06(self): + self.assertEqual(timedelta(milliseconds=1), timedelta(microseconds=1000)) + + def test___init__07(self): + self.assertEqual(timedelta(weeks=1.0 / 7), timedelta(days=1)) + + def test___init__08(self): + self.assertEqual(timedelta(days=1.0 / 24), timedelta(hours=1)) + + def test___init__09(self): + self.assertEqual(timedelta(hours=1.0 / 60), timedelta(minutes=1)) + + def test___init__10(self): + self.assertEqual(timedelta(minutes=1.0 / 60), timedelta(seconds=1)) + + def test___init__11(self): + self.assertEqual(timedelta(seconds=0.001), timedelta(milliseconds=1)) + + def test___init__12(self): + self.assertEqual(timedelta(milliseconds=0.001), timedelta(microseconds=1)) + + def test___init__13(self): + self.assertEqual(td1h, eval_mod(td1hr)) + + def test___init__14(self): + self.assertEqual(td10h2m, eval_mod(td10h2mr)) + + def test___init__15(self): + self.assertEqual(tdn10h2m40s, eval_mod(tdn10h2m40sr)) + + def test___init__16(self): + self.assertEqual(td1h2m40s100us, eval_mod(td1h2m40s100usr)) + + @unittest.skipIf(STDLIB, "standard datetime differs") + def test___repr__00(self): + self.assertEqual(repr(td1h), td1hr) + + @unittest.skipIf(STDLIB, "standard datetime differs") + def test___repr__01(self): + self.assertEqual(repr(td10h2m), td10h2mr) + + @unittest.skipIf(STDLIB, "standard datetime differs") + def test___repr__02(self): + self.assertEqual(repr(tdn10h2m40s), tdn10h2m40sr) + + @unittest.skipIf(STDLIB, "standard datetime differs") + def test___repr__03(self): + self.assertEqual(repr(td1h2m40s100us), td1h2m40s100usr) + + def test___repr__04(self): + self.assertEqual(td1, eval_mod(repr(td1))) + + def test_total_seconds00(self): + d = timedelta(days=365) + self.assertEqual(d.total_seconds(), 31536000.0) + + def test_days00(self): + self.assertEqual(td1.days, 2) + + def test_seconds00(self): + self.assertEqual(td1.seconds, 3) + + def test_microseconds00(self): + self.assertEqual(td1.microseconds, 4) + + def test___add__00(self): + self.assertEqual(a + b + c, timedelta(hours=7, minutes=6, seconds=10)) + + def test___add__01(self): + dt = a + datetime(2010, 1, 1, 12, 30) + self.assertEqual(dt, datetime(2010, 1, 1, 12 + 7, 30)) + + def test___sub__00(self): + self.assertEqual(a - b, timedelta(hours=6, minutes=60 - 6)) + + def test___neg__00(self): + self.assertEqual(-a, timedelta(hours=-7)) + + def test___neg__01(self): + self.assertEqual(-b, timedelta(hours=-1, minutes=54)) + + def test___neg__02(self): + self.assertEqual(-c, timedelta(hours=-1, minutes=59, seconds=50)) + + def test___pos__00(self): + self.assertEqual(+a, timedelta(hours=7)) + + def test___abs__00(self): + self.assertEqual(abs(a), a) + + def test___abs__01(self): + self.assertEqual(abs(-a), a) + + def test___mul__00(self): + self.assertEqual(a * 10, timedelta(hours=70)) + + def test___mul__01(self): + self.assertEqual(a * 10, 10 * a) + + def test___mul__02(self): + self.assertEqual(b * 10, timedelta(minutes=60)) + + def test___mul__03(self): + self.assertEqual(10 * b, timedelta(minutes=60)) + + def test___mul__04(self): + self.assertEqual(c * 10, timedelta(seconds=100)) + + def test___mul__05(self): + self.assertEqual(10 * c, timedelta(seconds=100)) + + def test___mul__06(self): + self.assertEqual(a * -1, -a) + + def test___mul__07(self): + self.assertEqual(b * -2, -b - b) + + def test___mul__08(self): + self.assertEqual(c * -2, -c + -c) + + def test___mul__09(self): + self.assertEqual(b * (60 * 24), (b * 60) * 24) + + def test___mul__10(self): + self.assertEqual(b * (60 * 24), (60 * b) * 24) + + def test___mul__11(self): + self.assertEqual(c * 6, timedelta(minutes=1)) + + def test___mul__12(self): + self.assertEqual(6 * c, timedelta(minutes=1)) + + def test___truediv__00(self): + self.assertEqual(a / 0.5, timedelta(hours=14)) + + def test___truediv__01(self): + self.assertEqual(b / 0.5, timedelta(minutes=12)) + + def test___truediv__02(self): + self.assertEqual(a / 7, timedelta(hours=1)) + + def test___truediv__03(self): + self.assertEqual(b / 6, timedelta(minutes=1)) + + def test___truediv__04(self): + self.assertEqual(c / 10, timedelta(seconds=1)) + + def test___truediv__05(self): + self.assertEqual(a / 10, timedelta(minutes=7 * 6)) + + def test___truediv__06(self): + self.assertEqual(a / 3600, timedelta(seconds=7)) + + def test___truediv__07(self): + self.assertEqual(a / a, 1.0) + + def test___truediv__08(self): + t = timedelta(hours=1, minutes=24, seconds=19) + second = timedelta(seconds=1) + self.assertEqual(t / second, 5059.0) + + def test___truediv__09(self): + t = timedelta(minutes=2, seconds=30) + minute = timedelta(minutes=1) + self.assertEqual(t / minute, 2.5) + + def test___floordiv__00(self): + self.assertEqual(a // 7, timedelta(hours=1)) + + def test___floordiv__01(self): + self.assertEqual(b // 6, timedelta(minutes=1)) + + def test___floordiv__02(self): + self.assertEqual(c // 10, timedelta(seconds=1)) + + def test___floordiv__03(self): + self.assertEqual(a // 10, timedelta(minutes=7 * 6)) + + def test___floordiv__04(self): + self.assertEqual(a // 3600, timedelta(seconds=7)) + + def test___floordiv__05(self): + t = timedelta(hours=1, minutes=24, seconds=19) + second = timedelta(seconds=1) + self.assertEqual(t // second, 5059) + + def test___floordiv__06(self): + t = timedelta(minutes=2, seconds=30) + minute = timedelta(minutes=1) + self.assertEqual(t // minute, 2) + + def test___mod__00(self): + t = timedelta(minutes=2, seconds=30) + r = t % timedelta(minutes=1) + self.assertEqual(r, timedelta(seconds=30)) + + def test___mod__01(self): + t = timedelta(minutes=-2, seconds=30) + r = t % timedelta(minutes=1) + self.assertEqual(r, timedelta(seconds=30)) + + def test___divmod__00(self): + t = timedelta(minutes=2, seconds=30) + q, r = divmod(t, timedelta(minutes=1)) + self.assertEqual(q, 2) + self.assertEqual(r, timedelta(seconds=30)) + + def test___divmod__01(self): + t = timedelta(minutes=-2, seconds=30) + q, r = divmod(t, timedelta(minutes=1)) + self.assertEqual(q, -2) + self.assertEqual(r, timedelta(seconds=30)) + + def test___eq__00(self): + self.assertEqual(td1, td2) + + def test___eq__01(self): + self.assertTrue(not td1 != td2) + + def test___eq__02(self): + self.assertEqual(timedelta(hours=6, minutes=60), a) + + def test___eq__03(self): + self.assertEqual(timedelta(seconds=60 * 6), b) + + def test___eq__04(self): + self.assertTrue(not td1 == td3) + + def test___eq__05(self): + self.assertTrue(td1 != td3) + + def test___eq__06(self): + self.assertTrue(td3 != td1) + + def test___eq__07(self): + self.assertTrue(not td3 == td1) + + def test___le__00(self): + self.assertTrue(td1 <= td2) + + def test___le__01(self): + self.assertTrue(td1 <= td3) + + def test___le__02(self): + self.assertTrue(not td3 <= td1) + + def test___lt__00(self): + self.assertTrue(not td1 < td2) + + def test___lt__01(self): + self.assertTrue(td1 < td3) + + def test___lt__02(self): + self.assertTrue(not td3 < td1) + + def test___ge__00(self): + self.assertTrue(td1 >= td2) + + def test___ge__01(self): + self.assertTrue(td3 >= td1) + + def test___ge__02(self): + self.assertTrue(not td1 >= td3) + + def test___gt__00(self): + self.assertTrue(not td1 > td2) + + def test___gt__01(self): + self.assertTrue(td3 > td1) + + def test___gt__02(self): + self.assertTrue(not td1 > td3) + + def test___bool__00(self): + self.assertTrue(timedelta(hours=1)) + + def test___bool__01(self): + self.assertTrue(timedelta(minutes=1)) + + def test___bool__02(self): + self.assertTrue(timedelta(seconds=1)) + + def test___bool__03(self): + self.assertTrue(not td0) + + def test___str__00(self): + self.assertEqual(str(timedelta(days=1)), "1 day, 0:00:00") + + def test___str__01(self): + self.assertEqual(str(timedelta(days=-1)), "-1 day, 0:00:00") + + def test___str__02(self): + self.assertEqual(str(timedelta(days=2)), "2 days, 0:00:00") + + def test___str__03(self): + self.assertEqual(str(timedelta(days=-2)), "-2 days, 0:00:00") + + def test___str__04(self): + self.assertEqual(str(timedelta(hours=12, minutes=58, seconds=59)), "12:58:59") + + def test___str__05(self): + self.assertEqual(str(timedelta(hours=2, minutes=3, seconds=4)), "2:03:04") + + def test___hash__00(self): + self.assertEqual(td0, td4) + self.assertEqual(hash(td0), hash(td4)) + + def test___hash__01(self): + tt0 = td0 + timedelta(weeks=7) + tt4 = td4 + timedelta(days=7 * 7) + self.assertEqual(hash(tt0), hash(tt4)) + + def test___hash__02(self): + d = {td0: 1} + d[td4] = 2 + self.assertEqual(len(d), 1) + self.assertEqual(d[td0], 2) + + def test_constant00(self): + self.assertIsInstance(timedelta.min, timedelta) + self.assertIsInstance(timedelta.max, timedelta) + self.assertIsInstance(timedelta.resolution, timedelta) + self.assertTrue(timedelta.max > timedelta.min) + + def test_constant01(self): + self.assertEqual(timedelta.min, timedelta(days=-999_999_999)) + + def test_constant02(self): + self.assertEqual( + timedelta.max, + timedelta(days=999_999_999, seconds=24 * 3600 - 1, microseconds=10**6 - 1), + ) + + def test_constant03(self): + self.assertEqual(timedelta.resolution, timedelta(microseconds=1)) + + def test_computation00(self): + self.assertEqual((3 * us) * 0.5, 2 * us) + + def test_computation01(self): + self.assertEqual((5 * us) * 0.5, 2 * us) + + def test_computation02(self): + self.assertEqual(0.5 * (3 * us), 2 * us) + + def test_computation03(self): + self.assertEqual(0.5 * (5 * us), 2 * us) + + def test_computation04(self): + self.assertEqual((-3 * us) * 0.5, -2 * us) + + def test_computation05(self): + self.assertEqual((-5 * us) * 0.5, -2 * us) + + def test_computation06(self): + self.assertEqual((3 * us) / 2, 2 * us) + + def test_computation07(self): + self.assertEqual((5 * us) / 2, 2 * us) + + def test_computation08(self): + self.assertEqual((-3 * us) / 2.0, -2 * us) + + def test_computation09(self): + self.assertEqual((-5 * us) / 2.0, -2 * us) + + def test_computation10(self): + self.assertEqual((3 * us) / -2, -2 * us) + + def test_computation11(self): + self.assertEqual((5 * us) / -2, -2 * us) + + def test_computation12(self): + self.assertEqual((3 * us) / -2.0, -2 * us) + + def test_computation13(self): + self.assertEqual((5 * us) / -2.0, -2 * us) + + def test_computation14(self): + for i in range(-10, 10): + # with self.subTest(i=i): not supported by Micropython + self.assertEqual((i * us / 3) // us, round(i / 3)) + + def test_computation15(self): + for i in range(-10, 10): + # with self.subTest(i=i): not supported by Micropython + self.assertEqual((i * us / -3) // us, round(i / -3)) + + def test_carries00(self): + td1 = timedelta( + days=100, + weeks=-7, + hours=-24 * (100 - 49), + minutes=-3, + seconds=3 * 60 + 1, + ) + td2 = timedelta(seconds=1) + self.assertEqual(td1, td2) + + def test_resolution00(self): + self.assertIsInstance(timedelta.min, timedelta) + + def test_resolution01(self): + self.assertIsInstance(timedelta.max, timedelta) + + def test_resolution02(self): + self.assertIsInstance(timedelta.resolution, timedelta) + + def test_resolution03(self): + self.assertTrue(timedelta.max > timedelta.min) + + def test_resolution04(self): + self.assertEqual(timedelta.resolution, timedelta(microseconds=1)) + + @unittest.skipIf(STDLIB, "standard timedelta has no tuple()") + def test_tuple00(self): + self.assertEqual(td1.tuple(), (2, 0, 0, 3, 4)) + + @unittest.skipIf(STDLIB, "standard timedelta has no tuple()") + def test_tuple01(self): + self.assertEqual(td1h2m40s100us.tuple(), (0, 1, 2, 40, 100)) + + +### timezone ################################################################# + + +class Cet(tzinfo): + # Central European Time (see https://en.wikipedia.org/wiki/Summer_time_in_Europe) + + def utcoffset(self, dt): + h = 2 if self.isdst(dt)[0] else 1 + return timedelta(hours=h) + + def dst(self, dt): + h = 1 if self.isdst(dt)[0] else 0 + return timedelta(hours=h) + + def tzname(self, dt): + return "CEST" if self.isdst(dt)[0] else "CET" + + def fromutc(self, dt): + assert dt.tzinfo is self + isdst, fold = self.isdst(dt, utc=True) + h = 2 if isdst else 1 + dt += timedelta(hours=h) + dt = dt.replace(fold=fold) + return dt + + def isdst(self, dt, utc=False): + if dt is None: + return False, None + + year = dt.year + if not 2000 <= year < 2100: + # Formulas below are valid in the range [2000; 2100) + raise ValueError + + hour = 1 if utc else 3 + day = 31 - (5 * year // 4 + 4) % 7 # last Sunday of March + beg = datetime(year, 3, day, hour) + day = 31 - (5 * year // 4 + 1) % 7 # last Sunday of October + end = datetime(year, 10, day, hour) + + dt = dt.replace(tzinfo=None) + if utc: + fold = 1 if end <= dt < end + timedelta(hours=1) else 0 + else: + fold = dt.fold + isdst = beg <= dt < end + return isdst, fold + + def __repr__(self): + return "Cet()" + + def __str__(self): + return self.tzname(None) + + def __eq__(self, other): + return repr(self) == repr(other) + + def __hash__(self): + return hash(repr(self)) + + +class USTimeZone(tzinfo): + DSTSTART = datetime(1, 3, 8, 2) + DSTEND = datetime(1, 11, 1, 2) + ZERO = timedelta(0) + HOUR = timedelta(hours=1) + + def __init__(self, hours, reprname, stdname, dstname): + self.stdoffset = timedelta(hours=hours) + self.reprname = reprname + self.stdname = stdname + self.dstname = dstname + + def __repr__(self): + return self.reprname + + def tzname(self, dt): + if self.dst(dt): + return self.dstname + else: + return self.stdname + + def utcoffset(self, dt): + return self.stdoffset + self.dst(dt) + + def dst(self, dt): + if dt is None or dt.tzinfo is None: + return self.ZERO + assert dt.tzinfo is self + start, end = USTimeZone.us_dst_range(dt.year) + dt = dt.replace(tzinfo=None) + if start + self.HOUR <= dt < end - self.HOUR: + return self.HOUR + if end - self.HOUR <= dt < end: + return self.ZERO if dt.fold else self.HOUR + if start <= dt < start + self.HOUR: + return self.HOUR if dt.fold else self.ZERO + return self.ZERO + + def fromutc(self, dt): + assert dt.tzinfo is self + start, end = USTimeZone.us_dst_range(dt.year) + start = start.replace(tzinfo=self) + end = end.replace(tzinfo=self) + std_time = dt + self.stdoffset + dst_time = std_time + self.HOUR + if end <= dst_time < end + self.HOUR: + return std_time.replace(fold=1) + if std_time < start or dst_time >= end: + return std_time + if start <= std_time < end - self.HOUR: + return dst_time + + @staticmethod + def us_dst_range(year): + start = first_sunday_on_or_after(USTimeZone.DSTSTART.replace(year=year)) + end = first_sunday_on_or_after(USTimeZone.DSTEND.replace(year=year)) + return start, end + + @staticmethod + def first_sunday_on_or_after(dt): + days_to_go = 6 - dt.weekday() + if days_to_go: + dt += timedelta(days_to_go) + return dt + + +class LocalTz: + def __init__(self, tz): + self.tz = tz + self._old = None + + @staticmethod + def _set(tz): + if hasattr(mod_time, "tzset"): # Python + if tz: + os.environ["TZ"] = tz + else: + del os.environ["TZ"] + mod_time.tzset() + else: + if tz: + os.putenv("TZ", tz) + else: + os.unsetenv("TZ") + + def set(self): + self._old = os.getenv("TZ") + LocalTz._set(self.tz) + + def unset(self): + LocalTz._set(self._old) + self._old = None + + def __enter__(self): + self.set() + + def __exit__(self, typ, value, trace): + self.unset() + + +tz_acdt = timezone(timedelta(hours=9.5), "ACDT") +tz_est = timezone(-timedelta(hours=5), "EST") +tz1 = timezone(timedelta(hours=-1)) +tz2 = Cet() +tz3 = USTimeZone(-5, "Eastern", "EST", "EDT") + + +class Test1TimeZone(unittest.TestCase): + def test___init__00(self): + self.assertEqual(str(tz_acdt), "ACDT") + self.assertEqual(str(tz_acdt), tz_acdt.tzname(None)) + + def test___init__01(self): + self.assertEqual(str(tz_est), "EST") + self.assertEqual(str(tz_est), tz_est.tzname(None)) + + def test___init__02(self): + self.assertEqual(str(tz1), "UTC-01:00") + self.assertEqual(str(tz1), tz1.tzname(None)) + + def test___init__03(self): + self.assertEqual(str(tz2), "CET") + self.assertEqual(str(tz2), tz2.tzname(None)) + + def test___init__04(self): + offset = timedelta(hours=-24, microseconds=1) + tz = timezone(offset) + self.assertIsInstance(tz, timezone) + + def test___init__05(self): + offset = timedelta(hours=24, microseconds=-1) + tz = timezone(offset) + self.assertIsInstance(tz, timezone) + + def test___init__06(self): + offset = timedelta(hours=-24) + self.assertRaises(ValueError, timezone, offset) + + def test___init__07(self): + offset = timedelta(hours=24) + self.assertRaises(ValueError, timezone, offset) + + def test___repr__00(self): + self.assertEqual(tz1, eval_mod(repr(tz1))) + + def test___eq__00(self): + self.assertEqual(timezone(timedelta(hours=1)), timezone(timedelta(hours=1))) + + def test___eq__01(self): + self.assertNotEqual(timezone(timedelta(hours=1)), timezone(timedelta(hours=2))) + + def test___eq__02(self): + self.assertEqual(timezone(timedelta(hours=-5)), timezone(timedelta(hours=-5), "EST")) + + def test_utcoffset00(self): + self.assertEqual(str(tz2.utcoffset(None)), "1:00:00") + + def test_utcoffset01(self): + self.assertEqual(str(tz2.utcoffset(datetime(2010, 3, 27, 12))), "1:00:00") + + def test_utcoffset02(self): + self.assertEqual(str(tz2.utcoffset(datetime(2010, 3, 28, 12))), "2:00:00") + + def test_utcoffset03(self): + self.assertEqual(str(tz2.utcoffset(datetime(2010, 10, 30, 12))), "2:00:00") + + def test_utcoffset04(self): + self.assertEqual(str(tz2.utcoffset(datetime(2010, 10, 31, 12))), "1:00:00") + + def test_tzname00(self): + self.assertEqual(tz2.tzname(datetime(2011, 1, 1)), "CET") + + def test_tzname01(self): + self.assertEqual(tz2.tzname(datetime(2011, 8, 1)), "CEST") + + def test_utc00(self): + self.assertEqual(timezone.utc.utcoffset(None), td0) + + def test_fromutc00(self): + utc = EPOCH.replace(tzinfo=tz_acdt) + self.assertEqual(tz_acdt.fromutc(utc), utc + 9.5 * td1h) + + def test_fromutc01(self): + utc = EPOCH.replace(tzinfo=tz_est) + self.assertEqual(tz_est.fromutc(utc), utc + 5 * -td1h) + + def test_fromutc02(self): + utc = datetime(2010, 3, 28, 0, 59, 59, 999_999, tz2) + dt = tz2.fromutc(utc) + self.assertEqual(dt, utc + td1h) + self.assertFalse(dt.fold) + + def test_fromutc03(self): + utc = datetime(2010, 3, 28, 1, 0, 0, 0, tz2) + dt = tz2.fromutc(utc) + self.assertEqual(dt, utc + 2 * td1h) + self.assertFalse(dt.fold) + + def test_fromutc04(self): + utc = datetime(2010, 10, 31, 0, 59, 59, 999_999, tz2) + dt = tz2.fromutc(utc) + self.assertEqual(dt, utc + 2 * td1h) + self.assertFalse(dt.fold) + + def test_fromutc05(self): + utc = datetime(2010, 10, 31, 1, 0, 0, 0, tz2) + dt = tz2.fromutc(utc) + self.assertEqual(dt, utc + td1h) + self.assertTrue(dt.fold) + + def test_fromutc06(self): + dt1 = tz2.fromutc(datetime(2010, 10, 31, 0, 0, 0, 0, tz2)) + dt2 = tz2.fromutc(datetime(2010, 10, 31, 1, 0, 0, 0, tz2)) + self.assertEqual(dt1, dt2) + self.assertNotEqual(dt1.fold, dt2.fold) + + def test_aware_datetime00(self): + t = datetime(1, 1, 1) + self.assertEqual(tz1.tzname(t), t.replace(tzinfo=tz1).tzname()) + + def test_aware_datetime01(self): + t = datetime(1, 1, 1) + self.assertEqual(tz1.utcoffset(t), t.replace(tzinfo=tz1).utcoffset()) + + def test_aware_datetime02(self): + t = datetime(1, 1, 1) + self.assertEqual(tz1.dst(t), t.replace(tzinfo=tz1).dst()) + + def test_offset_boundaries00(self): + td = timedelta(hours=23, minutes=59, seconds=59, microseconds=999999) + for i in (1, -1): + self.assertIsInstance(timezone(i * td), timezone) + + def test_offset_boundaries01(self): + td = timedelta(hours=24) + for i in (1, -1): + with self.assertRaises(ValueError): + timezone(i * td) + + +### date ##################################################################### + +d1 = date(2002, 1, 31) +d1r = "datetime.date(0, 0, 730881)" +d2 = date(1956, 1, 31) +d2d1s = (46 * 365 + len(range(1956, 2002, 4))) * 24 * 60 * 60 +d3 = date(2002, 3, 1) +d4 = date(2002, 3, 2) +d5 = date(2002, 1, 31) + +hour = timedelta(hours=1) +day = timedelta(days=1) +week = timedelta(weeks=1) +max_days = MAXYEAR * 365 + MAXYEAR // 4 - MAXYEAR // 100 + MAXYEAR // 400 + + +class Test2Date(unittest.TestCase): + def test___init__00(self): + self.assertEqual(d1.year, 2002) + self.assertEqual(d1.month, 1) + self.assertEqual(d1.day, 31) + + @unittest.skipIf(STDLIB, "not supported by standard datetime") + def test___init__01(self): + date(0, 0, 1) + + @unittest.skipIf(STDLIB, "not supported by standard datetime") + def test___init__02(self): + date(0, 0, max_days) + + def test___init__03(self): + datetime(2000, 2, 29) + + def test___init__04(self): + datetime(2004, 2, 29) + + def test___init__05(self): + datetime(2400, 2, 29) + + def test___init__06(self): + self.assertRaises(ValueError, datetime, 2000, 2, 30) + + def test___init__07(self): + self.assertRaises(ValueError, datetime, 2001, 2, 29) + + def test___init__08(self): + self.assertRaises(ValueError, datetime, 2100, 2, 29) + + def test___init__09(self): + self.assertRaises(ValueError, datetime, 1900, 2, 29) + + def test___init__10(self): + self.assertRaises(ValueError, date, MINYEAR - 1, 1, 1) + self.assertRaises(ValueError, date, MINYEAR, 0, 1) + self.assertRaises(ValueError, date, MINYEAR, 1, 0) + + def test___init__11(self): + self.assertRaises(ValueError, date, MAXYEAR + 1, 12, 31) + self.assertRaises(ValueError, date, MAXYEAR, 13, 31) + self.assertRaises(ValueError, date, MAXYEAR, 12, 32) + + def test___init__12(self): + self.assertRaises(ValueError, date, 1, 2, 29) + self.assertRaises(ValueError, date, 1, 4, 31) + self.assertRaises(ValueError, date, 1, 6, 31) + self.assertRaises(ValueError, date, 1, 9, 31) + self.assertRaises(ValueError, date, 1, 11, 31) + + def test_fromtimestamp00(self): + with LocalTz("UTC"): + d = date.fromtimestamp(1012435200) + self.assertEqual(d, d1) + + def test_fromtimestamp01(self): + with LocalTz("UTC"): + d = date.fromtimestamp(1012435200 + 1) + self.assertEqual(d, d1) + + def test_fromtimestamp02(self): + with LocalTz("UTC"): + d = date.fromtimestamp(1012435200 - 1) + self.assertEqual(d, d1 - timedelta(days=1)) + + def test_fromtimestamp03(self): + with LocalTz("Europe/Rome"): + d = date.fromtimestamp(1012435200 - 3601) + self.assertEqual(d, d1 - timedelta(days=1)) + + def test_today00(self): + tm = mod_time.localtime()[:3] + dt = date.today() + dd = (dt.year, dt.month, dt.day) + self.assertEqual(tm, dd) + + def test_fromordinal00(self): + self.assertEqual(date.fromordinal(1), date(1, 1, 1)) + + def test_fromordinal01(self): + self.assertEqual(date.fromordinal(max_days), date(MAXYEAR, 12, 31)) + + def test_fromisoformat00(self): + self.assertEqual(datetime.fromisoformat("1975-08-10"), datetime(1975, 8, 10)) + + def test_year00(self): + self.assertEqual(d1.year, 2002) + + def test_year01(self): + self.assertEqual(d2.year, 1956) + + def test_month00(self): + self.assertEqual(d1.month, 1) + + def test_month01(self): + self.assertEqual(d4.month, 3) + + def test_day00(self): + self.assertEqual(d1.day, 31) + + def test_day01(self): + self.assertEqual(d4.day, 2) + + def test_toordinal00(self): + self.assertEqual(date(1, 1, 1).toordinal(), 1) + + def test_toordinal01(self): + self.assertEqual(date(MAXYEAR, 12, 31).toordinal(), max_days) + + def test_timetuple00(self): + self.assertEqual(d1.timetuple()[:8], (2002, 1, 31, 0, 0, 0, 3, 31)) + + def test_timetuple01(self): + self.assertEqual(d3.timetuple()[:8], (2002, 3, 1, 0, 0, 0, 4, 60)) + + def test_replace00(self): + self.assertEqual(d1.replace(), d1) + + def test_replace01(self): + self.assertEqual(d1.replace(year=2001), date(2001, 1, 31)) + + def test_replace02(self): + self.assertEqual(d1.replace(month=5), date(2002, 5, 31)) + + def test_replace03(self): + self.assertEqual(d1.replace(day=16), date(2002, 1, 16)) + + def test___add__00(self): + self.assertEqual(d4 + hour, d4) + + def test___add__01(self): + self.assertEqual(d4 + day, date(2002, 3, 3)) + + def test___add__02(self): + self.assertEqual(d4 + week, date(2002, 3, 9)) + + def test___add__03(self): + self.assertEqual(d4 + 52 * week, date(2003, 3, 1)) + + def test___add__04(self): + self.assertEqual(d4 + -hour, date(2002, 3, 1)) + + def test___add__05(self): + self.assertEqual(d5 + -day, date(2002, 1, 30)) + + def test___add__06(self): + self.assertEqual(d4 + -week, date(2002, 2, 23)) + + def test___sub__00(self): + d = d1 - d2 + self.assertEqual(d.total_seconds(), d2d1s) + + def test___sub__01(self): + self.assertEqual(d4 - hour, d4) + + def test___sub__02(self): + self.assertEqual(d4 - day, date(2002, 3, 1)) + + def test___sub__03(self): + self.assertEqual(d4 - week, date(2002, 2, 23)) + + def test___sub__04(self): + self.assertEqual(d4 - 52 * week, date(2001, 3, 3)) + + def test___sub__05(self): + self.assertEqual(d4 - -hour, date(2002, 3, 3)) + + def test___sub__06(self): + self.assertEqual(d4 - -day, date(2002, 3, 3)) + + def test___sub__07(self): + self.assertEqual(d4 - -week, date(2002, 3, 9)) + + def test___eq__00(self): + self.assertEqual(d1, d5) + + def test___eq__01(self): + self.assertFalse(d1 != d5) + + def test___eq__02(self): + self.assertTrue(d2 != d5) + + def test___eq__03(self): + self.assertTrue(d5 != d2) + + def test___eq__04(self): + self.assertFalse(d2 == d5) + + def test___eq__05(self): + self.assertFalse(d5 == d2) + + def test___eq__06(self): + self.assertFalse(d1 == None) + + def test___eq__07(self): + self.assertTrue(d1 != None) + + def test___le__00(self): + self.assertTrue(d1 <= d5) + + def test___le__01(self): + self.assertTrue(d2 <= d5) + + def test___le__02(self): + self.assertFalse(d5 <= d2) + + def test___ge__00(self): + self.assertTrue(d1 >= d5) + + def test___ge__01(self): + self.assertTrue(d5 >= d2) + + def test___ge__02(self): + self.assertFalse(d2 >= d5) + + def test___lt__00(self): + self.assertFalse(d1 < d5) + + def test___lt__01(self): + self.assertTrue(d2 < d5) + + def test___lt__02(self): + self.assertFalse(d5 < d2) + + def test___gt__00(self): + self.assertFalse(d1 > d5) + + def test___gt__01(self): + self.assertTrue(d5 > d2) + + def test___gt__02(self): + self.assertFalse(d2 > d5) + + def test_weekday00(self): + for i in range(7): + # March 4, 2002 is a Monday + self.assertEqual(datetime(2002, 3, 4 + i).weekday(), i) + # January 2, 1956 is a Monday + self.assertEqual(datetime(1956, 1, 2 + i).weekday(), i) + + def test_isoweekday00(self): + for i in range(7): + self.assertEqual(datetime(2002, 3, 4 + i).isoweekday(), i + 1) + self.assertEqual(datetime(1956, 1, 2 + i).isoweekday(), i + 1) + + def test_isoformat00(self): + self.assertEqual(d1.isoformat(), "2002-01-31") + + @unittest.skipIf(STDLIB, "standard datetime differs") + def test___repr__00(self): + self.assertEqual(repr(d1), d1r) + + def test___repr__01(self): + self.assertEqual(d1, eval_mod(repr(d1))) + + def test___hash__00(self): + self.assertEqual(d1, d5) + self.assertEqual(hash(d1), hash(d5)) + + def test___hash__01(self): + dd1 = d1 + timedelta(weeks=7) + dd5 = d5 + timedelta(days=7 * 7) + self.assertEqual(hash(dd1), hash(dd5)) + + def test___hash__02(self): + d = {d1: 1} + d[d5] = 2 + self.assertEqual(len(d), 1) + self.assertEqual(d[d1], 2) + + +### time ##################################################################### + +t1 = time(18, 45, 3, 1234) +t1r = "datetime.time(microsecond=67503001234, tzinfo=None, fold=0)" +t1f = time(18, 45, 3, 1234, fold=1) +t1fr = f"datetime.time(microsecond=67503001234, tzinfo=None, fold=1)" +t1z = time(18, 45, 3, 1234, tz1) +t1zr = f"datetime.time(microsecond=67503001234, tzinfo={repr(tz1)}, fold=0)" +t2 = time(12, 59, 59, 100) +t2z = time(12, 59, 59, 100, tz2) +t3 = time(18, 45, 3, 1234) +t3z = time(18, 45, 3, 1234, tz2) +t4 = time(18, 45, 3, 1234, fold=1) +t4z = time(18, 45, 3, 1234, tz2, fold=1) +t5z = time(20, 45, 3, 1234, tz2) + + +class Test3Time(unittest.TestCase): + def test___init__00(self): + t = time() + self.assertEqual(t.hour, 0) + self.assertEqual(t.minute, 0) + self.assertEqual(t.second, 0) + self.assertEqual(t.microsecond, 0) + self.assertEqual(t.tzinfo, None) + self.assertEqual(t.fold, 0) + + def test___init__01(self): + t = time(12) + self.assertEqual(t.hour, 12) + self.assertEqual(t.minute, 0) + self.assertEqual(t.second, 0) + self.assertEqual(t.microsecond, 0) + self.assertEqual(t.tzinfo, None) + self.assertEqual(t.fold, 0) + + def test___init__02(self): + self.assertEqual(t1z.hour, 18) + self.assertEqual(t1z.minute, 45) + self.assertEqual(t1z.second, 3) + self.assertEqual(t1z.microsecond, 1234) + self.assertEqual(t1z.tzinfo, tz1) + self.assertEqual(t1z.fold, 0) + + def test___init__03(self): + t = time(microsecond=1, fold=1) + self.assertEqual(t.fold, 1) + + @unittest.skipIf(STDLIB, "not supported by standard datetime") + def test___init__04(self): + time(microsecond=24 * 60 * 60 * 1_000_000 - 1) + + def test___init__05(self): + self.assertRaises(ValueError, time, -1, 0, 0, 0) + self.assertRaises(ValueError, time, 0, -1, 0, 0) + self.assertRaises(ValueError, time, 0, 0, -1, 0) + self.assertRaises(ValueError, time, 0, 0, 0, -1) + self.assertRaises(ValueError, time, 0, 0, 0, 0, fold=-1) + + def test___init__06(self): + self.assertRaises(ValueError, time, 24, 0, 0, 0) + self.assertRaises(ValueError, time, 0, 60, 0, 0) + self.assertRaises(ValueError, time, 0, 0, 60, 0) + self.assertRaises(ValueError, time, 0, 0, 0, 0, fold=2) + + @unittest.skipIf(STDLIB, "not supported by standard datetime") + def test___init__07(self): + self.assertRaises(ValueError, time, microsecond=24 * 60 * 60 * 1_000_000) + + def test_fromisoformat00(self): + self.assertEqual(time.fromisoformat("01"), time(1)) + + def test_fromisoformat01(self): + self.assertEqual(time.fromisoformat("13:30"), time(13, 30)) + + def test_fromisoformat02(self): + self.assertEqual(time.fromisoformat("23:30:12"), time(23, 30, 12)) + + def test_fromisoformat03(self): + self.assertEqual(str(time.fromisoformat("11:03:04+01:00")), "11:03:04+01:00") + + def test_hour00(self): + self.assertEqual(t1.hour, 18) + + def test_hour01(self): + self.assertEqual(t2z.hour, 12) + + def test_minute00(self): + self.assertEqual(t1.minute, 45) + + def test_minute01(self): + self.assertEqual(t2z.minute, 59) + + def test_second00(self): + self.assertEqual(t1.second, 3) + + def test_second01(self): + self.assertEqual(t2z.second, 59) + + def test_microsecond00(self): + self.assertEqual(t1.microsecond, 1234) + + def test_microsecond01(self): + self.assertEqual(t2z.microsecond, 100) + + def test_tzinfo00(self): + self.assertEqual(t1.tzinfo, None) + + def test_tzinfo01(self): + self.assertEqual(t2z.tzinfo, tz2) + + def test_fold00(self): + self.assertEqual(t1.fold, 0) + + def test_replace00(self): + self.assertEqual(t2z.replace(), t2z) + + def test_replace01(self): + self.assertEqual(t2z.replace(hour=20), time(20, 59, 59, 100, tz2)) + + def test_replace02(self): + self.assertEqual(t2z.replace(minute=4), time(12, 4, 59, 100, tz2)) + + def test_replace03(self): + self.assertEqual(t2z.replace(second=16), time(12, 59, 16, 100, tz2)) + + def test_replace04(self): + self.assertEqual(t2z.replace(microsecond=99), time(12, 59, 59, 99, tz2)) + + def test_replace05(self): + self.assertEqual(t2z.replace(tzinfo=tz1), time(12, 59, 59, 100, tz1)) + + def test_isoformat00(self): + self.assertEqual(t1.isoformat(), "18:45:03.001234") + + def test_isoformat01(self): + self.assertEqual(t1z.isoformat(), "18:45:03.001234-01:00") + + def test_isoformat02(self): + self.assertEqual(t2z.isoformat(), "12:59:59.000100+01:00") + + @unittest.skipIf(STDLIB, "standard datetime differs") + def test___repr__00(self): + self.assertEqual(repr(t1), t1r) + + @unittest.skipIf(STDLIB, "standard datetime differs") + def test___repr__01(self): + self.assertEqual(repr(t1f), t1fr) + + @unittest.skipIf(STDLIB, "standard datetime differs") + def test___repr__02(self): + self.assertEqual(repr(t1z), t1zr) + + def test___repr__03(self): + self.assertEqual(t1, eval_mod(repr(t1))) + + def test___repr__04(self): + self.assertEqual(t1z, eval_mod(repr(t1z))) + + def test___repr__05(self): + self.assertEqual(t4, eval_mod(repr(t4))) + + def test___repr__06(self): + dt = eval_mod(repr(t4z)) + self.assertEqual(t4z, eval_mod(repr(t4z))) + + def test___bool__00(self): + self.assertTrue(t1) + + def test___bool__01(self): + self.assertTrue(t1z) + + def test___bool__02(self): + self.assertTrue(time()) + + def test___eq__00(self): + self.assertEqual(t1, t1) + + def test___eq__01(self): + self.assertEqual(t1z, t1z) + + def test___eq__02(self): + self.assertNotEqual(t1, t1z) + + def test___eq__03(self): + self.assertNotEqual(t1z, t2z) + + def test___eq__04(self): + self.assertEqual(t1z, t5z) + + def test___eq__05(self): + self.assertEqual(t1, t1f) + + def test___lt__00(self): + self.assertTrue(t2 < t1) + + def test___lt__01(self): + self.assertTrue(t2z < t1z) + + def test___lt__02(self): + self.assertRaises(TypeError, t1.__lt__, t1z) + + def test___le__00(self): + self.assertTrue(t3 <= t1) + + def test___le__01(self): + self.assertTrue(t1z <= t5z) + + def test___le__02(self): + self.assertRaises(TypeError, t1.__le__, t1z) + + def test___ge__00(self): + self.assertTrue(t1 >= t3) + + def test___ge__01(self): + self.assertTrue(t5z >= t1z) + + def test___ge__02(self): + self.assertRaises(TypeError, t1.__ge__, t1z) + + def test___gt__00(self): + self.assertTrue(t1 > t2) + + def test___gt__01(self): + self.assertTrue(t1z > t2z) + + def test___gt__02(self): + self.assertRaises(TypeError, t1.__gt__, t1z) + + def test___hash__00(self): + self.assertEqual(t1, t3) + self.assertEqual(hash(t1), hash(t3)) + + def test___hash__01(self): + d = {t1: 1} + d[t3] = 3 + self.assertEqual(len(d), 1) + self.assertEqual(d[t1], 3) + + def test___hash__02(self): + self.assertNotEqual(t1, t1z) + self.assertNotEqual(hash(t1), hash(t1z)) + + def test___hash__03(self): + self.assertNotEqual(t1z, t3z) + self.assertNotEqual(hash(t1z), hash(t3z)) + + def test___hash__04(self): + tf = t1.replace(fold=1) + self.assertEqual(t1, tf) + self.assertEqual(hash(t1), hash(tf)) + + def test_utcoffset00(self): + self.assertEqual(t1.utcoffset(), None) + + def test_utcoffset01(self): + self.assertEqual(t1z.utcoffset(), timedelta(hours=-1)) + + def test_utcoffset02(self): + self.assertEqual(t2z.utcoffset(), timedelta(hours=1)) + + def test_dst00(self): + self.assertEqual(t1.dst(), None) + + def test_dst01(self): + self.assertEqual(t1z.dst(), None) + + def test_dst02(self): + self.assertEqual(t2z.dst(), td0) + + def test_tzname00(self): + self.assertEqual(t1.tzname(), None) + + def test_tzname01(self): + self.assertEqual(t1z.tzname(), "UTC-01:00") + + def test_tzname02(self): + self.assertEqual(t2z.tzname(), "CET") + + def test_constant00(self): + self.assertIsInstance(timedelta.resolution, timedelta) + self.assertTrue(timedelta.max > timedelta.min) + + def test_constant01(self): + self.assertEqual(time.min, time(0)) + + def test_constant02(self): + self.assertEqual(time.max, time(23, 59, 59, 999_999)) + + def test_constant03(self): + self.assertEqual(time.resolution, timedelta(microseconds=1)) + + +### datetime ################################################################# + +dt1 = datetime(2002, 1, 31) +dt1z1 = datetime(2002, 1, 31, tzinfo=tz1) +dt1z2 = datetime(2002, 1, 31, tzinfo=tz2) +dt2 = datetime(1956, 1, 31) +dt3 = datetime(2002, 3, 1, 12, 59, 59, 100, tz2) +dt4 = datetime(2002, 3, 2, 17, 6) +dt5 = datetime(2002, 1, 31) +dt5z2 = datetime(2002, 1, 31, tzinfo=tz2) + +dt1r = "datetime.datetime(2002, 1, 31, 0, 0, 0, 0, None, fold=0)" +dt3r = "datetime.datetime(2002, 3, 1, 12, 59, 59, 100, Cet(), fold=0)" +dt4r = "datetime.datetime(2002, 3, 2, 17, 6, 0, 0, None, fold=0)" + +d1t1 = datetime(2002, 1, 31, 18, 45, 3, 1234) +d1t1f = datetime(2002, 1, 31, 18, 45, 3, 1234, fold=1) +d1t1z = datetime(2002, 1, 31, 18, 45, 3, 1234, tz1) + +dt27tz2 = datetime(2010, 3, 27, 12, tzinfo=tz2) # last CET day +dt28tz2 = datetime(2010, 3, 28, 12, tzinfo=tz2) # first CEST day +dt30tz2 = datetime(2010, 10, 30, 12, tzinfo=tz2) # last CEST day +dt31tz2 = datetime(2010, 10, 31, 12, tzinfo=tz2) # first CET day + + +# Tests where datetime depens on date and time +class Test4DateTime(unittest.TestCase): + def test_combine00(self): + dt = datetime.combine(d1, t1) + self.assertEqual(dt, d1t1) + + def test_combine01(self): + dt = datetime.combine(d1, t1) + self.assertEqual(dt.date(), d1) + + def test_combine02(self): + dt1 = datetime.combine(d1, t1) + dt2 = datetime.combine(dt1, t1) + self.assertEqual(dt1, dt2) + + def test_combine03(self): + dt = datetime.combine(d1, t1) + self.assertEqual(dt.time(), t1) + + def test_combine04(self): + dt = datetime.combine(d1, t1, tz1) + self.assertEqual(dt, d1t1z) + + def test_combine05(self): + dt = datetime.combine(d1, t1z) + self.assertEqual(dt, d1t1z) + + def test_combine06(self): + dt = datetime.combine(d1, t1f) + self.assertEqual(dt, d1t1f) + + def test_date00(self): + self.assertEqual(d1t1.date(), d1) + + def test_time00(self): + self.assertEqual(d1t1.time(), t1) + + def test_time01(self): + self.assertNotEqual(d1t1z.time(), t1z) + + def test_timetz00(self): + self.assertEqual(d1t1.timetz(), t1) + + def test_timetz01(self): + self.assertEqual(d1t1z.timetz(), t1z) + + def test_timetz02(self): + self.assertEqual(d1t1f.timetz(), t1f) + + +# Tests where datetime is independent from date and time +class Test5DateTime(unittest.TestCase): + @classmethod + def setUpClass(cls): + for k in ("date", "time"): + del mod_datetime.__dict__[k] + + def test___init__00(self): + d = datetime(2002, 3, 1, 12, 0, fold=1) + self.assertEqual(d.year, 2002) + self.assertEqual(d.month, 3) + self.assertEqual(d.day, 1) + self.assertEqual(d.hour, 12) + self.assertEqual(d.minute, 0) + self.assertEqual(d.second, 0) + self.assertEqual(d.microsecond, 0) + self.assertEqual(d.tzinfo, None) + self.assertEqual(d.fold, 1) + + def test___init__01(self): + self.assertEqual(dt3.year, 2002) + self.assertEqual(dt3.month, 3) + self.assertEqual(dt3.day, 1) + self.assertEqual(dt3.hour, 12) + self.assertEqual(dt3.minute, 59) + self.assertEqual(dt3.second, 59) + self.assertEqual(dt3.microsecond, 100) + self.assertEqual(dt3.tzinfo, tz2) + self.assertEqual(dt3.fold, 0) + + def test___init__02(self): + datetime(MINYEAR, 1, 1) + + def test___init__03(self): + datetime(MAXYEAR, 12, 31) + + def test___init__04(self): + self.assertRaises(ValueError, datetime, MINYEAR - 1, 1, 1) + + def test___init__05(self): + self.assertRaises(ValueError, datetime, MAXYEAR + 1, 1, 1) + + def test___init__06(self): + self.assertRaises(ValueError, datetime, 2000, 0, 1) + + def test___init__07(self): + datetime(2000, 2, 29) + + def test___init__08(self): + datetime(2004, 2, 29) + + def test___init__09(self): + datetime(2400, 2, 29) + + def test___init__10(self): + self.assertRaises(ValueError, datetime, 2000, 2, 30) + + def test___init__11(self): + self.assertRaises(ValueError, datetime, 2001, 2, 29) + + def test___init__12(self): + self.assertRaises(ValueError, datetime, 2100, 2, 29) + + def test___init__13(self): + self.assertRaises(ValueError, datetime, 1900, 2, 29) + + def test___init__14(self): + self.assertRaises(ValueError, datetime, 2000, 1, 0) + + def test___init__15(self): + self.assertRaises(ValueError, datetime, 2000, 1, 32) + + def test___init__16(self): + self.assertRaises(ValueError, datetime, 2000, 1, 31, -1) + + def test___init__17(self): + self.assertRaises(ValueError, datetime, 2000, 1, 31, 24) + + def test___init__18(self): + self.assertRaises(ValueError, datetime, 2000, 1, 31, 23, -1) + + def test___init__19(self): + self.assertRaises(ValueError, datetime, 2000, 1, 31, 23, 60) + + def test___init__20(self): + self.assertRaises(ValueError, datetime, 2000, 1, 31, 23, 59, -1) + + def test___init__21(self): + self.assertRaises(ValueError, datetime, 2000, 1, 31, 23, 59, 60) + + def test___init__22(self): + self.assertEqual(dt1, eval_mod(dt1r)) + + def test___init__23(self): + self.assertEqual(dt3, eval_mod(dt3r)) + + def test___init__24(self): + self.assertEqual(dt4, eval_mod(dt4r)) + + def test_fromtimestamp00(self): + with LocalTz("Europe/Rome"): + ts = 1012499103.001234 + if LOCALTZ: + dt = datetime.fromtimestamp(ts) + self.assertEqual(dt, d1t1) + else: + self.assertRaises(NotImplementedError, datetime.fromtimestamp, ts) + + def test_fromtimestamp01(self): + ts = 1012506303.001234 + self.assertEqual(datetime.fromtimestamp(ts, tz1), d1t1z) + + def test_fromtimestamp02(self): + ts = 1269687600 + self.assertEqual(datetime.fromtimestamp(ts, tz2), dt27tz2) + + def test_fromtimestamp03(self): + ts = 1269770400 + self.assertEqual(datetime.fromtimestamp(ts, tz2), dt28tz2) + + def test_fromtimestamp04(self): + with LocalTz("Europe/Rome"): + dt = datetime(2010, 10, 31, 0, 30, tzinfo=timezone.utc) + ts = (dt - EPOCH).total_seconds() + dt = dt.replace(tzinfo=None) + 2 * td1h + if LOCALTZ: + ds = datetime.fromtimestamp(ts) + self.assertEqual(ds, dt) + self.assertFalse(ds.fold) + else: + self.assertRaises(NotImplementedError, datetime.fromtimestamp, ts) + + def test_fromtimestamp05(self): + with LocalTz("Europe/Rome"): + dt = datetime(2010, 10, 31, 1, 30, tzinfo=timezone.utc) + ts = (dt - EPOCH).total_seconds() + dt = dt.replace(tzinfo=None) + 1 * td1h + if LOCALTZ: + ds = datetime.fromtimestamp(ts) + self.assertEqual(ds, dt) + self.assertTrue(ds.fold) + else: + self.assertRaises(NotImplementedError, datetime.fromtimestamp, ts) + + def test_fromtimestamp06(self): + with LocalTz("US/Eastern"): + dt = datetime(2020, 11, 1, 5, 30, tzinfo=timezone.utc) + ts = (dt - EPOCH).total_seconds() + dt = dt.replace(tzinfo=None) - 4 * td1h + if LOCALTZ: + ds = datetime.fromtimestamp(ts) + self.assertEqual(ds, dt) + else: + self.assertRaises(NotImplementedError, datetime.fromtimestamp, ts) + + def test_fromtimestamp07(self): + with LocalTz("US/Eastern"): + dt = datetime(2020, 11, 1, 7, 30, tzinfo=timezone.utc) + ts = (dt - EPOCH).total_seconds() + dt = dt.replace(tzinfo=None) - 5 * td1h + if LOCALTZ: + ds = datetime.fromtimestamp(ts) + self.assertEqual(ds, dt) + else: + self.assertRaises(NotImplementedError, datetime.fromtimestamp, ts) + + @unittest.skipIf(not LOCALTZ, "naive datetime not supported") + def test_now00(self): + tm = datetime(*mod_time.localtime()[:6]) + dt = datetime.now() + self.assertAlmostEqual(tm, dt, delta=timedelta(seconds=1)) + + def test_now01(self): + tm = datetime(*mod_time.gmtime()[:6], tzinfo=tz2) + tm += tz2.utcoffset(tm) + dt = datetime.now(tz2) + self.assertAlmostEqual(tm, dt, delta=timedelta(seconds=1)) + + def test_fromordinal00(self): + self.assertEqual(datetime.fromordinal(1), datetime(1, 1, 1)) + + def test_fromordinal01(self): + self.assertEqual(datetime.fromordinal(max_days), datetime(MAXYEAR, 12, 31)) + + def test_fromisoformat00(self): + self.assertEqual(datetime.fromisoformat("1975-08-10"), datetime(1975, 8, 10)) + + def test_fromisoformat01(self): + self.assertEqual(datetime.fromisoformat("1975-08-10 23"), datetime(1975, 8, 10, 23)) + + def test_fromisoformat02(self): + self.assertEqual(datetime.fromisoformat("1975-08-10 23:30"), datetime(1975, 8, 10, 23, 30)) + + def test_fromisoformat03(self): + self.assertEqual( + datetime.fromisoformat("1975-08-10 23:30:12"), datetime(1975, 8, 10, 23, 30, 12) + ) + + def test_fromisoformat04(self): + self.assertEqual( + str(datetime.fromisoformat("1975-08-10 23:30:12+01:00")), "1975-08-10 23:30:12+01:00" + ) + + def test_year00(self): + self.assertEqual(dt1.year, 2002) + + def test_year01(self): + self.assertEqual(dt2.year, 1956) + + def test_month00(self): + self.assertEqual(dt1.month, 1) + + def test_month01(self): + self.assertEqual(dt3.month, 3) + + def test_day00(self): + self.assertEqual(dt1.day, 31) + + def test_day01(self): + self.assertEqual(dt4.day, 2) + + def test_hour00(self): + self.assertEqual(dt1.hour, 0) + + def test_hour01(self): + self.assertEqual(dt3.hour, 12) + + def test_minute00(self): + self.assertEqual(dt1.minute, 0) + + def test_minute01(self): + self.assertEqual(dt3.minute, 59) + + def test_second00(self): + self.assertEqual(dt1.second, 0) + + def test_second01(self): + self.assertEqual(dt3.second, 59) + + def test_microsecond00(self): + self.assertEqual(dt1.microsecond, 0) + + def test_microsecond01(self): + self.assertEqual(dt3.microsecond, 100) + + def test_tzinfo00(self): + self.assertEqual(dt1.tzinfo, None) + + def test_tzinfo01(self): + self.assertEqual(dt3.tzinfo, tz2) + + def test_fold00(self): + self.assertEqual(dt1.fold, 0) + + def test___add__00(self): + self.assertEqual(dt4 + hour, datetime(2002, 3, 2, 18, 6)) + + def test___add__01(self): + self.assertEqual(hour + dt4, datetime(2002, 3, 2, 18, 6)) + + def test___add__02(self): + self.assertEqual(dt4 + 10 * hour, datetime(2002, 3, 3, 3, 6)) + + def test___add__03(self): + self.assertEqual(dt4 + day, datetime(2002, 3, 3, 17, 6)) + + def test___add__04(self): + self.assertEqual(dt4 + week, datetime(2002, 3, 9, 17, 6)) + + def test___add__05(self): + self.assertEqual(dt4 + 52 * week, datetime(2003, 3, 1, 17, 6)) + + def test___add__06(self): + self.assertEqual(dt4 + (week + day + hour), datetime(2002, 3, 10, 18, 6)) + + def test___add__07(self): + self.assertEqual(dt5 + -day, datetime(2002, 1, 30)) + + def test___add__08(self): + self.assertEqual(-hour + dt4, datetime(2002, 3, 2, 16, 6)) + + def test___sub__00(self): + d = dt1 - dt2 + self.assertEqual(d.total_seconds(), d2d1s) + + def test___sub__01(self): + self.assertEqual(dt4 - hour, datetime(2002, 3, 2, 16, 6)) + + def test___sub__02(self): + self.assertEqual(dt4 - hour, dt4 + -hour) + + def test___sub__03(self): + self.assertEqual(dt4 - 20 * hour, datetime(2002, 3, 1, 21, 6)) + + def test___sub__04(self): + self.assertEqual(dt4 - day, datetime(2002, 3, 1, 17, 6)) + + def test___sub__05(self): + self.assertEqual(dt4 - week, datetime(2002, 2, 23, 17, 6)) + + def test___sub__06(self): + self.assertEqual(dt4 - 52 * week, datetime(2001, 3, 3, 17, 6)) + + def test___sub__07(self): + self.assertEqual(dt4 - (week + day + hour), datetime(2002, 2, 22, 16, 6)) + + def test_computation00(self): + self.assertEqual((dt4 + week) - dt4, week) + + def test_computation01(self): + self.assertEqual((dt4 + day) - dt4, day) + + def test_computation02(self): + self.assertEqual((dt4 + hour) - dt4, hour) + + def test_computation03(self): + self.assertEqual(dt4 - (dt4 + week), -week) + + def test_computation04(self): + self.assertEqual(dt4 - (dt4 + day), -day) + + def test_computation05(self): + self.assertEqual(dt4 - (dt4 + hour), -hour) + + def test_computation06(self): + self.assertEqual(dt4 - (dt4 - week), week) + + def test_computation07(self): + self.assertEqual(dt4 - (dt4 - day), day) + + def test_computation08(self): + self.assertEqual(dt4 - (dt4 - hour), hour) + + def test_computation09(self): + self.assertEqual(dt4 + (week + day + hour), (((dt4 + week) + day) + hour)) + + def test_computation10(self): + self.assertEqual(dt4 - (week + day + hour), (((dt4 - week) - day) - hour)) + + def test___eq__00(self): + self.assertEqual(dt1, dt5) + + def test___eq__01(self): + self.assertFalse(dt1 != dt5) + + def test___eq__02(self): + self.assertTrue(dt2 != dt5) + + def test___eq__03(self): + self.assertTrue(dt5 != dt2) + + def test___eq__04(self): + self.assertFalse(dt2 == dt5) + + def test___eq__05(self): + self.assertFalse(dt5 == dt2) + + def test___eq__06(self): + self.assertFalse(dt1 == dt1z1) + + def test___eq__07(self): + self.assertFalse(dt1z1 == dt1z2) + + def test___eq__08(self): + self.assertTrue(dt1z2 == dt5z2) + + def test___le__00(self): + self.assertTrue(dt1 <= dt5) + + def test___le__01(self): + self.assertTrue(dt2 <= dt5) + + def test___le__02(self): + self.assertFalse(dt5 <= dt2) + + def test___le__03(self): + self.assertFalse(dt1z1 <= dt1z2) + + def test___le__04(self): + self.assertTrue(dt1z2 <= dt5z2) + + def test___le__05(self): + self.assertRaises(TypeError, dt1.__le__, dt1z1) + + def test___ge__00(self): + self.assertTrue(dt1 >= dt5) + + def test___ge__01(self): + self.assertTrue(dt5 >= dt2) + + def test___ge__02(self): + self.assertFalse(dt2 >= dt5) + + def test___ge__03(self): + self.assertTrue(dt1z1 >= dt1z2) + + def test___ge__04(self): + self.assertTrue(dt1z2 >= dt5z2) + + def test___ge__05(self): + self.assertRaises(TypeError, dt1.__ge__, dt1z1) + + def test___lt__00(self): + self.assertFalse(dt1 < dt5) + + def test___lt__01(self): + self.assertTrue(dt2 < dt5) + + def test___lt__02(self): + self.assertFalse(dt5 < dt2) + + def test___lt__03(self): + self.assertFalse(dt1z1 < dt1z2) + + def test___lt__04(self): + self.assertFalse(dt1z2 < dt5z2) + + def test___lt__05(self): + self.assertRaises(TypeError, dt1.__lt__, dt1z1) + + def test___gt__00(self): + self.assertFalse(dt1 > dt5) + + def test___gt__01(self): + self.assertTrue(dt5 > dt2) + + def test___gt__02(self): + self.assertFalse(dt2 > dt5) + + def test___gt__03(self): + self.assertTrue(dt1z1 > dt1z2) + + def test___gt__04(self): + self.assertFalse(dt1z2 > dt5z2) + + def test___gt__05(self): + self.assertRaises(TypeError, dt1.__gt__, dt1z1) + + def test_replace00(self): + self.assertEqual(dt3.replace(), dt3) + + def test_replace01(self): + self.assertEqual(dt3.replace(year=2001), datetime(2001, 3, 1, 12, 59, 59, 100, tz2)) + + def test_replace02(self): + self.assertEqual(dt3.replace(month=4), datetime(2002, 4, 1, 12, 59, 59, 100, tz2)) + + def test_replace03(self): + self.assertEqual(dt3.replace(day=16), datetime(2002, 3, 16, 12, 59, 59, 100, tz2)) + + def test_replace04(self): + self.assertEqual(dt3.replace(hour=13), datetime(2002, 3, 1, 13, 59, 59, 100, tz2)) + + def test_replace05(self): + self.assertEqual(dt3.replace(minute=0), datetime(2002, 3, 1, 12, 0, 59, 100, tz2)) + + def test_replace06(self): + self.assertEqual(dt3.replace(second=1), datetime(2002, 3, 1, 12, 59, 1, 100, tz2)) + + def test_replace07(self): + self.assertEqual(dt3.replace(microsecond=99), datetime(2002, 3, 1, 12, 59, 59, 99, tz2)) + + def test_replace08(self): + self.assertEqual(dt3.replace(tzinfo=tz1), datetime(2002, 3, 1, 12, 59, 59, 100, tz1)) + + def test_replace09(self): + self.assertRaises(ValueError, datetime(2000, 2, 29).replace, year=2001) + + def test_astimezone00(self): + dt = datetime(2002, 3, 1, 11, 59, 59, 100, timezone.utc) + self.assertEqual(dt3.astimezone(timezone.utc), dt) + + def test_astimezone01(self): + self.assertIs(dt1z1.astimezone(tz1), dt1z1) + + def test_astimezone02(self): + dt = datetime(2002, 1, 31, 2, 0, tzinfo=tz2) + self.assertEqual(dt1z1.astimezone(tz2), dt) + + def test_astimezone03(self): + dt = datetime(2002, 1, 31, 10, 30, tzinfo=tz_acdt) + self.assertEqual(dt1z1.astimezone(tz_acdt), dt) + + def test_astimezone04(self): + with LocalTz("Europe/Rome"): + dt1 = dt27tz2 + dt2 = dt1.replace(tzinfo=None) + if LOCALTZ: + self.assertEqual(dt1, dt2.astimezone(tz2)) + else: + self.assertRaises(NotImplementedError, dt2.astimezone, tz2) + + def test_astimezone05(self): + with LocalTz("Europe/Rome"): + dt1 = dt28tz2 + dt2 = dt1.replace(tzinfo=None) + if LOCALTZ: + self.assertEqual(dt1, dt2.astimezone(tz2)) + else: + self.assertRaises(NotImplementedError, dt2.astimezone, tz2) + + def test_astimezone06(self): + with LocalTz("Europe/Rome"): + dt1 = dt30tz2 + dt2 = dt1.replace(tzinfo=None) + if LOCALTZ: + self.assertEqual(dt1, dt2.astimezone(tz2)) + else: + self.assertRaises(NotImplementedError, dt2.astimezone, tz2) + + def test_astimezone07(self): + with LocalTz("Europe/Rome"): + dt1 = dt31tz2 + dt2 = dt1.replace(tzinfo=None) + if LOCALTZ: + self.assertEqual(dt1, dt2.astimezone(tz2)) + else: + self.assertRaises(NotImplementedError, dt2.astimezone, tz2) + + def test_astimezone08(self): + with LocalTz("Europe/Rome"): + dt1 = dt3 + dt2 = dt1.replace(tzinfo=None) + if LOCALTZ: + self.assertEqual(dt1, dt2.astimezone(tz2)) + else: + self.assertRaises(NotImplementedError, dt2.astimezone, tz2) + + def test_utcoffset00(self): + self.assertEqual(dt1.utcoffset(), None) + + def test_utcoffset01(self): + self.assertEqual(dt27tz2.utcoffset(), timedelta(hours=1)) + + def test_utcoffset02(self): + self.assertEqual(dt28tz2.utcoffset(), timedelta(hours=2)) + + def test_utcoffset03(self): + self.assertEqual(dt30tz2.utcoffset(), timedelta(hours=2)) + + def test_utcoffset04(self): + self.assertEqual(dt31tz2.utcoffset(), timedelta(hours=1)) + + def test_dst00(self): + self.assertEqual(dt1.dst(), None) + + def test_dst01(self): + self.assertEqual(dt27tz2.dst(), timedelta(hours=0)) + + def test_dst02(self): + self.assertEqual(dt28tz2.dst(), timedelta(hours=1)) + + def test_tzname00(self): + self.assertEqual(dt1.tzname(), None) + + def test_tzname01(self): + self.assertEqual(dt27tz2.tzname(), "CET") + + def test_tzname02(self): + self.assertEqual(dt28tz2.tzname(), "CEST") + + def test_timetuple00(self): + with LocalTz("Europe/Rome"): + self.assertEqual(dt1.timetuple()[:8], (2002, 1, 31, 0, 0, 0, 3, 31)) + + def test_timetuple01(self): + self.assertEqual(dt27tz2.timetuple()[:8], (2010, 3, 27, 12, 0, 0, 5, 86)) + + def test_timetuple02(self): + self.assertEqual(dt28tz2.timetuple()[:8], (2010, 3, 28, 12, 0, 0, 6, 87)) + + def test_timetuple03(self): + with LocalTz("Europe/Rome"): + self.assertEqual( + dt27tz2.replace(tzinfo=None).timetuple()[:8], (2010, 3, 27, 12, 0, 0, 5, 86) + ) + + def test_timetuple04(self): + self.assertEqual( + dt28tz2.replace(tzinfo=None).timetuple()[:8], (2010, 3, 28, 12, 0, 0, 6, 87) + ) + + def test_toordinal00(self): + self.assertEqual(datetime(1, 1, 1).toordinal(), 1) + + def test_toordinal01(self): + self.assertEqual(datetime(1, 12, 31).toordinal(), 365) + + def test_toordinal02(self): + self.assertEqual(datetime(2, 1, 1).toordinal(), 366) + + def test_toordinal03(self): + # https://www.timeanddate.com/date/dateadded.html?d1=1&m1=1&y1=1&type=add&ad=730882 + self.assertEqual(dt1.toordinal(), 730_882 - 1) + + def test_toordinal04(self): + # https://www.timeanddate.com/date/dateadded.html?d1=1&m1=1&y1=1&type=add&ad=730911 + self.assertEqual(dt3.toordinal(), 730_911 - 1) + + def test_weekday00(self): + self.assertEqual(dt1.weekday(), d1.weekday()) + + def test_timestamp00(self): + with LocalTz("Europe/Rome"): + if LOCALTZ: + self.assertEqual(d1t1.timestamp(), 1012499103.001234) + else: + self.assertRaises(NotImplementedError, d1t1.timestamp) + + def test_timestamp01(self): + self.assertEqual(d1t1z.timestamp(), 1012506303.001234) + + def test_timestamp02(self): + with LocalTz("Europe/Rome"): + dt = datetime(2010, 3, 28, 2, 30) # doens't exist + if LOCALTZ: + self.assertEqual(dt.timestamp(), 1269739800.0) + else: + self.assertRaises(NotImplementedError, dt.timestamp) + + def test_timestamp03(self): + with LocalTz("Europe/Rome"): + dt = datetime(2010, 8, 10, 2, 30) + if LOCALTZ: + self.assertEqual(dt.timestamp(), 1281400200.0) + else: + self.assertRaises(NotImplementedError, dt.timestamp) + + def test_timestamp04(self): + with LocalTz("Europe/Rome"): + dt = datetime(2010, 10, 31, 2, 30, fold=0) + if LOCALTZ: + self.assertEqual(dt.timestamp(), 1288485000.0) + else: + self.assertRaises(NotImplementedError, dt.timestamp) + + def test_timestamp05(self): + with LocalTz("Europe/Rome"): + dt = datetime(2010, 10, 31, 2, 30, fold=1) + if LOCALTZ: + self.assertEqual(dt.timestamp(), 1288488600.0) + else: + self.assertRaises(NotImplementedError, dt.timestamp) + + def test_timestamp06(self): + with LocalTz("US/Eastern"): + dt = datetime(2020, 3, 8, 2, 30) # doens't exist + if LOCALTZ: + self.assertEqual(dt.timestamp(), 1583652600.0) + else: + self.assertRaises(NotImplementedError, dt.timestamp) + + def test_timestamp07(self): + with LocalTz("US/Eastern"): + dt = datetime(2020, 8, 10, 2, 30) + if LOCALTZ: + self.assertEqual(dt.timestamp(), 1597041000.0) + else: + self.assertRaises(NotImplementedError, dt.timestamp) + + def test_timestamp08(self): + with LocalTz("US/Eastern"): + dt = datetime(2020, 11, 1, 2, 30, fold=0) + if LOCALTZ: + self.assertEqual(dt.timestamp(), 1604215800.0) + else: + self.assertRaises(NotImplementedError, dt.timestamp) + + def test_timestamp09(self): + with LocalTz("US/Eastern"): + dt = datetime(2020, 11, 1, 2, 30, fold=1) + if LOCALTZ: + self.assertEqual(dt.timestamp(), 1604215800.0) + else: + self.assertRaises(NotImplementedError, dt.timestamp) + + def test_isoweekday00(self): + self.assertEqual(dt1.isoweekday(), d1.isoweekday()) + + def test_isoformat00(self): + self.assertEqual(dt3.isoformat(), "2002-03-01T12:59:59.000100+01:00") + + def test_isoformat01(self): + self.assertEqual(dt3.isoformat("T"), "2002-03-01T12:59:59.000100+01:00") + + def test_isoformat02(self): + self.assertEqual(dt3.isoformat(" "), "2002-03-01 12:59:59.000100+01:00") + + def test_isoformat03(self): + self.assertEqual(str(dt3), "2002-03-01 12:59:59.000100+01:00") + + @unittest.skipIf(STDLIB, "standard datetime differs") + def test___repr__00(self): + self.assertEqual(repr(dt1), dt1r) + + @unittest.skipIf(STDLIB, "standard datetime differs") + def test___repr__01(self): + self.assertEqual(repr(dt3), dt3r) + + @unittest.skipIf(STDLIB, "standard datetime differs") + def test___repr__02(self): + self.assertEqual(repr(dt4), dt4r) + + def test___repr__03(self): + self.assertEqual(dt1, eval_mod(repr(dt1))) + + def test___repr__04(self): + self.assertEqual(dt3, eval_mod(repr(dt3))) + + def test___repr__05(self): + self.assertEqual(dt4, eval_mod(repr(dt4))) + + def test___hash__00(self): + self.assertEqual(dt1, dt5) + self.assertEqual(hash(dt1), hash(dt5)) + + def test___hash__01(self): + dd1 = dt1 + timedelta(weeks=7) + dd5 = dt5 + timedelta(days=7 * 7) + self.assertEqual(hash(dd1), hash(dd5)) + + def test___hash__02(self): + d = {dt1: 1} + d[dt5] = 2 + self.assertEqual(len(d), 1) + self.assertEqual(d[dt1], 2) + + def test___hash__03(self): + self.assertNotEqual(dt1, dt1z1) + self.assertNotEqual(hash(dt1), hash(dt1z1)) + + def test___hash__04(self): + self.assertNotEqual(dt1z1, dt5z2) + self.assertNotEqual(hash(dt1z1), hash(dt5z2)) + + @unittest.skipIf(STDLIB, "standard datetime has no tuple()") + def test_tuple00(self): + self.assertEqual(dt1.tuple(), (2002, 1, 31, 0, 0, 0, 0, None, 0)) + + @unittest.skipIf(STDLIB, "standard datetime has no tuple()") + def test_tuple01(self): + self.assertEqual(dt27tz2.tuple(), (2010, 3, 27, 12, 0, 0, 0, tz2, 0)) + + @unittest.skipIf(STDLIB, "standard datetime has no tuple()") + def test_tuple02(self): + self.assertEqual(dt28tz2.tuple(), (2010, 3, 28, 12, 0, 0, 0, tz2, 0)) + + +if __name__ == "__main__": + unittest.main() diff --git a/unix-ffi/datetime/datetime.py b/unix-ffi/datetime/datetime.py deleted file mode 100644 index 8f3dff0d..00000000 --- a/unix-ffi/datetime/datetime.py +++ /dev/null @@ -1,2276 +0,0 @@ -"""Concrete date/time and related types. - -See http://www.iana.org/time-zones/repository/tz-link.html for -time zone and DST data sources. -""" - -import time as _time -import math as _math - - -def _cmp(x, y): - return 0 if x == y else 1 if x > y else -1 - - -MINYEAR = 1 -MAXYEAR = 9999 -_MAXORDINAL = 3652059 # date.max.toordinal() - -# Utility functions, adapted from Python's Demo/classes/Dates.py, which -# also assumes the current Gregorian calendar indefinitely extended in -# both directions. Difference: Dates.py calls January 1 of year 0 day -# number 1. The code here calls January 1 of year 1 day number 1. This is -# to match the definition of the "proleptic Gregorian" calendar in Dershowitz -# and Reingold's "Calendrical Calculations", where it's the base calendar -# for all computations. See the book for algorithms for converting between -# proleptic Gregorian ordinals and many other calendar systems. - -_DAYS_IN_MONTH = [None, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] - -_DAYS_BEFORE_MONTH = [None] -dbm = 0 -for dim in _DAYS_IN_MONTH[1:]: - _DAYS_BEFORE_MONTH.append(dbm) - dbm += dim -del dbm, dim - - -def _is_leap(year): - "year -> 1 if leap year, else 0." - return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) - - -def _days_before_year(year): - "year -> number of days before January 1st of year." - y = year - 1 - return y * 365 + y // 4 - y // 100 + y // 400 - - -def _days_in_month(year, month): - "year, month -> number of days in that month in that year." - assert 1 <= month <= 12, month - if month == 2 and _is_leap(year): - return 29 - return _DAYS_IN_MONTH[month] - - -def _days_before_month(year, month): - "year, month -> number of days in year preceding first day of month." - assert 1 <= month <= 12, "month must be in 1..12" - return _DAYS_BEFORE_MONTH[month] + (month > 2 and _is_leap(year)) - - -def _ymd2ord(year, month, day): - "year, month, day -> ordinal, considering 01-Jan-0001 as day 1." - assert 1 <= month <= 12, "month must be in 1..12" - dim = _days_in_month(year, month) - assert 1 <= day <= dim, "day must be in 1..%d" % dim - return _days_before_year(year) + _days_before_month(year, month) + day - - -_DI400Y = _days_before_year(401) # number of days in 400 years -_DI100Y = _days_before_year(101) # " " " " 100 " -_DI4Y = _days_before_year(5) # " " " " 4 " - -# A 4-year cycle has an extra leap day over what we'd get from pasting -# together 4 single years. -assert _DI4Y == 4 * 365 + 1 - -# Similarly, a 400-year cycle has an extra leap day over what we'd get from -# pasting together 4 100-year cycles. -assert _DI400Y == 4 * _DI100Y + 1 - -# OTOH, a 100-year cycle has one fewer leap day than we'd get from -# pasting together 25 4-year cycles. -assert _DI100Y == 25 * _DI4Y - 1 - - -def _ord2ymd(n): - "ordinal -> (year, month, day), considering 01-Jan-0001 as day 1." - - # n is a 1-based index, starting at 1-Jan-1. The pattern of leap years - # repeats exactly every 400 years. The basic strategy is to find the - # closest 400-year boundary at or before n, then work with the offset - # from that boundary to n. Life is much clearer if we subtract 1 from - # n first -- then the values of n at 400-year boundaries are exactly - # those divisible by _DI400Y: - # - # D M Y n n-1 - # -- --- ---- ---------- ---------------- - # 31 Dec -400 -_DI400Y -_DI400Y -1 - # 1 Jan -399 -_DI400Y +1 -_DI400Y 400-year boundary - # ... - # 30 Dec 000 -1 -2 - # 31 Dec 000 0 -1 - # 1 Jan 001 1 0 400-year boundary - # 2 Jan 001 2 1 - # 3 Jan 001 3 2 - # ... - # 31 Dec 400 _DI400Y _DI400Y -1 - # 1 Jan 401 _DI400Y +1 _DI400Y 400-year boundary - n -= 1 - n400, n = divmod(n, _DI400Y) - year = n400 * 400 + 1 # ..., -399, 1, 401, ... - - # Now n is the (non-negative) offset, in days, from January 1 of year, to - # the desired date. Now compute how many 100-year cycles precede n. - # Note that it's possible for n100 to equal 4! In that case 4 full - # 100-year cycles precede the desired day, which implies the desired - # day is December 31 at the end of a 400-year cycle. - n100, n = divmod(n, _DI100Y) - - # Now compute how many 4-year cycles precede it. - n4, n = divmod(n, _DI4Y) - - # And now how many single years. Again n1 can be 4, and again meaning - # that the desired day is December 31 at the end of the 4-year cycle. - n1, n = divmod(n, 365) - - year += n100 * 100 + n4 * 4 + n1 - if n1 == 4 or n100 == 4: - assert n == 0 - return year - 1, 12, 31 - - # Now the year is correct, and n is the offset from January 1. We find - # the month via an estimate that's either exact or one too large. - leapyear = n1 == 3 and (n4 != 24 or n100 == 3) - assert leapyear == _is_leap(year) - month = (n + 50) >> 5 - preceding = _DAYS_BEFORE_MONTH[month] + (month > 2 and leapyear) - if preceding > n: # estimate is too large - month -= 1 - preceding -= _DAYS_IN_MONTH[month] + (month == 2 and leapyear) - n -= preceding - assert 0 <= n < _days_in_month(year, month) - - # Now the year and month are correct, and n is the offset from the - # start of that month: we're done! - return year, month, n + 1 - - -# Month and day names. For localized versions, see the calendar module. -_MONTHNAMES = [ - None, - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", -] -_DAYNAMES = [None, "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] - - -def _build_struct_time(y, m, d, hh, mm, ss, dstflag): - wday = (_ymd2ord(y, m, d) + 6) % 7 - dnum = _days_before_month(y, m) + d - return _time.struct_time((y, m, d, hh, mm, ss, wday, dnum, dstflag)) - - -def _format_time(hh, mm, ss, us): - # Skip trailing microseconds when us==0. - result = "%02d:%02d:%02d" % (hh, mm, ss) - if us: - result += ".%06d" % us - return result - - -# Correctly substitute for %z and %Z escapes in strftime formats. -def _wrap_strftime(object, format, timetuple): - # Don't call utcoffset() or tzname() unless actually needed. - freplace = None # the string to use for %f - zreplace = None # the string to use for %z - Zreplace = None # the string to use for %Z - - # Scan format for %z and %Z escapes, replacing as needed. - newformat = [] - push = newformat.append - i, n = 0, len(format) - while i < n: - ch = format[i] - i += 1 - if ch == "%": - if i < n: - ch = format[i] - i += 1 - if ch == "f": - if freplace is None: - freplace = "%06d" % getattr(object, "microsecond", 0) - newformat.append(freplace) - elif ch == "z": - if zreplace is None: - zreplace = "" - if hasattr(object, "utcoffset"): - offset = object.utcoffset() - if offset is not None: - sign = "+" - if offset.days < 0: - offset = -offset - sign = "-" - h, m = divmod(offset, timedelta(hours=1)) - assert not m % timedelta(minutes=1), "whole minute" - m //= timedelta(minutes=1) - zreplace = "%c%02d%02d" % (sign, h, m) - assert "%" not in zreplace - newformat.append(zreplace) - elif ch == "Z": - if Zreplace is None: - Zreplace = "" - if hasattr(object, "tzname"): - s = object.tzname() - if s is not None: - # strftime is going to have at this: escape % - Zreplace = s.replace("%", "%%") - newformat.append(Zreplace) - else: - push("%") - push(ch) - else: - push("%") - else: - push(ch) - newformat = "".join(newformat) - return _time.strftime(newformat, timetuple) - - -def _call_tzinfo_method(tzinfo, methname, tzinfoarg): - if tzinfo is None: - return None - return getattr(tzinfo, methname)(tzinfoarg) - - -# Just raise TypeError if the arg isn't None or a string. -def _check_tzname(name): - if name is not None and not isinstance(name, str): - raise TypeError("tzinfo.tzname() must return None or string, " "not '%s'" % type(name)) - - -# name is the offset-producing method, "utcoffset" or "dst". -# offset is what it returned. -# If offset isn't None or timedelta, raises TypeError. -# If offset is None, returns None. -# Else offset is checked for being in range, and a whole # of minutes. -# If it is, its integer value is returned. Else ValueError is raised. -def _check_utc_offset(name, offset): - assert name in ("utcoffset", "dst") - if offset is None: - return - if not isinstance(offset, timedelta): - raise TypeError( - "tzinfo.%s() must return None " "or timedelta, not '%s'" % (name, type(offset)) - ) - if offset % timedelta(minutes=1) or offset.microseconds: - raise ValueError( - "tzinfo.%s() must return a whole number " "of minutes, got %s" % (name, offset) - ) - if not -timedelta(1) < offset < timedelta(1): - raise ValueError( - "%s()=%s, must be must be strictly between" - " -timedelta(hours=24) and timedelta(hours=24)" % (name, offset) - ) - - -def _check_date_fields(year, month, day): - if not isinstance(year, int): - raise TypeError("int expected") - if not MINYEAR <= year <= MAXYEAR: - raise ValueError("year must be in %d..%d" % (MINYEAR, MAXYEAR), year) - if not 1 <= month <= 12: - raise ValueError("month must be in 1..12", month) - dim = _days_in_month(year, month) - if not 1 <= day <= dim: - raise ValueError("day must be in 1..%d" % dim, day) - - -def _check_time_fields(hour, minute, second, microsecond): - if not isinstance(hour, int): - raise TypeError("int expected") - if not 0 <= hour <= 23: - raise ValueError("hour must be in 0..23", hour) - if not 0 <= minute <= 59: - raise ValueError("minute must be in 0..59", minute) - if not 0 <= second <= 59: - raise ValueError("second must be in 0..59", second) - if not 0 <= microsecond <= 999999: - raise ValueError("microsecond must be in 0..999999", microsecond) - - -def _check_tzinfo_arg(tz): - if tz is not None and not isinstance(tz, tzinfo): - raise TypeError("tzinfo argument must be None or of a tzinfo subclass") - - -def _cmperror(x, y): - raise TypeError("can't compare '%s' to '%s'" % (type(x).__name__, type(y).__name__)) - - -class timedelta: - """Represent the difference between two datetime objects. - - Supported operators: - - - add, subtract timedelta - - unary plus, minus, abs - - compare to timedelta - - multiply, divide by int - - In addition, datetime supports subtraction of two datetime objects - returning a timedelta, and addition or subtraction of a datetime - and a timedelta giving a datetime. - - Representation: (days, seconds, microseconds). Why? Because I - felt like it. - """ - - __slots__ = "_days", "_seconds", "_microseconds" - - def __new__( - cls, days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0 - ): - # Doing this efficiently and accurately in C is going to be difficult - # and error-prone, due to ubiquitous overflow possibilities, and that - # C double doesn't have enough bits of precision to represent - # microseconds over 10K years faithfully. The code here tries to make - # explicit where go-fast assumptions can be relied on, in order to - # guide the C implementation; it's way more convoluted than speed- - # ignoring auto-overflow-to-long idiomatic Python could be. - - # XXX Check that all inputs are ints or floats. - - # Final values, all integer. - # s and us fit in 32-bit signed ints; d isn't bounded. - d = s = us = 0 - - # Normalize everything to days, seconds, microseconds. - days += weeks * 7 - seconds += minutes * 60 + hours * 3600 - microseconds += milliseconds * 1000 - - # Get rid of all fractions, and normalize s and us. - # Take a deep breath . - if isinstance(days, float): - dayfrac, days = _math.modf(days) - daysecondsfrac, daysecondswhole = _math.modf(dayfrac * (24.0 * 3600.0)) - assert daysecondswhole == int(daysecondswhole) # can't overflow - s = int(daysecondswhole) - assert days == int(days) - d = int(days) - else: - daysecondsfrac = 0.0 - d = days - assert isinstance(daysecondsfrac, float) - assert abs(daysecondsfrac) <= 1.0 - assert isinstance(d, int) - assert abs(s) <= 24 * 3600 - # days isn't referenced again before redefinition - - if isinstance(seconds, float): - secondsfrac, seconds = _math.modf(seconds) - assert seconds == int(seconds) - seconds = int(seconds) - secondsfrac += daysecondsfrac - assert abs(secondsfrac) <= 2.0 - else: - secondsfrac = daysecondsfrac - # daysecondsfrac isn't referenced again - assert isinstance(secondsfrac, float) - assert abs(secondsfrac) <= 2.0 - - assert isinstance(seconds, int) - days, seconds = divmod(seconds, 24 * 3600) - d += days - s += int(seconds) # can't overflow - assert isinstance(s, int) - assert abs(s) <= 2 * 24 * 3600 - # seconds isn't referenced again before redefinition - - usdouble = secondsfrac * 1e6 - assert abs(usdouble) < 2.1e6 # exact value not critical - # secondsfrac isn't referenced again - - if isinstance(microseconds, float): - microseconds += usdouble - microseconds = round(microseconds, 0) - seconds, microseconds = divmod(microseconds, 1e6) - assert microseconds == int(microseconds) - assert seconds == int(seconds) - days, seconds = divmod(seconds, 24.0 * 3600.0) - assert days == int(days) - assert seconds == int(seconds) - d += int(days) - s += int(seconds) # can't overflow - assert isinstance(s, int) - assert abs(s) <= 3 * 24 * 3600 - else: - seconds, microseconds = divmod(microseconds, 1000000) - days, seconds = divmod(seconds, 24 * 3600) - d += days - s += int(seconds) # can't overflow - assert isinstance(s, int) - assert abs(s) <= 3 * 24 * 3600 - microseconds = float(microseconds) - microseconds += usdouble - microseconds = round(microseconds, 0) - assert abs(s) <= 3 * 24 * 3600 - assert abs(microseconds) < 3.1e6 - - # Just a little bit of carrying possible for microseconds and seconds. - assert isinstance(microseconds, float) - assert int(microseconds) == microseconds - us = int(microseconds) - seconds, us = divmod(us, 1000000) - s += seconds # cant't overflow - assert isinstance(s, int) - days, s = divmod(s, 24 * 3600) - d += days - - assert isinstance(d, int) - assert isinstance(s, int) and 0 <= s < 24 * 3600 - assert isinstance(us, int) and 0 <= us < 1000000 - - self = object.__new__(cls) - - self._days = d - self._seconds = s - self._microseconds = us - if abs(d) > 999999999: - raise OverflowError("timedelta # of days is too large: %d" % d) - - return self - - def __repr__(self): - if self._microseconds: - return "%s(%d, %d, %d)" % ( - "datetime." + self.__class__.__name__, - self._days, - self._seconds, - self._microseconds, - ) - if self._seconds: - return "%s(%d, %d)" % ( - "datetime." + self.__class__.__name__, - self._days, - self._seconds, - ) - return "%s(%d)" % ("datetime." + self.__class__.__name__, self._days) - - def __str__(self): - mm, ss = divmod(self._seconds, 60) - hh, mm = divmod(mm, 60) - s = "%d:%02d:%02d" % (hh, mm, ss) - if self._days: - - def plural(n): - return n, abs(n) != 1 and "s" or "" - - s = ("%d day%s, " % plural(self._days)) + s - if self._microseconds: - s = s + ".%06d" % self._microseconds - return s - - def total_seconds(self): - """Total seconds in the duration.""" - return ((self.days * 86400 + self.seconds) * 10**6 + self.microseconds) / 10**6 - - # Read-only field accessors - @property - def days(self): - """days""" - return self._days - - @property - def seconds(self): - """seconds""" - return self._seconds - - @property - def microseconds(self): - """microseconds""" - return self._microseconds - - def __add__(self, other): - if isinstance(other, timedelta): - # for CPython compatibility, we cannot use - # our __class__ here, but need a real timedelta - return timedelta( - self._days + other._days, - self._seconds + other._seconds, - self._microseconds + other._microseconds, - ) - return NotImplemented - - __radd__ = __add__ - - def __sub__(self, other): - if isinstance(other, timedelta): - # for CPython compatibility, we cannot use - # our __class__ here, but need a real timedelta - return timedelta( - self._days - other._days, - self._seconds - other._seconds, - self._microseconds - other._microseconds, - ) - return NotImplemented - - def __rsub__(self, other): - if isinstance(other, timedelta): - return -self + other - return NotImplemented - - def __neg__(self): - # for CPython compatibility, we cannot use - # our __class__ here, but need a real timedelta - return timedelta(-self._days, -self._seconds, -self._microseconds) - - def __pos__(self): - return self - - def __abs__(self): - if self._days < 0: - return -self - else: - return self - - def __mul__(self, other): - if isinstance(other, int): - # for CPython compatibility, we cannot use - # our __class__ here, but need a real timedelta - return timedelta(self._days * other, self._seconds * other, self._microseconds * other) - if isinstance(other, float): - # a, b = other.as_integer_ratio() - # return self * a / b - usec = self._to_microseconds() - return timedelta(0, 0, round(usec * other)) - return NotImplemented - - __rmul__ = __mul__ - - def _to_microseconds(self): - return (self._days * (24 * 3600) + self._seconds) * 1000000 + self._microseconds - - def __floordiv__(self, other): - if not isinstance(other, (int, timedelta)): - return NotImplemented - usec = self._to_microseconds() - if isinstance(other, timedelta): - return usec // other._to_microseconds() - if isinstance(other, int): - return timedelta(0, 0, usec // other) - - def __truediv__(self, other): - if not isinstance(other, (int, float, timedelta)): - return NotImplemented - usec = self._to_microseconds() - if isinstance(other, timedelta): - return usec / other._to_microseconds() - if isinstance(other, int): - return timedelta(0, 0, usec / other) - if isinstance(other, float): - # a, b = other.as_integer_ratio() - # return timedelta(0, 0, b * usec / a) - return timedelta(0, 0, round(usec / other)) - - def __mod__(self, other): - if isinstance(other, timedelta): - r = self._to_microseconds() % other._to_microseconds() - return timedelta(0, 0, r) - return NotImplemented - - def __divmod__(self, other): - if isinstance(other, timedelta): - q, r = divmod(self._to_microseconds(), other._to_microseconds()) - return q, timedelta(0, 0, r) - return NotImplemented - - # Comparisons of timedelta objects with other. - - def __eq__(self, other): - if isinstance(other, timedelta): - return self._cmp(other) == 0 - else: - return False - - def __ne__(self, other): - if isinstance(other, timedelta): - return self._cmp(other) != 0 - else: - return True - - def __le__(self, other): - if isinstance(other, timedelta): - return self._cmp(other) <= 0 - else: - _cmperror(self, other) - - def __lt__(self, other): - if isinstance(other, timedelta): - return self._cmp(other) < 0 - else: - _cmperror(self, other) - - def __ge__(self, other): - if isinstance(other, timedelta): - return self._cmp(other) >= 0 - else: - _cmperror(self, other) - - def __gt__(self, other): - if isinstance(other, timedelta): - return self._cmp(other) > 0 - else: - _cmperror(self, other) - - def _cmp(self, other): - assert isinstance(other, timedelta) - return _cmp(self._getstate(), other._getstate()) - - def __hash__(self): - return hash(self._getstate()) - - def __bool__(self): - return self._days != 0 or self._seconds != 0 or self._microseconds != 0 - - # Pickle support. - - def _getstate(self): - return (self._days, self._seconds, self._microseconds) - - def __reduce__(self): - return (self.__class__, self._getstate()) - - -timedelta.min = timedelta(-999999999) -timedelta.max = timedelta(days=999999999, hours=23, minutes=59, seconds=59, microseconds=999999) -timedelta.resolution = timedelta(microseconds=1) - - -class date: - """Concrete date type. - - Constructors: - - __new__() - fromtimestamp() - today() - fromordinal() - - Operators: - - __repr__, __str__ - __cmp__, __hash__ - __add__, __radd__, __sub__ (add/radd only with timedelta arg) - - Methods: - - timetuple() - toordinal() - weekday() - isoweekday(), isocalendar(), isoformat() - ctime() - strftime() - - Properties (readonly): - year, month, day - """ - - __slots__ = "_year", "_month", "_day" - - def __new__(cls, year, month=None, day=None): - """Constructor. - - Arguments: - - year, month, day (required, base 1) - """ - if ( - isinstance(year, bytes) and len(year) == 4 and 1 <= year[2] <= 12 and month is None - ): # Month is sane - # Pickle support - self = object.__new__(cls) - self.__setstate(year) - return self - _check_date_fields(year, month, day) - self = object.__new__(cls) - self._year = year - self._month = month - self._day = day - return self - - # Additional constructors - - @classmethod - def fromtimestamp(cls, t): - "Construct a date from a POSIX timestamp (like time.time())." - y, m, d, hh, mm, ss, weekday, jday, dst = _time.localtime(t) - return cls(y, m, d) - - @classmethod - def today(cls): - "Construct a date from time.time()." - t = _time.time() - return cls.fromtimestamp(t) - - @classmethod - def fromordinal(cls, n): - """Contruct a date from a proleptic Gregorian ordinal. - - January 1 of year 1 is day 1. Only the year, month and day are - non-zero in the result. - """ - y, m, d = _ord2ymd(n) - return cls(y, m, d) - - # Conversions to string - - def __repr__(self): - """Convert to formal string, for repr(). - - >>> dt = datetime(2010, 1, 1) - >>> repr(dt) - 'datetime.datetime(2010, 1, 1, 0, 0)' - - >>> dt = datetime(2010, 1, 1, tzinfo=timezone.utc) - >>> repr(dt) - 'datetime.datetime(2010, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)' - """ - return "%s(%d, %d, %d)" % ( - "datetime." + self.__class__.__name__, - self._year, - self._month, - self._day, - ) - - # XXX These shouldn't depend on time.localtime(), because that - # clips the usable dates to [1970 .. 2038). At least ctime() is - # easily done without using strftime() -- that's better too because - # strftime("%c", ...) is locale specific. - - def ctime(self): - "Return ctime() style string." - weekday = self.toordinal() % 7 or 7 - return "%s %s %2d 00:00:00 %04d" % ( - _DAYNAMES[weekday], - _MONTHNAMES[self._month], - self._day, - self._year, - ) - - def strftime(self, fmt): - "Format using strftime()." - return _wrap_strftime(self, fmt, self.timetuple()) - - def __format__(self, fmt): - if len(fmt) != 0: - return self.strftime(fmt) - return str(self) - - def isoformat(self): - """Return the date formatted according to ISO. - - This is 'YYYY-MM-DD'. - - References: - - http://www.w3.org/TR/NOTE-datetime - - http://www.cl.cam.ac.uk/~mgk25/iso-time.html - """ - return "%04d-%02d-%02d" % (self._year, self._month, self._day) - - __str__ = isoformat - - # Read-only field accessors - @property - def year(self): - """year (1-9999)""" - return self._year - - @property - def month(self): - """month (1-12)""" - return self._month - - @property - def day(self): - """day (1-31)""" - return self._day - - # Standard conversions, __cmp__, __hash__ (and helpers) - - def timetuple(self): - "Return local time tuple compatible with time.localtime()." - return _build_struct_time(self._year, self._month, self._day, 0, 0, 0, -1) - - def toordinal(self): - """Return proleptic Gregorian ordinal for the year, month and day. - - January 1 of year 1 is day 1. Only the year, month and day values - contribute to the result. - """ - return _ymd2ord(self._year, self._month, self._day) - - def replace(self, year=None, month=None, day=None): - """Return a new date with new values for the specified fields.""" - if year is None: - year = self._year - if month is None: - month = self._month - if day is None: - day = self._day - _check_date_fields(year, month, day) - return date(year, month, day) - - # Comparisons of date objects with other. - - def __eq__(self, other): - if isinstance(other, date): - return self._cmp(other) == 0 - return NotImplemented - - def __ne__(self, other): - if isinstance(other, date): - return self._cmp(other) != 0 - return NotImplemented - - def __le__(self, other): - if isinstance(other, date): - return self._cmp(other) <= 0 - return NotImplemented - - def __lt__(self, other): - if isinstance(other, date): - return self._cmp(other) < 0 - return NotImplemented - - def __ge__(self, other): - if isinstance(other, date): - return self._cmp(other) >= 0 - return NotImplemented - - def __gt__(self, other): - if isinstance(other, date): - return self._cmp(other) > 0 - return NotImplemented - - def _cmp(self, other): - assert isinstance(other, date) - y, m, d = self._year, self._month, self._day - y2, m2, d2 = other._year, other._month, other._day - return _cmp((y, m, d), (y2, m2, d2)) - - def __hash__(self): - "Hash." - return hash(self._getstate()) - - # Computations - - def __add__(self, other): - "Add a date to a timedelta." - if isinstance(other, timedelta): - o = self.toordinal() + other.days - if 0 < o <= _MAXORDINAL: - return date.fromordinal(o) - raise OverflowError("result out of range") - return NotImplemented - - __radd__ = __add__ - - def __sub__(self, other): - """Subtract two dates, or a date and a timedelta.""" - if isinstance(other, timedelta): - return self + timedelta(-other.days) - if isinstance(other, date): - days1 = self.toordinal() - days2 = other.toordinal() - return timedelta(days1 - days2) - return NotImplemented - - def weekday(self): - "Return day of the week, where Monday == 0 ... Sunday == 6." - return (self.toordinal() + 6) % 7 - - # Day-of-the-week and week-of-the-year, according to ISO - - def isoweekday(self): - "Return day of the week, where Monday == 1 ... Sunday == 7." - # 1-Jan-0001 is a Monday - return self.toordinal() % 7 or 7 - - def isocalendar(self): - """Return a 3-tuple containing ISO year, week number, and weekday. - - The first ISO week of the year is the (Mon-Sun) week - containing the year's first Thursday; everything else derives - from that. - - The first week is 1; Monday is 1 ... Sunday is 7. - - ISO calendar algorithm taken from - http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm - """ - year = self._year - week1monday = _isoweek1monday(year) - today = _ymd2ord(self._year, self._month, self._day) - # Internally, week and day have origin 0 - week, day = divmod(today - week1monday, 7) - if week < 0: - year -= 1 - week1monday = _isoweek1monday(year) - week, day = divmod(today - week1monday, 7) - elif week >= 52: - if today >= _isoweek1monday(year + 1): - year += 1 - week = 0 - return year, week + 1, day + 1 - - # Pickle support. - - def _getstate(self): - yhi, ylo = divmod(self._year, 256) - return (bytes([yhi, ylo, self._month, self._day]),) - - def __setstate(self, string): - if len(string) != 4 or not (1 <= string[2] <= 12): - raise TypeError("not enough arguments") - yhi, ylo, self._month, self._day = string - self._year = yhi * 256 + ylo - - def __reduce__(self): - return (self.__class__, self._getstate()) - - -_date_class = date # so functions w/ args named "date" can get at the class - -date.min = date(1, 1, 1) -date.max = date(9999, 12, 31) -date.resolution = timedelta(days=1) - - -class tzinfo: - """Abstract base class for time zone info classes. - - Subclasses must override the name(), utcoffset() and dst() methods. - """ - - __slots__ = () - - def __new__(self, *args, **kwargs): - """Constructor.""" - return object.__new__(self) - - def tzname(self, dt): - "datetime -> string name of time zone." - raise NotImplementedError("tzinfo subclass must override tzname()") - - def utcoffset(self, dt): - "datetime -> minutes east of UTC (negative for west of UTC)" - raise NotImplementedError("tzinfo subclass must override utcoffset()") - - def dst(self, dt): - """datetime -> DST offset in minutes east of UTC. - - Return 0 if DST not in effect. utcoffset() must include the DST - offset. - """ - raise NotImplementedError("tzinfo subclass must override dst()") - - def fromutc(self, dt): - "datetime in UTC -> datetime in local time." - - if not isinstance(dt, datetime): - raise TypeError("fromutc() requires a datetime argument") - if dt.tzinfo is not self: - raise ValueError("dt.tzinfo is not self") - - dtoff = dt.utcoffset() - if dtoff is None: - raise ValueError("fromutc() requires a non-None utcoffset() " "result") - - # See the long comment block at the end of this file for an - # explanation of this algorithm. - dtdst = dt.dst() - if dtdst is None: - raise ValueError("fromutc() requires a non-None dst() result") - delta = dtoff - dtdst - if delta: - dt += delta - dtdst = dt.dst() - if dtdst is None: - raise ValueError("fromutc(): dt.dst gave inconsistent " "results; cannot convert") - return dt + dtdst - - # Pickle support. - - def __reduce__(self): - getinitargs = getattr(self, "__getinitargs__", None) - if getinitargs: - args = getinitargs() - else: - args = () - getstate = getattr(self, "__getstate__", None) - if getstate: - state = getstate() - else: - state = getattr(self, "__dict__", None) or None - if state is None: - return (self.__class__, args) - else: - return (self.__class__, args, state) - - -_tzinfo_class = tzinfo - - -class time: - """Time with time zone. - - Constructors: - - __new__() - - Operators: - - __repr__, __str__ - __cmp__, __hash__ - - Methods: - - strftime() - isoformat() - utcoffset() - tzname() - dst() - - Properties (readonly): - hour, minute, second, microsecond, tzinfo - """ - - def __new__(cls, hour=0, minute=0, second=0, microsecond=0, tzinfo=None): - """Constructor. - - Arguments: - - hour, minute (required) - second, microsecond (default to zero) - tzinfo (default to None) - """ - self = object.__new__(cls) - if isinstance(hour, bytes) and len(hour) == 6: - # Pickle support - self.__setstate(hour, minute or None) - return self - _check_tzinfo_arg(tzinfo) - _check_time_fields(hour, minute, second, microsecond) - self._hour = hour - self._minute = minute - self._second = second - self._microsecond = microsecond - self._tzinfo = tzinfo - return self - - # Read-only field accessors - @property - def hour(self): - """hour (0-23)""" - return self._hour - - @property - def minute(self): - """minute (0-59)""" - return self._minute - - @property - def second(self): - """second (0-59)""" - return self._second - - @property - def microsecond(self): - """microsecond (0-999999)""" - return self._microsecond - - @property - def tzinfo(self): - """timezone info object""" - return self._tzinfo - - # Standard conversions, __hash__ (and helpers) - - # Comparisons of time objects with other. - - def __eq__(self, other): - if isinstance(other, time): - return self._cmp(other, allow_mixed=True) == 0 - else: - return False - - def __ne__(self, other): - if isinstance(other, time): - return self._cmp(other, allow_mixed=True) != 0 - else: - return True - - def __le__(self, other): - if isinstance(other, time): - return self._cmp(other) <= 0 - else: - _cmperror(self, other) - - def __lt__(self, other): - if isinstance(other, time): - return self._cmp(other) < 0 - else: - _cmperror(self, other) - - def __ge__(self, other): - if isinstance(other, time): - return self._cmp(other) >= 0 - else: - _cmperror(self, other) - - def __gt__(self, other): - if isinstance(other, time): - return self._cmp(other) > 0 - else: - _cmperror(self, other) - - def _cmp(self, other, allow_mixed=False): - assert isinstance(other, time) - mytz = self._tzinfo - ottz = other._tzinfo - myoff = otoff = None - - if mytz is ottz: - base_compare = True - else: - myoff = self.utcoffset() - otoff = other.utcoffset() - base_compare = myoff == otoff - - if base_compare: - return _cmp( - (self._hour, self._minute, self._second, self._microsecond), - (other._hour, other._minute, other._second, other._microsecond), - ) - if myoff is None or otoff is None: - if allow_mixed: - return 2 # arbitrary non-zero value - else: - raise TypeError("cannot compare naive and aware times") - myhhmm = self._hour * 60 + self._minute - myoff // timedelta(minutes=1) - othhmm = other._hour * 60 + other._minute - otoff // timedelta(minutes=1) - return _cmp( - (myhhmm, self._second, self._microsecond), (othhmm, other._second, other._microsecond) - ) - - def __hash__(self): - """Hash.""" - tzoff = self.utcoffset() - if not tzoff: # zero or None - return hash(self._getstate()[0]) - h, m = divmod(timedelta(hours=self.hour, minutes=self.minute) - tzoff, timedelta(hours=1)) - assert not m % timedelta(minutes=1), "whole minute" - m //= timedelta(minutes=1) - if 0 <= h < 24: - return hash(time(h, m, self.second, self.microsecond)) - return hash((h, m, self.second, self.microsecond)) - - # Conversion to string - - def _tzstr(self, sep=":"): - """Return formatted timezone offset (+xx:xx) or None.""" - off = self.utcoffset() - if off is not None: - if off.days < 0: - sign = "-" - off = -off - else: - sign = "+" - hh, mm = divmod(off, timedelta(hours=1)) - assert not mm % timedelta(minutes=1), "whole minute" - mm //= timedelta(minutes=1) - assert 0 <= hh < 24 - off = "%s%02d%s%02d" % (sign, hh, sep, mm) - return off - - def __repr__(self): - """Convert to formal string, for repr().""" - if self._microsecond != 0: - s = ", %d, %d" % (self._second, self._microsecond) - elif self._second != 0: - s = ", %d" % self._second - else: - s = "" - s = "%s(%d, %d%s)" % ("datetime." + self.__class__.__name__, self._hour, self._minute, s) - if self._tzinfo is not None: - assert s[-1:] == ")" - s = s[:-1] + ", tzinfo=%r" % self._tzinfo + ")" - return s - - def isoformat(self): - """Return the time formatted according to ISO. - - This is 'HH:MM:SS.mmmmmm+zz:zz', or 'HH:MM:SS+zz:zz' if - self.microsecond == 0. - """ - s = _format_time(self._hour, self._minute, self._second, self._microsecond) - tz = self._tzstr() - if tz: - s += tz - return s - - __str__ = isoformat - - def strftime(self, fmt): - """Format using strftime(). The date part of the timestamp passed - to underlying strftime should not be used. - """ - # The year must be >= 1000 else Python's strftime implementation - # can raise a bogus exception. - timetuple = (1900, 1, 1, self._hour, self._minute, self._second, 0, 1, -1) - return _wrap_strftime(self, fmt, timetuple) - - def __format__(self, fmt): - if len(fmt) != 0: - return self.strftime(fmt) - return str(self) - - # Timezone functions - - def utcoffset(self): - """Return the timezone offset in minutes east of UTC (negative west of - UTC).""" - if self._tzinfo is None: - return None - offset = self._tzinfo.utcoffset(None) - _check_utc_offset("utcoffset", offset) - return offset - - def tzname(self): - """Return the timezone name. - - Note that the name is 100% informational -- there's no requirement that - it mean anything in particular. For example, "GMT", "UTC", "-500", - "-5:00", "EDT", "US/Eastern", "America/New York" are all valid replies. - """ - if self._tzinfo is None: - return None - name = self._tzinfo.tzname(None) - _check_tzname(name) - return name - - def dst(self): - """Return 0 if DST is not in effect, or the DST offset (in minutes - eastward) if DST is in effect. - - This is purely informational; the DST offset has already been added to - the UTC offset returned by utcoffset() if applicable, so there's no - need to consult dst() unless you're interested in displaying the DST - info. - """ - if self._tzinfo is None: - return None - offset = self._tzinfo.dst(None) - _check_utc_offset("dst", offset) - return offset - - def replace(self, hour=None, minute=None, second=None, microsecond=None, tzinfo=True): - """Return a new time with new values for the specified fields.""" - if hour is None: - hour = self.hour - if minute is None: - minute = self.minute - if second is None: - second = self.second - if microsecond is None: - microsecond = self.microsecond - if tzinfo is True: - tzinfo = self.tzinfo - _check_time_fields(hour, minute, second, microsecond) - _check_tzinfo_arg(tzinfo) - return time(hour, minute, second, microsecond, tzinfo) - - def __bool__(self): - if self.second or self.microsecond: - return True - offset = self.utcoffset() or timedelta(0) - return timedelta(hours=self.hour, minutes=self.minute) != offset - - # Pickle support. - - def _getstate(self): - us2, us3 = divmod(self._microsecond, 256) - us1, us2 = divmod(us2, 256) - basestate = bytes([self._hour, self._minute, self._second, us1, us2, us3]) - if self._tzinfo is None: - return (basestate,) - else: - return (basestate, self._tzinfo) - - def __setstate(self, string, tzinfo): - if len(string) != 6 or string[0] >= 24: - raise TypeError("an integer is required") - (self._hour, self._minute, self._second, us1, us2, us3) = string - self._microsecond = (((us1 << 8) | us2) << 8) | us3 - if tzinfo is None or isinstance(tzinfo, _tzinfo_class): - self._tzinfo = tzinfo - else: - raise TypeError("bad tzinfo state arg %r" % tzinfo) - - def __reduce__(self): - return (time, self._getstate()) - - -_time_class = time # so functions w/ args named "time" can get at the class - -time.min = time(0, 0, 0) -time.max = time(23, 59, 59, 999999) -time.resolution = timedelta(microseconds=1) - - -class datetime(date): - """datetime(year, month, day[, hour[, minute[, second[, microsecond[,tzinfo]]]]]) - - The year, month and day arguments are required. tzinfo may be None, or an - instance of a tzinfo subclass. The remaining arguments may be ints. - """ - - __slots__ = date.__slots__ + ("_hour", "_minute", "_second", "_microsecond", "_tzinfo") - - def __new__( - cls, year, month=None, day=None, hour=0, minute=0, second=0, microsecond=0, tzinfo=None - ): - if isinstance(year, bytes) and len(year) == 10: - # Pickle support - self = date.__new__(cls, year[:4]) - self.__setstate(year, month) - return self - _check_tzinfo_arg(tzinfo) - _check_time_fields(hour, minute, second, microsecond) - self = date.__new__(cls, year, month, day) - self._hour = hour - self._minute = minute - self._second = second - self._microsecond = microsecond - self._tzinfo = tzinfo - return self - - # Read-only field accessors - @property - def hour(self): - """hour (0-23)""" - return self._hour - - @property - def minute(self): - """minute (0-59)""" - return self._minute - - @property - def second(self): - """second (0-59)""" - return self._second - - @property - def microsecond(self): - """microsecond (0-999999)""" - return self._microsecond - - @property - def tzinfo(self): - """timezone info object""" - return self._tzinfo - - @classmethod - def fromtimestamp(cls, t, tz=None): - """Construct a datetime from a POSIX timestamp (like time.time()). - - A timezone info object may be passed in as well. - """ - - _check_tzinfo_arg(tz) - - converter = _time.localtime if tz is None else _time.gmtime - - t, frac = divmod(t, 1.0) - us = int(frac * 1e6) - - # If timestamp is less than one microsecond smaller than a - # full second, us can be rounded up to 1000000. In this case, - # roll over to seconds, otherwise, ValueError is raised - # by the constructor. - if us == 1000000: - t += 1 - us = 0 - y, m, d, hh, mm, ss, weekday, jday, dst = converter(t) - ss = min(ss, 59) # clamp out leap seconds if the platform has them - result = cls(y, m, d, hh, mm, ss, us, tz) - if tz is not None: - result = tz.fromutc(result) - return result - - @classmethod - def utcfromtimestamp(cls, t): - "Construct a UTC datetime from a POSIX timestamp (like time.time())." - t, frac = divmod(t, 1.0) - us = int(frac * 1e6) - - # If timestamp is less than one microsecond smaller than a - # full second, us can be rounded up to 1000000. In this case, - # roll over to seconds, otherwise, ValueError is raised - # by the constructor. - if us == 1000000: - t += 1 - us = 0 - y, m, d, hh, mm, ss, weekday, jday, dst = _time.gmtime(t) - ss = min(ss, 59) # clamp out leap seconds if the platform has them - return cls(y, m, d, hh, mm, ss, us) - - # XXX This is supposed to do better than we *can* do by using time.time(), - # XXX if the platform supports a more accurate way. The C implementation - # XXX uses gettimeofday on platforms that have it, but that isn't - # XXX available from Python. So now() may return different results - # XXX across the implementations. - @classmethod - def now(cls, tz=None): - "Construct a datetime from time.time() and optional time zone info." - t = _time.time() - return cls.fromtimestamp(t, tz) - - @classmethod - def utcnow(cls): - "Construct a UTC datetime from time.time()." - t = _time.time() - return cls.utcfromtimestamp(t) - - @classmethod - def combine(cls, date, time): - "Construct a datetime from a given date and a given time." - if not isinstance(date, _date_class): - raise TypeError("date argument must be a date instance") - if not isinstance(time, _time_class): - raise TypeError("time argument must be a time instance") - return cls( - date.year, - date.month, - date.day, - time.hour, - time.minute, - time.second, - time.microsecond, - time.tzinfo, - ) - - def timetuple(self): - "Return local time tuple compatible with time.localtime()." - dst = self.dst() - if dst is None: - dst = -1 - elif dst: - dst = 1 - else: - dst = 0 - return _build_struct_time( - self.year, self.month, self.day, self.hour, self.minute, self.second, dst - ) - - def timestamp(self): - "Return POSIX timestamp as float" - if self._tzinfo is None: - return ( - _time.mktime( - ( - self.year, - self.month, - self.day, - self.hour, - self.minute, - self.second, - -1, - -1, - -1, - ) - ) - + self.microsecond / 1e6 - ) - else: - return (self - _EPOCH).total_seconds() - - def utctimetuple(self): - "Return UTC time tuple compatible with time.gmtime()." - offset = self.utcoffset() - if offset: - self -= offset - y, m, d = self.year, self.month, self.day - hh, mm, ss = self.hour, self.minute, self.second - return _build_struct_time(y, m, d, hh, mm, ss, 0) - - def date(self): - "Return the date part." - return date(self._year, self._month, self._day) - - def time(self): - "Return the time part, with tzinfo None." - return time(self.hour, self.minute, self.second, self.microsecond) - - def timetz(self): - "Return the time part, with same tzinfo." - return time(self.hour, self.minute, self.second, self.microsecond, self._tzinfo) - - def replace( - self, - year=None, - month=None, - day=None, - hour=None, - minute=None, - second=None, - microsecond=None, - tzinfo=True, - ): - """Return a new datetime with new values for the specified fields.""" - if year is None: - year = self.year - if month is None: - month = self.month - if day is None: - day = self.day - if hour is None: - hour = self.hour - if minute is None: - minute = self.minute - if second is None: - second = self.second - if microsecond is None: - microsecond = self.microsecond - if tzinfo is True: - tzinfo = self.tzinfo - _check_date_fields(year, month, day) - _check_time_fields(hour, minute, second, microsecond) - _check_tzinfo_arg(tzinfo) - return datetime(year, month, day, hour, minute, second, microsecond, tzinfo) - - def astimezone(self, tz=None): - if tz is None: - if self.tzinfo is None: - raise ValueError("astimezone() requires an aware datetime") - ts = (self - _EPOCH) // timedelta(seconds=1) - localtm = _time.localtime(ts) - local = datetime(*localtm[:6]) - try: - # Extract TZ data if available - gmtoff = localtm.tm_gmtoff - zone = localtm.tm_zone - except AttributeError: - # Compute UTC offset and compare with the value implied - # by tm_isdst. If the values match, use the zone name - # implied by tm_isdst. - delta = local - datetime(*_time.gmtime(ts)[:6]) - dst = _time.daylight and localtm.tm_isdst > 0 - gmtoff = -(_time.altzone if dst else _time.timezone) - if delta == timedelta(seconds=gmtoff): - tz = timezone(delta, _time.tzname[dst]) - else: - tz = timezone(delta) - else: - tz = timezone(timedelta(seconds=gmtoff), zone) - - elif not isinstance(tz, tzinfo): - raise TypeError("tz argument must be an instance of tzinfo") - - mytz = self.tzinfo - if mytz is None: - raise ValueError("astimezone() requires an aware datetime") - - if tz is mytz: - return self - - # Convert self to UTC, and attach the new time zone object. - myoffset = self.utcoffset() - if myoffset is None: - raise ValueError("astimezone() requires an aware datetime") - utc = (self - myoffset).replace(tzinfo=tz) - - # Convert from UTC to tz's local time. - return tz.fromutc(utc) - - # Ways to produce a string. - - def ctime(self): - "Return ctime() style string." - weekday = self.toordinal() % 7 or 7 - return "%s %s %2d %02d:%02d:%02d %04d" % ( - _DAYNAMES[weekday], - _MONTHNAMES[self._month], - self._day, - self._hour, - self._minute, - self._second, - self._year, - ) - - def isoformat(self, sep="T"): - """Return the time formatted according to ISO. - - This is 'YYYY-MM-DD HH:MM:SS.mmmmmm', or 'YYYY-MM-DD HH:MM:SS' if - self.microsecond == 0. - - If self.tzinfo is not None, the UTC offset is also attached, giving - 'YYYY-MM-DD HH:MM:SS.mmmmmm+HH:MM' or 'YYYY-MM-DD HH:MM:SS+HH:MM'. - - Optional argument sep specifies the separator between date and - time, default 'T'. - """ - s = "%04d-%02d-%02d%s" % (self._year, self._month, self._day, sep) + _format_time( - self._hour, self._minute, self._second, self._microsecond - ) - off = self.utcoffset() - if off is not None: - if off.days < 0: - sign = "-" - off = -off - else: - sign = "+" - hh, mm = divmod(off, timedelta(hours=1)) - assert not mm % timedelta(minutes=1), "whole minute" - mm //= timedelta(minutes=1) - s += "%s%02d:%02d" % (sign, hh, mm) - return s - - def __repr__(self): - """Convert to formal string, for repr().""" - L = [ - self._year, - self._month, - self._day, # These are never zero - self._hour, - self._minute, - self._second, - self._microsecond, - ] - if L[-1] == 0: - del L[-1] - if L[-1] == 0: - del L[-1] - s = ", ".join(map(str, L)) - s = "%s(%s)" % ("datetime." + self.__class__.__name__, s) - if self._tzinfo is not None: - assert s[-1:] == ")" - s = s[:-1] + ", tzinfo=%r" % self._tzinfo + ")" - return s - - def __str__(self): - "Convert to string, for str()." - return self.isoformat(sep=" ") - - @classmethod - def strptime(cls, date_string, format): - "string, format -> new datetime parsed from a string (like time.strptime())." - import _strptime - - return _strptime._strptime_datetime(cls, date_string, format) - - def utcoffset(self): - """Return the timezone offset in minutes east of UTC (negative west of - UTC).""" - if self._tzinfo is None: - return None - offset = self._tzinfo.utcoffset(self) - _check_utc_offset("utcoffset", offset) - return offset - - def tzname(self): - """Return the timezone name. - - Note that the name is 100% informational -- there's no requirement that - it mean anything in particular. For example, "GMT", "UTC", "-500", - "-5:00", "EDT", "US/Eastern", "America/New York" are all valid replies. - """ - name = _call_tzinfo_method(self._tzinfo, "tzname", self) - _check_tzname(name) - return name - - def dst(self): - """Return 0 if DST is not in effect, or the DST offset (in minutes - eastward) if DST is in effect. - - This is purely informational; the DST offset has already been added to - the UTC offset returned by utcoffset() if applicable, so there's no - need to consult dst() unless you're interested in displaying the DST - info. - """ - if self._tzinfo is None: - return None - offset = self._tzinfo.dst(self) - _check_utc_offset("dst", offset) - return offset - - # Comparisons of datetime objects with other. - - def __eq__(self, other): - if isinstance(other, datetime): - return self._cmp(other, allow_mixed=True) == 0 - elif not isinstance(other, date): - return NotImplemented - else: - return False - - def __ne__(self, other): - if isinstance(other, datetime): - return self._cmp(other, allow_mixed=True) != 0 - elif not isinstance(other, date): - return NotImplemented - else: - return True - - def __le__(self, other): - if isinstance(other, datetime): - return self._cmp(other) <= 0 - elif not isinstance(other, date): - return NotImplemented - else: - _cmperror(self, other) - - def __lt__(self, other): - if isinstance(other, datetime): - return self._cmp(other) < 0 - elif not isinstance(other, date): - return NotImplemented - else: - _cmperror(self, other) - - def __ge__(self, other): - if isinstance(other, datetime): - return self._cmp(other) >= 0 - elif not isinstance(other, date): - return NotImplemented - else: - _cmperror(self, other) - - def __gt__(self, other): - if isinstance(other, datetime): - return self._cmp(other) > 0 - elif not isinstance(other, date): - return NotImplemented - else: - _cmperror(self, other) - - def _cmp(self, other, allow_mixed=False): - assert isinstance(other, datetime) - mytz = self._tzinfo - ottz = other._tzinfo - myoff = otoff = None - - if mytz is ottz: - base_compare = True - else: - myoff = self.utcoffset() - otoff = other.utcoffset() - base_compare = myoff == otoff - - if base_compare: - return _cmp( - ( - self._year, - self._month, - self._day, - self._hour, - self._minute, - self._second, - self._microsecond, - ), - ( - other._year, - other._month, - other._day, - other._hour, - other._minute, - other._second, - other._microsecond, - ), - ) - if myoff is None or otoff is None: - if allow_mixed: - return 2 # arbitrary non-zero value - else: - raise TypeError("cannot compare naive and aware datetimes") - # XXX What follows could be done more efficiently... - diff = self - other # this will take offsets into account - if diff.days < 0: - return -1 - return diff and 1 or 0 - - def __add__(self, other): - "Add a datetime and a timedelta." - if not isinstance(other, timedelta): - return NotImplemented - delta = timedelta( - self.toordinal(), - hours=self._hour, - minutes=self._minute, - seconds=self._second, - microseconds=self._microsecond, - ) - delta += other - hour, rem = divmod(delta.seconds, 3600) - minute, second = divmod(rem, 60) - if 0 < delta.days <= _MAXORDINAL: - return datetime.combine( - date.fromordinal(delta.days), - time(hour, minute, second, delta.microseconds, tzinfo=self._tzinfo), - ) - raise OverflowError("result out of range") - - __radd__ = __add__ - - def __sub__(self, other): - "Subtract two datetimes, or a datetime and a timedelta." - if not isinstance(other, datetime): - if isinstance(other, timedelta): - return self + -other - return NotImplemented - - days1 = self.toordinal() - days2 = other.toordinal() - secs1 = self._second + self._minute * 60 + self._hour * 3600 - secs2 = other._second + other._minute * 60 + other._hour * 3600 - base = timedelta(days1 - days2, secs1 - secs2, self._microsecond - other._microsecond) - if self._tzinfo is other._tzinfo: - return base - myoff = self.utcoffset() - otoff = other.utcoffset() - if myoff == otoff: - return base - if myoff is None or otoff is None: - raise TypeError("cannot mix naive and timezone-aware time") - return base + otoff - myoff - - def __hash__(self): - tzoff = self.utcoffset() - if tzoff is None: - return hash(self._getstate()[0]) - days = _ymd2ord(self.year, self.month, self.day) - seconds = self.hour * 3600 + self.minute * 60 + self.second - return hash(timedelta(days, seconds, self.microsecond) - tzoff) - - # Pickle support. - - def _getstate(self): - yhi, ylo = divmod(self._year, 256) - us2, us3 = divmod(self._microsecond, 256) - us1, us2 = divmod(us2, 256) - basestate = bytes( - [ - yhi, - ylo, - self._month, - self._day, - self._hour, - self._minute, - self._second, - us1, - us2, - us3, - ] - ) - if self._tzinfo is None: - return (basestate,) - else: - return (basestate, self._tzinfo) - - def __setstate(self, string, tzinfo): - ( - yhi, - ylo, - self._month, - self._day, - self._hour, - self._minute, - self._second, - us1, - us2, - us3, - ) = string - self._year = yhi * 256 + ylo - self._microsecond = (((us1 << 8) | us2) << 8) | us3 - if tzinfo is None or isinstance(tzinfo, _tzinfo_class): - self._tzinfo = tzinfo - else: - raise TypeError("bad tzinfo state arg %r" % tzinfo) - - def __reduce__(self): - return (self.__class__, self._getstate()) - - -datetime.min = datetime(1, 1, 1) -datetime.max = datetime(9999, 12, 31, 23, 59, 59, 999999) -datetime.resolution = timedelta(microseconds=1) - - -def _isoweek1monday(year): - # Helper to calculate the day number of the Monday starting week 1 - # XXX This could be done more efficiently - THURSDAY = 3 - firstday = _ymd2ord(year, 1, 1) - firstweekday = (firstday + 6) % 7 # See weekday() above - week1monday = firstday - firstweekday - if firstweekday > THURSDAY: - week1monday += 7 - return week1monday - - -class timezone(tzinfo): - __slots__ = "_offset", "_name" - - # Sentinel value to disallow None - _Omitted = object() - - def __new__(cls, offset, name=_Omitted): - if not isinstance(offset, timedelta): - raise TypeError("offset must be a timedelta") - if name is cls._Omitted: - if not offset: - return cls.utc - name = None - elif not isinstance(name, str): - raise TypeError("name must be a string") - if not cls._minoffset <= offset <= cls._maxoffset: - raise ValueError( - "offset must be a timedelta" - " strictly between -timedelta(hours=24) and" - " timedelta(hours=24)." - ) - if offset.microseconds != 0 or offset.seconds % 60 != 0: - raise ValueError( - "offset must be a timedelta" " representing a whole number of minutes" - ) - return cls._create(offset, name) - - @classmethod - def _create(cls, offset, name=None): - self = tzinfo.__new__(cls) - self._offset = offset - self._name = name - return self - - def __getinitargs__(self): - """pickle support""" - if self._name is None: - return (self._offset,) - return (self._offset, self._name) - - def __eq__(self, other): - if type(other) != timezone: - return False - return self._offset == other._offset - - def __hash__(self): - return hash(self._offset) - - def __repr__(self): - """Convert to formal string, for repr(). - - >>> tz = timezone.utc - >>> repr(tz) - 'datetime.timezone.utc' - >>> tz = timezone(timedelta(hours=-5), 'EST') - >>> repr(tz) - "datetime.timezone(datetime.timedelta(-1, 68400), 'EST')" - """ - if self is self.utc: - return "datetime.timezone.utc" - if self._name is None: - return "%s(%r)" % ("datetime." + self.__class__.__name__, self._offset) - return "%s(%r, %r)" % ("datetime." + self.__class__.__name__, self._offset, self._name) - - def __str__(self): - return self.tzname(None) - - def utcoffset(self, dt): - if isinstance(dt, datetime) or dt is None: - return self._offset - raise TypeError("utcoffset() argument must be a datetime instance" " or None") - - def tzname(self, dt): - if isinstance(dt, datetime) or dt is None: - if self._name is None: - return self._name_from_offset(self._offset) - return self._name - raise TypeError("tzname() argument must be a datetime instance" " or None") - - def dst(self, dt): - if isinstance(dt, datetime) or dt is None: - return None - raise TypeError("dst() argument must be a datetime instance" " or None") - - def fromutc(self, dt): - if isinstance(dt, datetime): - if dt.tzinfo is not self: - raise ValueError("fromutc: dt.tzinfo " "is not self") - return dt + self._offset - raise TypeError("fromutc() argument must be a datetime instance" " or None") - - _maxoffset = timedelta(hours=23, minutes=59) - _minoffset = -_maxoffset - - @staticmethod - def _name_from_offset(delta): - if delta < timedelta(0): - sign = "-" - delta = -delta - else: - sign = "+" - hours, rest = divmod(delta, timedelta(hours=1)) - minutes = rest // timedelta(minutes=1) - return "UTC{}{:02d}:{:02d}".format(sign, hours, minutes) - - -timezone.utc = timezone._create(timedelta(0)) -timezone.min = timezone._create(timezone._minoffset) -timezone.max = timezone._create(timezone._maxoffset) -_EPOCH = datetime(1970, 1, 1, tzinfo=timezone.utc) -""" -Some time zone algebra. For a datetime x, let - x.n = x stripped of its timezone -- its naive time. - x.o = x.utcoffset(), and assuming that doesn't raise an exception or - return None - x.d = x.dst(), and assuming that doesn't raise an exception or - return None - x.s = x's standard offset, x.o - x.d - -Now some derived rules, where k is a duration (timedelta). - -1. x.o = x.s + x.d - This follows from the definition of x.s. - -2. If x and y have the same tzinfo member, x.s = y.s. - This is actually a requirement, an assumption we need to make about - sane tzinfo classes. - -3. The naive UTC time corresponding to x is x.n - x.o. - This is again a requirement for a sane tzinfo class. - -4. (x+k).s = x.s - This follows from #2, and that datimetimetz+timedelta preserves tzinfo. - -5. (x+k).n = x.n + k - Again follows from how arithmetic is defined. - -Now we can explain tz.fromutc(x). Let's assume it's an interesting case -(meaning that the various tzinfo methods exist, and don't blow up or return -None when called). - -The function wants to return a datetime y with timezone tz, equivalent to x. -x is already in UTC. - -By #3, we want - - y.n - y.o = x.n [1] - -The algorithm starts by attaching tz to x.n, and calling that y. So -x.n = y.n at the start. Then it wants to add a duration k to y, so that [1] -becomes true; in effect, we want to solve [2] for k: - - (y+k).n - (y+k).o = x.n [2] - -By #1, this is the same as - - (y+k).n - ((y+k).s + (y+k).d) = x.n [3] - -By #5, (y+k).n = y.n + k, which equals x.n + k because x.n=y.n at the start. -Substituting that into [3], - - x.n + k - (y+k).s - (y+k).d = x.n; the x.n terms cancel, leaving - k - (y+k).s - (y+k).d = 0; rearranging, - k = (y+k).s - (y+k).d; by #4, (y+k).s == y.s, so - k = y.s - (y+k).d - -On the RHS, (y+k).d can't be computed directly, but y.s can be, and we -approximate k by ignoring the (y+k).d term at first. Note that k can't be -very large, since all offset-returning methods return a duration of magnitude -less than 24 hours. For that reason, if y is firmly in std time, (y+k).d must -be 0, so ignoring it has no consequence then. - -In any case, the new value is - - z = y + y.s [4] - -It's helpful to step back at look at [4] from a higher level: it's simply -mapping from UTC to tz's standard time. - -At this point, if - - z.n - z.o = x.n [5] - -we have an equivalent time, and are almost done. The insecurity here is -at the start of daylight time. Picture US Eastern for concreteness. The wall -time jumps from 1:59 to 3:00, and wall hours of the form 2:MM don't make good -sense then. The docs ask that an Eastern tzinfo class consider such a time to -be EDT (because it's "after 2"), which is a redundant spelling of 1:MM EST -on the day DST starts. We want to return the 1:MM EST spelling because that's -the only spelling that makes sense on the local wall clock. - -In fact, if [5] holds at this point, we do have the standard-time spelling, -but that takes a bit of proof. We first prove a stronger result. What's the -difference between the LHS and RHS of [5]? Let - - diff = x.n - (z.n - z.o) [6] - -Now - z.n = by [4] - (y + y.s).n = by #5 - y.n + y.s = since y.n = x.n - x.n + y.s = since z and y are have the same tzinfo member, - y.s = z.s by #2 - x.n + z.s - -Plugging that back into [6] gives - - diff = - x.n - ((x.n + z.s) - z.o) = expanding - x.n - x.n - z.s + z.o = cancelling - - z.s + z.o = by #2 - z.d - -So diff = z.d. - -If [5] is true now, diff = 0, so z.d = 0 too, and we have the standard-time -spelling we wanted in the endcase described above. We're done. Contrarily, -if z.d = 0, then we have a UTC equivalent, and are also done. - -If [5] is not true now, diff = z.d != 0, and z.d is the offset we need to -add to z (in effect, z is in tz's standard time, and we need to shift the -local clock into tz's daylight time). - -Let - - z' = z + z.d = z + diff [7] - -and we can again ask whether - - z'.n - z'.o = x.n [8] - -If so, we're done. If not, the tzinfo class is insane, according to the -assumptions we've made. This also requires a bit of proof. As before, let's -compute the difference between the LHS and RHS of [8] (and skipping some of -the justifications for the kinds of substitutions we've done several times -already): - - diff' = x.n - (z'.n - z'.o) = replacing z'.n via [7] - x.n - (z.n + diff - z'.o) = replacing diff via [6] - x.n - (z.n + x.n - (z.n - z.o) - z'.o) = - x.n - z.n - x.n + z.n - z.o + z'.o = cancel x.n - - z.n + z.n - z.o + z'.o = cancel z.n - - z.o + z'.o = #1 twice - -z.s - z.d + z'.s + z'.d = z and z' have same tzinfo - z'.d - z.d - -So z' is UTC-equivalent to x iff z'.d = z.d at this point. If they are equal, -we've found the UTC-equivalent so are done. In fact, we stop with [7] and -return z', not bothering to compute z'.d. - -How could z.d and z'd differ? z' = z + z.d [7], so merely moving z' by -a dst() offset, and starting *from* a time already in DST (we know z.d != 0), -would have to change the result dst() returns: we start in DST, and moving -a little further into it takes us out of DST. - -There isn't a sane case where this can happen. The closest it gets is at -the end of DST, where there's an hour in UTC with no spelling in a hybrid -tzinfo class. In US Eastern, that's 5:MM UTC = 0:MM EST = 1:MM EDT. During -that hour, on an Eastern clock 1:MM is taken as being in standard time (6:MM -UTC) because the docs insist on that, but 0:MM is taken as being in daylight -time (4:MM UTC). There is no local time mapping to 5:MM UTC. The local -clock jumps from 1:59 back to 1:00 again, and repeats the 1:MM hour in -standard time. Since that's what the local clock *does*, we want to map both -UTC hours 5:MM and 6:MM to 1:MM Eastern. The result is ambiguous -in local time, but so it goes -- it's the way the local clock works. - -When x = 5:MM UTC is the input to this algorithm, x.o=0, y.o=-5 and y.d=0, -so z=0:MM. z.d=60 (minutes) then, so [5] doesn't hold and we keep going. -z' = z + z.d = 1:MM then, and z'.d=0, and z'.d - z.d = -60 != 0 so [8] -(correctly) concludes that z' is not UTC-equivalent to x. - -Because we know z.d said z was in daylight time (else [5] would have held and -we would have stopped then), and we know z.d != z'.d (else [8] would have held -and we have stopped then), and there are only 2 possible values dst() can -return in Eastern, it follows that z'.d must be 0 (which it is in the example, -but the reasoning doesn't depend on the example -- it depends on there being -two possible dst() outcomes, one zero and the other non-zero). Therefore -z' must be in standard time, and is the spelling we want in this case. - -Note again that z' is not UTC-equivalent as far as the hybrid tzinfo class is -concerned (because it takes z' as being in standard time rather than the -daylight time we intend here), but returning it gives the real-life "local -clock repeats an hour" behavior when mapping the "unspellable" UTC hour into -tz. - -When the input is 6:MM, z=1:MM and z.d=0, and we stop at once, again with -the 1:MM standard time spelling we want. - -So how can this break? One of the assumptions must be violated. Two -possibilities: - -1) [2] effectively says that y.s is invariant across all y belong to a given - time zone. This isn't true if, for political reasons or continental drift, - a region decides to change its base offset from UTC. - -2) There may be versions of "double daylight" time where the tail end of - the analysis gives up a step too early. I haven't thought about that - enough to say. - -In any case, it's clear that the default fromutc() is strong enough to handle -"almost all" time zones: so long as the standard offset is invariant, it -doesn't matter if daylight time transition points change from year to year, or -if daylight time is skipped in some years; it doesn't matter how large or -small dst() may get within its bounds; and it doesn't even matter if some -perverse time zone returns a negative dst()). So a breaking case must be -pretty bizarre, and a tzinfo subclass can override fromutc() if it is. -""" -try: - from _datetime import * -except ImportError: - pass -else: - # Clean up unused names - del ( - _DAYNAMES, - _DAYS_BEFORE_MONTH, - _DAYS_IN_MONTH, - _DI100Y, - _DI400Y, - _DI4Y, - _MAXORDINAL, - _MONTHNAMES, - _build_struct_time, - _call_tzinfo_method, - _check_date_fields, - _check_time_fields, - _check_tzinfo_arg, - _check_tzname, - _check_utc_offset, - _cmp, - _cmperror, - _date_class, - _days_before_month, - _days_before_year, - _days_in_month, - _format_time, - _is_leap, - _isoweek1monday, - _math, - _ord2ymd, - _time, - _time_class, - _tzinfo_class, - _wrap_strftime, - _ymd2ord, - ) - # XXX Since import * above excludes names that start with _, - # docstring does not get overwritten. In the future, it may be - # appropriate to maintain a single module level docstring and - # remove the following line. - from _datetime import __doc__ diff --git a/unix-ffi/datetime/metadata.txt b/unix-ffi/datetime/metadata.txt deleted file mode 100644 index 950962ae..00000000 --- a/unix-ffi/datetime/metadata.txt +++ /dev/null @@ -1,3 +0,0 @@ -srctype = cpython -type = module -version = 3.3.3-1 diff --git a/unix-ffi/datetime/setup.py b/unix-ffi/datetime/setup.py deleted file mode 100644 index 2f502e52..00000000 --- a/unix-ffi/datetime/setup.py +++ /dev/null @@ -1,24 +0,0 @@ -import sys - -# Remove current dir from sys.path, otherwise setuptools will peek up our -# module instead of system's. -sys.path.pop(0) -from setuptools import setup - -sys.path.append("..") -import sdist_upip - -setup( - name="micropython-datetime", - version="3.3.3-1", - description="CPython datetime module ported to MicroPython", - long_description="This is a module ported from CPython standard library to be compatible with\nMicroPython interpreter. Usually, this means applying small patches for\nfeatures not supported (yet, or at all) in MicroPython. Sometimes, heavier\nchanges are required. Note that CPython modules are written with availability\nof vast resources in mind, and may not work for MicroPython ports with\nlimited heap. If you are affected by such a case, please help reimplement\nthe module from scratch.", - url="https://github.com/micropython/micropython-lib", - author="CPython Developers", - author_email="python-dev@python.org", - maintainer="micropython-lib Developers", - maintainer_email="micro-python@googlegroups.com", - license="Python", - cmdclass={"sdist": sdist_upip.sdist}, - py_modules=["datetime"], -) diff --git a/unix-ffi/datetime/test_datetime.py b/unix-ffi/datetime/test_datetime.py deleted file mode 100644 index 463f2b28..00000000 --- a/unix-ffi/datetime/test_datetime.py +++ /dev/null @@ -1,3881 +0,0 @@ -"""Test date/time type. - -See http://www.zope.org/Members/fdrake/DateTimeWiki/TestCases -""" - -import sys -import pickle -import unittest - -from operator import lt, le, gt, ge, eq, ne, truediv, floordiv, mod - -from test import support - -import datetime as datetime_module -from datetime import MINYEAR, MAXYEAR -from datetime import timedelta -from datetime import tzinfo -from datetime import time -from datetime import timezone -from datetime import date, datetime -import time as _time - -# Needed by test_datetime -# import _strptime -# - - -pickle_choices = [(pickle, pickle, proto) for proto in range(pickle.HIGHEST_PROTOCOL + 1)] -assert len(pickle_choices) == pickle.HIGHEST_PROTOCOL + 1 - -# An arbitrary collection of objects of non-datetime types, for testing -# mixed-type comparisons. -OTHERSTUFF = (10, 34.5, "abc", {}, [], ()) - - -# XXX Copied from test_float. -INF = float("inf") -NAN = float("nan") - - -############################################################################# -# module tests - - -class TestModule(unittest.TestCase): - def test_constants(self): - datetime = datetime_module - self.assertEqual(datetime.MINYEAR, 1) - self.assertEqual(datetime.MAXYEAR, 9999) - - -############################################################################# -# tzinfo tests - - -class FixedOffset(tzinfo): - def __init__(self, offset, name, dstoffset=42): - if isinstance(offset, int): - offset = timedelta(minutes=offset) - if isinstance(dstoffset, int): - dstoffset = timedelta(minutes=dstoffset) - self.__offset = offset - self.__name = name - self.__dstoffset = dstoffset - - def __repr__(self): - return self.__name.lower() - - def utcoffset(self, dt): - return self.__offset - - def tzname(self, dt): - return self.__name - - def dst(self, dt): - return self.__dstoffset - - -class PicklableFixedOffset(FixedOffset): - def __init__(self, offset=None, name=None, dstoffset=None): - FixedOffset.__init__(self, offset, name, dstoffset) - - -class TestTZInfo(unittest.TestCase): - def test_non_abstractness(self): - # In order to allow subclasses to get pickled, the C implementation - # wasn't able to get away with having __init__ raise - # NotImplementedError. - useless = tzinfo() - dt = datetime.max - self.assertRaises(NotImplementedError, useless.tzname, dt) - self.assertRaises(NotImplementedError, useless.utcoffset, dt) - self.assertRaises(NotImplementedError, useless.dst, dt) - - def test_subclass_must_override(self): - class NotEnough(tzinfo): - def __init__(self, offset, name): - self.__offset = offset - self.__name = name - - self.assertTrue(issubclass(NotEnough, tzinfo)) - ne = NotEnough(3, "NotByALongShot") - self.assertIsInstance(ne, tzinfo) - - dt = datetime.now() - self.assertRaises(NotImplementedError, ne.tzname, dt) - self.assertRaises(NotImplementedError, ne.utcoffset, dt) - self.assertRaises(NotImplementedError, ne.dst, dt) - - def test_normal(self): - fo = FixedOffset(3, "Three") - self.assertIsInstance(fo, tzinfo) - for dt in datetime.now(), None: - self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3)) - self.assertEqual(fo.tzname(dt), "Three") - self.assertEqual(fo.dst(dt), timedelta(minutes=42)) - - @unittest.skip("Skip pickling for MicroPython") - def test_pickling_base(self): - # There's no point to pickling tzinfo objects on their own (they - # carry no data), but they need to be picklable anyway else - # concrete subclasses can't be pickled. - orig = tzinfo.__new__(tzinfo) - self.assertTrue(type(orig) is tzinfo) - for pickler, unpickler, proto in pickle_choices: - green = pickler.dumps(orig, proto) - derived = unpickler.loads(green) - self.assertTrue(type(derived) is tzinfo) - - @unittest.skip("Skip pickling for MicroPython") - def test_pickling_subclass(self): - # Make sure we can pickle/unpickle an instance of a subclass. - offset = timedelta(minutes=-300) - for otype, args in [ - (PicklableFixedOffset, (offset, "cookie")), - (timezone, (offset,)), - (timezone, (offset, "EST")), - ]: - orig = otype(*args) - oname = orig.tzname(None) - self.assertIsInstance(orig, tzinfo) - self.assertIs(type(orig), otype) - self.assertEqual(orig.utcoffset(None), offset) - self.assertEqual(orig.tzname(None), oname) - for pickler, unpickler, proto in pickle_choices: - green = pickler.dumps(orig, proto) - derived = unpickler.loads(green) - self.assertIsInstance(derived, tzinfo) - self.assertIs(type(derived), otype) - self.assertEqual(derived.utcoffset(None), offset) - self.assertEqual(derived.tzname(None), oname) - - -class TestTimeZone(unittest.TestCase): - def setUp(self): - self.ACDT = timezone(timedelta(hours=9.5), "ACDT") - self.EST = timezone(-timedelta(hours=5), "EST") - self.DT = datetime(2010, 1, 1) - - def test_str(self): - for tz in [self.ACDT, self.EST, timezone.utc, timezone.min, timezone.max]: - self.assertEqual(str(tz), tz.tzname(None)) - - def test_repr(self): - datetime = datetime_module - for tz in [self.ACDT, self.EST, timezone.utc, timezone.min, timezone.max]: - # test round-trip - tzrep = repr(tz) - # MicroPython doesn't use locals() in eval() - tzrep = tzrep.replace("datetime.", "") - self.assertEqual(tz, eval(tzrep)) - - def test_class_members(self): - limit = timedelta(hours=23, minutes=59) - self.assertEqual(timezone.utc.utcoffset(None), ZERO) - self.assertEqual(timezone.min.utcoffset(None), -limit) - self.assertEqual(timezone.max.utcoffset(None), limit) - - def test_constructor(self): - self.assertIs(timezone.utc, timezone(timedelta(0))) - self.assertIsNot(timezone.utc, timezone(timedelta(0), "UTC")) - self.assertEqual(timezone.utc, timezone(timedelta(0), "UTC")) - # invalid offsets - for invalid in [ - timedelta(microseconds=1), - timedelta(1, 1), - timedelta(seconds=1), - timedelta(1), - -timedelta(1), - ]: - self.assertRaises(ValueError, timezone, invalid) - self.assertRaises(ValueError, timezone, -invalid) - - with self.assertRaises(TypeError): - timezone(None) - with self.assertRaises(TypeError): - timezone(42) - with self.assertRaises(TypeError): - timezone(ZERO, None) - with self.assertRaises(TypeError): - timezone(ZERO, 42) - with self.assertRaises(TypeError): - timezone(ZERO, "ABC", "extra") - - def test_inheritance(self): - self.assertIsInstance(timezone.utc, tzinfo) - self.assertIsInstance(self.EST, tzinfo) - - def test_utcoffset(self): - dummy = self.DT - for h in [0, 1.5, 12]: - offset = h * HOUR - self.assertEqual(offset, timezone(offset).utcoffset(dummy)) - self.assertEqual(-offset, timezone(-offset).utcoffset(dummy)) - - with self.assertRaises(TypeError): - self.EST.utcoffset("") - with self.assertRaises(TypeError): - self.EST.utcoffset(5) - - def test_dst(self): - self.assertIsNone(timezone.utc.dst(self.DT)) - - with self.assertRaises(TypeError): - self.EST.dst("") - with self.assertRaises(TypeError): - self.EST.dst(5) - - def test_tzname(self): - self.assertEqual("UTC+00:00", timezone(ZERO).tzname(None)) - self.assertEqual("UTC-05:00", timezone(-5 * HOUR).tzname(None)) - self.assertEqual("UTC+09:30", timezone(9.5 * HOUR).tzname(None)) - self.assertEqual("UTC-00:01", timezone(timedelta(minutes=-1)).tzname(None)) - self.assertEqual("XYZ", timezone(-5 * HOUR, "XYZ").tzname(None)) - - with self.assertRaises(TypeError): - self.EST.tzname("") - with self.assertRaises(TypeError): - self.EST.tzname(5) - - def test_fromutc(self): - with self.assertRaises(ValueError): - timezone.utc.fromutc(self.DT) - with self.assertRaises(TypeError): - timezone.utc.fromutc("not datetime") - for tz in [self.EST, self.ACDT, Eastern]: - utctime = self.DT.replace(tzinfo=tz) - local = tz.fromutc(utctime) - self.assertEqual(local - utctime, tz.utcoffset(local)) - self.assertEqual(local, self.DT.replace(tzinfo=timezone.utc)) - - def test_comparison(self): - self.assertNotEqual(timezone(ZERO), timezone(HOUR)) - self.assertEqual(timezone(HOUR), timezone(HOUR)) - self.assertEqual(timezone(-5 * HOUR), timezone(-5 * HOUR, "EST")) - with self.assertRaises(TypeError): - timezone(ZERO) < timezone(ZERO) - self.assertIn(timezone(ZERO), {timezone(ZERO)}) - self.assertTrue(timezone(ZERO) != None) - self.assertFalse(timezone(ZERO) == None) - - def test_aware_datetime(self): - # test that timezone instances can be used by datetime - t = datetime(1, 1, 1) - for tz in [timezone.min, timezone.max, timezone.utc]: - self.assertEqual(tz.tzname(t), t.replace(tzinfo=tz).tzname()) - self.assertEqual(tz.utcoffset(t), t.replace(tzinfo=tz).utcoffset()) - self.assertEqual(tz.dst(t), t.replace(tzinfo=tz).dst()) - - -############################################################################# -# Base class for testing a particular aspect of timedelta, time, date and -# datetime comparisons. - - -class HarmlessMixedComparison: - # Test that __eq__ and __ne__ don't complain for mixed-type comparisons. - - # Subclasses must define 'theclass', and theclass(1, 1, 1) must be a - # legit constructor. - - def test_harmless_mixed_comparison(self): - me = self.theclass(1, 1, 1) - - self.assertFalse(me == ()) - self.assertTrue(me != ()) - self.assertFalse(() == me) - self.assertTrue(() != me) - - self.assertIn(me, [1, 20, [], me]) - self.assertIn([], [me, 1, 20, []]) - - def test_harmful_mixed_comparison(self): - me = self.theclass(1, 1, 1) - - self.assertRaises(TypeError, lambda: me < ()) - self.assertRaises(TypeError, lambda: me <= ()) - self.assertRaises(TypeError, lambda: me > ()) - self.assertRaises(TypeError, lambda: me >= ()) - - self.assertRaises(TypeError, lambda: () < me) - self.assertRaises(TypeError, lambda: () <= me) - self.assertRaises(TypeError, lambda: () > me) - self.assertRaises(TypeError, lambda: () >= me) - - -############################################################################# -# timedelta tests - - -class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase): - - theclass = timedelta - - def test_constructor(self): - eq = self.assertEqual - td = timedelta - - # Check keyword args to constructor - eq( - td(), - td(weeks=0, days=0, hours=0, minutes=0, seconds=0, milliseconds=0, microseconds=0), - ) - eq(td(1), td(days=1)) - eq(td(0, 1), td(seconds=1)) - eq(td(0, 0, 1), td(microseconds=1)) - eq(td(weeks=1), td(days=7)) - eq(td(days=1), td(hours=24)) - eq(td(hours=1), td(minutes=60)) - eq(td(minutes=1), td(seconds=60)) - eq(td(seconds=1), td(milliseconds=1000)) - eq(td(milliseconds=1), td(microseconds=1000)) - - # Check float args to constructor - eq(td(weeks=1.0 / 7), td(days=1)) - eq(td(days=1.0 / 24), td(hours=1)) - eq(td(hours=1.0 / 60), td(minutes=1)) - eq(td(minutes=1.0 / 60), td(seconds=1)) - eq(td(seconds=0.001), td(milliseconds=1)) - eq(td(milliseconds=0.001), td(microseconds=1)) - - def test_computations(self): - eq = self.assertEqual - td = timedelta - - a = td(7) # One week - b = td(0, 60) # One minute - c = td(0, 0, 1000) # One millisecond - eq(a + b + c, td(7, 60, 1000)) - eq(a - b, td(6, 24 * 3600 - 60)) - eq(b.__rsub__(a), td(6, 24 * 3600 - 60)) - eq(-a, td(-7)) - eq(+a, td(7)) - eq(-b, td(-1, 24 * 3600 - 60)) - eq(-c, td(-1, 24 * 3600 - 1, 999000)) - eq(abs(a), a) - eq(abs(-a), a) - eq(td(6, 24 * 3600), a) - eq(td(0, 0, 60 * 1000000), b) - eq(a * 10, td(70)) - eq(a * 10, 10 * a) - eq(a * 10, 10 * a) - eq(b * 10, td(0, 600)) - eq(10 * b, td(0, 600)) - eq(b * 10, td(0, 600)) - eq(c * 10, td(0, 0, 10000)) - eq(10 * c, td(0, 0, 10000)) - eq(c * 10, td(0, 0, 10000)) - eq(a * -1, -a) - eq(b * -2, -b - b) - eq(c * -2, -c + -c) - eq(b * (60 * 24), (b * 60) * 24) - eq(b * (60 * 24), (60 * b) * 24) - eq(c * 1000, td(0, 1)) - eq(1000 * c, td(0, 1)) - eq(a // 7, td(1)) - eq(b // 10, td(0, 6)) - eq(c // 1000, td(0, 0, 1)) - eq(a // 10, td(0, 7 * 24 * 360)) - eq(a // 3600000, td(0, 0, 7 * 24 * 1000)) - eq(a / 0.5, td(14)) - eq(b / 0.5, td(0, 120)) - eq(a / 7, td(1)) - eq(b / 10, td(0, 6)) - eq(c / 1000, td(0, 0, 1)) - eq(a / 10, td(0, 7 * 24 * 360)) - eq(a / 3600000, td(0, 0, 7 * 24 * 1000)) - - # Multiplication by float - us = td(microseconds=1) - eq((3 * us) * 0.5, 2 * us) - eq((5 * us) * 0.5, 2 * us) - eq(0.5 * (3 * us), 2 * us) - eq(0.5 * (5 * us), 2 * us) - eq((-3 * us) * 0.5, -2 * us) - eq((-5 * us) * 0.5, -2 * us) - - # Division by int and float - eq((3 * us) / 2, 2 * us) - eq((5 * us) / 2, 2 * us) - eq((-3 * us) / 2.0, -2 * us) - eq((-5 * us) / 2.0, -2 * us) - eq((3 * us) / -2, -2 * us) - eq((5 * us) / -2, -2 * us) - eq((3 * us) / -2.0, -2 * us) - eq((5 * us) / -2.0, -2 * us) - for i in range(-10, 10): - eq((i * us / 3) // us, round(i / 3)) - for i in range(-10, 10): - eq((i * us / -3) // us, round(i / -3)) - - # Issue #11576 - eq(td(999999999, 86399, 999999) - td(999999999, 86399, 999998), td(0, 0, 1)) - eq(td(999999999, 1, 1) - td(999999999, 1, 0), td(0, 0, 1)) - - def test_disallowed_computations(self): - a = timedelta(42) - - # Add/sub ints or floats should be illegal - for i in 1, 1.0: - self.assertRaises(TypeError, lambda: a + i) - self.assertRaises(TypeError, lambda: a - i) - self.assertRaises(TypeError, lambda: i + a) - self.assertRaises(TypeError, lambda: i - a) - - # Division of int by timedelta doesn't make sense. - # Division by zero doesn't make sense. - zero = 0 - self.assertRaises(TypeError, lambda: zero // a) - self.assertRaises(ZeroDivisionError, lambda: a // zero) - self.assertRaises(ZeroDivisionError, lambda: a / zero) - self.assertRaises(ZeroDivisionError, lambda: a / 0.0) - self.assertRaises(TypeError, lambda: a / "") - - @support.requires_IEEE_754 - def test_disallowed_special(self): - a = timedelta(42) - self.assertRaises(ValueError, a.__mul__, NAN) - self.assertRaises(ValueError, a.__truediv__, NAN) - - def test_basic_attributes(self): - days, seconds, us = 1, 7, 31 - td = timedelta(days, seconds, us) - self.assertEqual(td.days, days) - self.assertEqual(td.seconds, seconds) - self.assertEqual(td.microseconds, us) - - def test_total_seconds(self): - td = timedelta(days=365) - self.assertEqual(td.total_seconds(), 31536000.0) - for total_seconds in [123456.789012, -123456.789012, 0.123456, 0, 1e6]: - td = timedelta(seconds=total_seconds) - self.assertEqual(td.total_seconds(), total_seconds) - # Issue8644: Test that td.total_seconds() has the same - # accuracy as td / timedelta(seconds=1). - for ms in [-1, -2, -123]: - td = timedelta(microseconds=ms) - self.assertEqual(td.total_seconds(), td / timedelta(seconds=1)) - - def test_carries(self): - t1 = timedelta( - days=100, - weeks=-7, - hours=-24 * (100 - 49), - minutes=-3, - seconds=12, - microseconds=(3 * 60 - 12) * 1e6 + 1, - ) - t2 = timedelta(microseconds=1) - self.assertEqual(t1, t2) - - def test_hash_equality(self): - t1 = timedelta( - days=100, - weeks=-7, - hours=-24 * (100 - 49), - minutes=-3, - seconds=12, - microseconds=(3 * 60 - 12) * 1000000, - ) - t2 = timedelta() - self.assertEqual(hash(t1), hash(t2)) - - t1 += timedelta(weeks=7) - t2 += timedelta(days=7 * 7) - self.assertEqual(t1, t2) - self.assertEqual(hash(t1), hash(t2)) - - d = {t1: 1} - d[t2] = 2 - self.assertEqual(len(d), 1) - self.assertEqual(d[t1], 2) - - @unittest.skip("Skip pickling for MicroPython") - def test_pickling(self): - args = 12, 34, 56 - orig = timedelta(*args) - for pickler, unpickler, proto in pickle_choices: - green = pickler.dumps(orig, proto) - derived = unpickler.loads(green) - self.assertEqual(orig, derived) - - def test_compare(self): - t1 = timedelta(2, 3, 4) - t2 = timedelta(2, 3, 4) - self.assertEqual(t1, t2) - self.assertTrue(t1 <= t2) - self.assertTrue(t1 >= t2) - self.assertTrue(not t1 != t2) - self.assertTrue(not t1 < t2) - self.assertTrue(not t1 > t2) - - for args in (3, 3, 3), (2, 4, 4), (2, 3, 5): - t2 = timedelta(*args) # this is larger than t1 - self.assertTrue(t1 < t2) - self.assertTrue(t2 > t1) - self.assertTrue(t1 <= t2) - self.assertTrue(t2 >= t1) - self.assertTrue(t1 != t2) - self.assertTrue(t2 != t1) - self.assertTrue(not t1 == t2) - self.assertTrue(not t2 == t1) - self.assertTrue(not t1 > t2) - self.assertTrue(not t2 < t1) - self.assertTrue(not t1 >= t2) - self.assertTrue(not t2 <= t1) - - for badarg in OTHERSTUFF: - self.assertEqual(t1 == badarg, False) - self.assertEqual(t1 != badarg, True) - self.assertEqual(badarg == t1, False) - self.assertEqual(badarg != t1, True) - - self.assertRaises(TypeError, lambda: t1 <= badarg) - self.assertRaises(TypeError, lambda: t1 < badarg) - self.assertRaises(TypeError, lambda: t1 > badarg) - self.assertRaises(TypeError, lambda: t1 >= badarg) - self.assertRaises(TypeError, lambda: badarg <= t1) - self.assertRaises(TypeError, lambda: badarg < t1) - self.assertRaises(TypeError, lambda: badarg > t1) - self.assertRaises(TypeError, lambda: badarg >= t1) - - def test_str(self): - td = timedelta - eq = self.assertEqual - - eq(str(td(1)), "1 day, 0:00:00") - eq(str(td(-1)), "-1 day, 0:00:00") - eq(str(td(2)), "2 days, 0:00:00") - eq(str(td(-2)), "-2 days, 0:00:00") - - eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59") - eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04") - eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)), "-210 days, 23:12:34") - - eq(str(td(milliseconds=1)), "0:00:00.001000") - eq(str(td(microseconds=3)), "0:00:00.000003") - - eq( - str(td(days=999999999, hours=23, minutes=59, seconds=59, microseconds=999999)), - "999999999 days, 23:59:59.999999", - ) - - def test_repr(self): - name = "datetime." + self.theclass.__name__ - self.assertEqual(repr(self.theclass(1)), "%s(1)" % name) - self.assertEqual(repr(self.theclass(10, 2)), "%s(10, 2)" % name) - self.assertEqual(repr(self.theclass(-10, 2, 400000)), "%s(-10, 2, 400000)" % name) - - def test_roundtrip(self): - for td in ( - timedelta(days=999999999, hours=23, minutes=59, seconds=59, microseconds=999999), - timedelta(days=-999999999), - timedelta(days=-999999999, seconds=1), - timedelta(days=1, seconds=2, microseconds=3), - ): - - # Verify td -> string -> td identity. - s = repr(td) - self.assertTrue(s.startswith("datetime.")) - s = s[9:] - td2 = eval(s) - self.assertEqual(td, td2) - - # Verify identity via reconstructing from pieces. - td2 = timedelta(td.days, td.seconds, td.microseconds) - self.assertEqual(td, td2) - - def test_resolution_info(self): - self.assertIsInstance(timedelta.min, timedelta) - self.assertIsInstance(timedelta.max, timedelta) - self.assertIsInstance(timedelta.resolution, timedelta) - self.assertTrue(timedelta.max > timedelta.min) - self.assertEqual(timedelta.min, timedelta(-999999999)) - self.assertEqual(timedelta.max, timedelta(999999999, 24 * 3600 - 1, 1e6 - 1)) - self.assertEqual(timedelta.resolution, timedelta(0, 0, 1)) - - def test_overflow(self): - tiny = timedelta.resolution - - td = timedelta.min + tiny - td -= tiny # no problem - self.assertRaises(OverflowError, td.__sub__, tiny) - self.assertRaises(OverflowError, td.__add__, -tiny) - - td = timedelta.max - tiny - td += tiny # no problem - self.assertRaises(OverflowError, td.__add__, tiny) - self.assertRaises(OverflowError, td.__sub__, -tiny) - - self.assertRaises(OverflowError, lambda: -timedelta.max) - - day = timedelta(1) - self.assertRaises(OverflowError, day.__mul__, 10**9) - self.assertRaises(OverflowError, day.__mul__, 1e9) - self.assertRaises(OverflowError, day.__truediv__, 1e-20) - self.assertRaises(OverflowError, day.__truediv__, 1e-10) - self.assertRaises(OverflowError, day.__truediv__, 9e-10) - - @support.requires_IEEE_754 - def _test_overflow_special(self): - day = timedelta(1) - self.assertRaises(OverflowError, day.__mul__, INF) - self.assertRaises(OverflowError, day.__mul__, -INF) - - def test_microsecond_rounding(self): - td = timedelta - eq = self.assertEqual - - # Single-field rounding. - eq(td(milliseconds=0.4 / 1000), td(0)) # rounds to 0 - eq(td(milliseconds=-0.4 / 1000), td(0)) # rounds to 0 - eq(td(milliseconds=0.6 / 1000), td(microseconds=1)) - eq(td(milliseconds=-0.6 / 1000), td(microseconds=-1)) - - # Rounding due to contributions from more than one field. - us_per_hour = 3600e6 - us_per_day = us_per_hour * 24 - eq(td(days=0.4 / us_per_day), td(0)) - eq(td(hours=0.2 / us_per_hour), td(0)) - eq(td(days=0.4 / us_per_day, hours=0.2 / us_per_hour), td(microseconds=1)) - - eq(td(days=-0.4 / us_per_day), td(0)) - eq(td(hours=-0.2 / us_per_hour), td(0)) - eq(td(days=-0.4 / us_per_day, hours=-0.2 / us_per_hour), td(microseconds=-1)) - - def test_massive_normalization(self): - td = timedelta(microseconds=-1) - self.assertEqual((td.days, td.seconds, td.microseconds), (-1, 24 * 3600 - 1, 999999)) - - def test_bool(self): - self.assertTrue(timedelta(1)) - self.assertTrue(timedelta(0, 1)) - self.assertTrue(timedelta(0, 0, 1)) - self.assertTrue(timedelta(microseconds=1)) - self.assertTrue(not timedelta(0)) - - def test_subclass_timedelta(self): - class T(timedelta): - @staticmethod - def from_td(td): - return T(td.days, td.seconds, td.microseconds) - - def as_hours(self): - sum = self.days * 24 + self.seconds / 3600.0 + self.microseconds / 3600e6 - return round(sum) - - t1 = T(days=1) - self.assertTrue(type(t1) is T) - self.assertEqual(t1.as_hours(), 24) - - t2 = T(days=-1, seconds=-3600) - self.assertTrue(type(t2) is T) - self.assertEqual(t2.as_hours(), -25) - - t3 = t1 + t2 - self.assertTrue(type(t3) is timedelta) - t4 = T.from_td(t3) - self.assertTrue(type(t4) is T) - self.assertEqual(t3.days, t4.days) - self.assertEqual(t3.seconds, t4.seconds) - self.assertEqual(t3.microseconds, t4.microseconds) - self.assertEqual(str(t3), str(t4)) - self.assertEqual(t4.as_hours(), -1) - - def test_division(self): - t = timedelta(hours=1, minutes=24, seconds=19) - second = timedelta(seconds=1) - self.assertEqual(t / second, 5059.0) - self.assertEqual(t // second, 5059) - - t = timedelta(minutes=2, seconds=30) - minute = timedelta(minutes=1) - self.assertEqual(t / minute, 2.5) - self.assertEqual(t // minute, 2) - - zerotd = timedelta(0) - self.assertRaises(ZeroDivisionError, truediv, t, zerotd) - self.assertRaises(ZeroDivisionError, floordiv, t, zerotd) - - # self.assertRaises(TypeError, truediv, t, 2) - # note: floor division of a timedelta by an integer *is* - # currently permitted. - - def test_remainder(self): - t = timedelta(minutes=2, seconds=30) - minute = timedelta(minutes=1) - r = t % minute - self.assertEqual(r, timedelta(seconds=30)) - - t = timedelta(minutes=-2, seconds=30) - r = t % minute - self.assertEqual(r, timedelta(seconds=30)) - - zerotd = timedelta(0) - self.assertRaises(ZeroDivisionError, mod, t, zerotd) - - self.assertRaises(TypeError, mod, t, 10) - - def test_divmod(self): - t = timedelta(minutes=2, seconds=30) - minute = timedelta(minutes=1) - q, r = divmod(t, minute) - self.assertEqual(q, 2) - self.assertEqual(r, timedelta(seconds=30)) - - t = timedelta(minutes=-2, seconds=30) - q, r = divmod(t, minute) - self.assertEqual(q, -2) - self.assertEqual(r, timedelta(seconds=30)) - - zerotd = timedelta(0) - self.assertRaises(ZeroDivisionError, divmod, t, zerotd) - - self.assertRaises(TypeError, divmod, t, 10) - - -############################################################################# -# date tests - - -class TestDateOnly(unittest.TestCase): - # Tests here won't pass if also run on datetime objects, so don't - # subclass this to test datetimes too. - - def test_delta_non_days_ignored(self): - dt = date(2000, 1, 2) - delta = timedelta(days=1, hours=2, minutes=3, seconds=4, microseconds=5) - days = timedelta(delta.days) - self.assertEqual(days, timedelta(1)) - - dt2 = dt + delta - self.assertEqual(dt2, dt + days) - - dt2 = delta + dt - self.assertEqual(dt2, dt + days) - - dt2 = dt - delta - self.assertEqual(dt2, dt - days) - - delta = -delta - days = timedelta(delta.days) - self.assertEqual(days, timedelta(-2)) - - dt2 = dt + delta - self.assertEqual(dt2, dt + days) - - dt2 = delta + dt - self.assertEqual(dt2, dt + days) - - dt2 = dt - delta - self.assertEqual(dt2, dt - days) - - -class SubclassDate(date): - sub_var = 1 - - -class TestDate(HarmlessMixedComparison, unittest.TestCase): - # Tests here should pass for both dates and datetimes, except for a - # few tests that TestDateTime overrides. - - theclass = date - - def test_basic_attributes(self): - dt = self.theclass(2002, 3, 1) - self.assertEqual(dt.year, 2002) - self.assertEqual(dt.month, 3) - self.assertEqual(dt.day, 1) - - def test_roundtrip(self): - for dt in (self.theclass(1, 2, 3), self.theclass.today()): - # Verify dt -> string -> date identity. - s = repr(dt) - self.assertTrue(s.startswith("datetime.")) - s = s[9:] - dt2 = eval(s) - self.assertEqual(dt, dt2) - - # Verify identity via reconstructing from pieces. - dt2 = self.theclass(dt.year, dt.month, dt.day) - self.assertEqual(dt, dt2) - - def test_ordinal_conversions(self): - # Check some fixed values. - for y, m, d, n in [ - (1, 1, 1, 1), # calendar origin - (1, 12, 31, 365), - (2, 1, 1, 366), - # first example from "Calendrical Calculations" - (1945, 11, 12, 710347), - ]: - d = self.theclass(y, m, d) - self.assertEqual(n, d.toordinal()) - fromord = self.theclass.fromordinal(n) - self.assertEqual(d, fromord) - if hasattr(fromord, "hour"): - # if we're checking something fancier than a date, verify - # the extra fields have been zeroed out - self.assertEqual(fromord.hour, 0) - self.assertEqual(fromord.minute, 0) - self.assertEqual(fromord.second, 0) - self.assertEqual(fromord.microsecond, 0) - - # Check first and last days of year spottily across the whole - # range of years supported. - for year in range(MINYEAR, MAXYEAR + 1, 7): - # Verify (year, 1, 1) -> ordinal -> y, m, d is identity. - d = self.theclass(year, 1, 1) - n = d.toordinal() - d2 = self.theclass.fromordinal(n) - self.assertEqual(d, d2) - # Verify that moving back a day gets to the end of year-1. - if year > 1: - d = self.theclass.fromordinal(n - 1) - d2 = self.theclass(year - 1, 12, 31) - self.assertEqual(d, d2) - self.assertEqual(d2.toordinal(), n - 1) - - # Test every day in a leap-year and a non-leap year. - dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] - for year, isleap in (2000, True), (2002, False): - n = self.theclass(year, 1, 1).toordinal() - for month, maxday in zip(range(1, 13), dim): - if month == 2 and isleap: - maxday += 1 - for day in range(1, maxday + 1): - d = self.theclass(year, month, day) - self.assertEqual(d.toordinal(), n) - self.assertEqual(d, self.theclass.fromordinal(n)) - n += 1 - - def test_extreme_ordinals(self): - a = self.theclass.min - a = self.theclass(a.year, a.month, a.day) # get rid of time parts - aord = a.toordinal() - b = a.fromordinal(aord) - self.assertEqual(a, b) - - self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1)) - - b = a + timedelta(days=1) - self.assertEqual(b.toordinal(), aord + 1) - self.assertEqual(b, self.theclass.fromordinal(aord + 1)) - - a = self.theclass.max - a = self.theclass(a.year, a.month, a.day) # get rid of time parts - aord = a.toordinal() - b = a.fromordinal(aord) - self.assertEqual(a, b) - - self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1)) - - b = a - timedelta(days=1) - self.assertEqual(b.toordinal(), aord - 1) - self.assertEqual(b, self.theclass.fromordinal(aord - 1)) - - def test_bad_constructor_arguments(self): - # bad years - self.theclass(MINYEAR, 1, 1) # no exception - self.theclass(MAXYEAR, 1, 1) # no exception - self.assertRaises(ValueError, self.theclass, MINYEAR - 1, 1, 1) - self.assertRaises(ValueError, self.theclass, MAXYEAR + 1, 1, 1) - # bad months - self.theclass(2000, 1, 1) # no exception - self.theclass(2000, 12, 1) # no exception - self.assertRaises(ValueError, self.theclass, 2000, 0, 1) - self.assertRaises(ValueError, self.theclass, 2000, 13, 1) - # bad days - self.theclass(2000, 2, 29) # no exception - self.theclass(2004, 2, 29) # no exception - self.theclass(2400, 2, 29) # no exception - self.assertRaises(ValueError, self.theclass, 2000, 2, 30) - self.assertRaises(ValueError, self.theclass, 2001, 2, 29) - self.assertRaises(ValueError, self.theclass, 2100, 2, 29) - self.assertRaises(ValueError, self.theclass, 1900, 2, 29) - self.assertRaises(ValueError, self.theclass, 2000, 1, 0) - self.assertRaises(ValueError, self.theclass, 2000, 1, 32) - - def test_hash_equality(self): - d = self.theclass(2000, 12, 31) - # same thing - e = self.theclass(2000, 12, 31) - self.assertEqual(d, e) - self.assertEqual(hash(d), hash(e)) - - dic = {d: 1} - dic[e] = 2 - self.assertEqual(len(dic), 1) - self.assertEqual(dic[d], 2) - self.assertEqual(dic[e], 2) - - d = self.theclass(2001, 1, 1) - # same thing - e = self.theclass(2001, 1, 1) - self.assertEqual(d, e) - self.assertEqual(hash(d), hash(e)) - - dic = {d: 1} - dic[e] = 2 - self.assertEqual(len(dic), 1) - self.assertEqual(dic[d], 2) - self.assertEqual(dic[e], 2) - - def test_computations(self): - a = self.theclass(2002, 1, 31) - b = self.theclass(1956, 1, 31) - c = self.theclass(2001, 2, 1) - - diff = a - b - self.assertEqual(diff.days, 46 * 365 + len(range(1956, 2002, 4))) - self.assertEqual(diff.seconds, 0) - self.assertEqual(diff.microseconds, 0) - - day = timedelta(1) - week = timedelta(7) - a = self.theclass(2002, 3, 2) - self.assertEqual(a + day, self.theclass(2002, 3, 3)) - self.assertEqual(day + a, self.theclass(2002, 3, 3)) - self.assertEqual(a - day, self.theclass(2002, 3, 1)) - self.assertEqual(-day + a, self.theclass(2002, 3, 1)) - self.assertEqual(a + week, self.theclass(2002, 3, 9)) - self.assertEqual(a - week, self.theclass(2002, 2, 23)) - self.assertEqual(a + 52 * week, self.theclass(2003, 3, 1)) - self.assertEqual(a - 52 * week, self.theclass(2001, 3, 3)) - self.assertEqual((a + week) - a, week) - self.assertEqual((a + day) - a, day) - self.assertEqual((a - week) - a, -week) - self.assertEqual((a - day) - a, -day) - self.assertEqual(a - (a + week), -week) - self.assertEqual(a - (a + day), -day) - self.assertEqual(a - (a - week), week) - self.assertEqual(a - (a - day), day) - self.assertEqual(c - (c - day), day) - - # Add/sub ints or floats should be illegal - for i in 1, 1.0: - self.assertRaises(TypeError, lambda: a + i) - self.assertRaises(TypeError, lambda: a - i) - self.assertRaises(TypeError, lambda: i + a) - self.assertRaises(TypeError, lambda: i - a) - - # delta - date is senseless. - self.assertRaises(TypeError, lambda: day - a) - # mixing date and (delta or date) via * or // is senseless - self.assertRaises(TypeError, lambda: day * a) - self.assertRaises(TypeError, lambda: a * day) - self.assertRaises(TypeError, lambda: day // a) - self.assertRaises(TypeError, lambda: a // day) - self.assertRaises(TypeError, lambda: a * a) - self.assertRaises(TypeError, lambda: a // a) - # date + date is senseless - self.assertRaises(TypeError, lambda: a + a) - - def test_overflow(self): - tiny = self.theclass.resolution - - for delta in [tiny, timedelta(1), timedelta(2)]: - dt = self.theclass.min + delta - dt -= delta # no problem - self.assertRaises(OverflowError, dt.__sub__, delta) - self.assertRaises(OverflowError, dt.__add__, -delta) - - dt = self.theclass.max - delta - dt += delta # no problem - self.assertRaises(OverflowError, dt.__add__, delta) - self.assertRaises(OverflowError, dt.__sub__, -delta) - - def test_fromtimestamp(self): - import time - - # Try an arbitrary fixed value. - year, month, day = 1999, 9, 19 - ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1)) - d = self.theclass.fromtimestamp(ts) - self.assertEqual(d.year, year) - self.assertEqual(d.month, month) - self.assertEqual(d.day, day) - - @unittest.skip("Skip for MicroPython") - def test_insane_fromtimestamp(self): - # It's possible that some platform maps time_t to double, - # and that this test will fail there. This test should - # exempt such platforms (provided they return reasonable - # results!). - for insane in -1e200, 1e200: - self.assertRaises(OverflowError, self.theclass.fromtimestamp, insane) - - def test_today(self): - import time - - # We claim that today() is like fromtimestamp(time.time()), so - # prove it. - for dummy in range(3): - today = self.theclass.today() - ts = time.time() - todayagain = self.theclass.fromtimestamp(ts) - if today == todayagain: - break - # There are several legit reasons that could fail: - # 1. It recently became midnight, between the today() and the - # time() calls. - # 2. The platform time() has such fine resolution that we'll - # never get the same value twice. - # 3. The platform time() has poor resolution, and we just - # happened to call today() right before a resolution quantum - # boundary. - # 4. The system clock got fiddled between calls. - # In any case, wait a little while and try again. - time.sleep(0.1) - - # It worked or it didn't. If it didn't, assume it's reason #2, and - # let the test pass if they're within half a second of each other. - self.assertTrue(today == todayagain or abs(todayagain - today) < timedelta(seconds=0.5)) - - def test_weekday(self): - for i in range(7): - # March 4, 2002 is a Monday - self.assertEqual(self.theclass(2002, 3, 4 + i).weekday(), i) - self.assertEqual(self.theclass(2002, 3, 4 + i).isoweekday(), i + 1) - # January 2, 1956 is a Monday - self.assertEqual(self.theclass(1956, 1, 2 + i).weekday(), i) - self.assertEqual(self.theclass(1956, 1, 2 + i).isoweekday(), i + 1) - - def test_isocalendar(self): - # Check examples from - # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm - for i in range(7): - d = self.theclass(2003, 12, 22 + i) - self.assertEqual(d.isocalendar(), (2003, 52, i + 1)) - d = self.theclass(2003, 12, 29) + timedelta(i) - self.assertEqual(d.isocalendar(), (2004, 1, i + 1)) - d = self.theclass(2004, 1, 5 + i) - self.assertEqual(d.isocalendar(), (2004, 2, i + 1)) - d = self.theclass(2009, 12, 21 + i) - self.assertEqual(d.isocalendar(), (2009, 52, i + 1)) - d = self.theclass(2009, 12, 28) + timedelta(i) - self.assertEqual(d.isocalendar(), (2009, 53, i + 1)) - d = self.theclass(2010, 1, 4 + i) - self.assertEqual(d.isocalendar(), (2010, 1, i + 1)) - - def test_iso_long_years(self): - # Calculate long ISO years and compare to table from - # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm - ISO_LONG_YEARS_TABLE = """ - 4 32 60 88 - 9 37 65 93 - 15 43 71 99 - 20 48 76 - 26 54 82 - - 105 133 161 189 - 111 139 167 195 - 116 144 172 - 122 150 178 - 128 156 184 - - 201 229 257 285 - 207 235 263 291 - 212 240 268 296 - 218 246 274 - 224 252 280 - - 303 331 359 387 - 308 336 364 392 - 314 342 370 398 - 320 348 376 - 325 353 381 - """ - iso_long_years = sorted(map(int, ISO_LONG_YEARS_TABLE.split())) - L = [] - for i in range(400): - d = self.theclass(2000 + i, 12, 31) - d1 = self.theclass(1600 + i, 12, 31) - self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:]) - if d.isocalendar()[1] == 53: - L.append(i) - self.assertEqual(L, iso_long_years) - - def test_isoformat(self): - t = self.theclass(2, 3, 2) - self.assertEqual(t.isoformat(), "0002-03-02") - - def test_ctime(self): - t = self.theclass(2002, 3, 2) - self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002") - - def test_strftime(self): - t = self.theclass(2005, 3, 2) - self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05") - self.assertEqual(t.strftime(""), "") # SF bug #761337 - # self.assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784 - - self.assertRaises(TypeError, t.strftime) # needs an arg - self.assertRaises(TypeError, t.strftime, "one", "two") # too many args - self.assertRaises(TypeError, t.strftime, 42) # arg wrong type - - # test that unicode input is allowed (issue 2782) - self.assertEqual(t.strftime("%m"), "03") - - # A naive object replaces %z and %Z w/ empty strings. - self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''") - - # make sure that invalid format specifiers are handled correctly - # self.assertRaises(ValueError, t.strftime, "%e") - # self.assertRaises(ValueError, t.strftime, "%") - # self.assertRaises(ValueError, t.strftime, "%#") - - # oh well, some systems just ignore those invalid ones. - # at least, excercise them to make sure that no crashes - # are generated - for f in ["%e", "%", "%#"]: - try: - t.strftime(f) - except ValueError: - pass - - # check that this standard extension works - t.strftime("%f") - - def test_format(self): - dt = self.theclass(2007, 9, 10) - self.assertEqual(dt.__format__(""), str(dt)) - - # check that a derived class's __str__() gets called - class A(self.theclass): - def __str__(self): - return "A" - - a = A(2007, 9, 10) - self.assertEqual(a.__format__(""), "A") - - # check that a derived class's strftime gets called - class B(self.theclass): - def strftime(self, format_spec): - return "B" - - b = B(2007, 9, 10) - self.assertEqual(b.__format__(""), str(dt)) - - for fmt in [ - "m:%m d:%d y:%y", - "m:%m d:%d y:%y H:%H M:%M S:%S", - "%z %Z", - ]: - self.assertEqual(dt.__format__(fmt), dt.strftime(fmt)) - self.assertEqual(a.__format__(fmt), dt.strftime(fmt)) - self.assertEqual(b.__format__(fmt), "B") - - def test_resolution_info(self): - # XXX: Should min and max respect subclassing? - if issubclass(self.theclass, datetime): - expected_class = datetime - else: - expected_class = date - self.assertIsInstance(self.theclass.min, expected_class) - self.assertIsInstance(self.theclass.max, expected_class) - self.assertIsInstance(self.theclass.resolution, timedelta) - self.assertTrue(self.theclass.max > self.theclass.min) - - def test_extreme_timedelta(self): - big = self.theclass.max - self.theclass.min - # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds - n = (big.days * 24 * 3600 + big.seconds) * 1000000 + big.microseconds - # n == 315537897599999999 ~= 2**58.13 - justasbig = timedelta(0, 0, n) - self.assertEqual(big, justasbig) - self.assertEqual(self.theclass.min + big, self.theclass.max) - self.assertEqual(self.theclass.max - big, self.theclass.min) - - def test_timetuple(self): - for i in range(7): - # January 2, 1956 is a Monday (0) - d = self.theclass(1956, 1, 2 + i) - t = d.timetuple() - self.assertEqual(t, (1956, 1, 2 + i, 0, 0, 0, i, 2 + i, -1)) - # February 1, 1956 is a Wednesday (2) - d = self.theclass(1956, 2, 1 + i) - t = d.timetuple() - self.assertEqual(t, (1956, 2, 1 + i, 0, 0, 0, (2 + i) % 7, 32 + i, -1)) - # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day - # of the year. - d = self.theclass(1956, 3, 1 + i) - t = d.timetuple() - self.assertEqual(t, (1956, 3, 1 + i, 0, 0, 0, (3 + i) % 7, 61 + i, -1)) - self.assertEqual(t.tm_year, 1956) - self.assertEqual(t.tm_mon, 3) - self.assertEqual(t.tm_mday, 1 + i) - self.assertEqual(t.tm_hour, 0) - self.assertEqual(t.tm_min, 0) - self.assertEqual(t.tm_sec, 0) - self.assertEqual(t.tm_wday, (3 + i) % 7) - self.assertEqual(t.tm_yday, 61 + i) - self.assertEqual(t.tm_isdst, -1) - - def test_pickling(self): - args = 6, 7, 23 - orig = self.theclass(*args) - for pickler, unpickler, proto in pickle_choices: - green = pickler.dumps(orig, proto) - derived = unpickler.loads(green) - self.assertEqual(orig, derived) - - def test_compare(self): - t1 = self.theclass(2, 3, 4) - t2 = self.theclass(2, 3, 4) - self.assertEqual(t1, t2) - self.assertTrue(t1 <= t2) - self.assertTrue(t1 >= t2) - self.assertTrue(not t1 != t2) - self.assertTrue(not t1 < t2) - self.assertTrue(not t1 > t2) - - for args in (3, 3, 3), (2, 4, 4), (2, 3, 5): - t2 = self.theclass(*args) # this is larger than t1 - self.assertTrue(t1 < t2) - self.assertTrue(t2 > t1) - self.assertTrue(t1 <= t2) - self.assertTrue(t2 >= t1) - self.assertTrue(t1 != t2) - self.assertTrue(t2 != t1) - self.assertTrue(not t1 == t2) - self.assertTrue(not t2 == t1) - self.assertTrue(not t1 > t2) - self.assertTrue(not t2 < t1) - self.assertTrue(not t1 >= t2) - self.assertTrue(not t2 <= t1) - - for badarg in OTHERSTUFF: - self.assertEqual(t1 == badarg, False) - self.assertEqual(t1 != badarg, True) - self.assertEqual(badarg == t1, False) - self.assertEqual(badarg != t1, True) - - self.assertRaises(TypeError, lambda: t1 < badarg) - self.assertRaises(TypeError, lambda: t1 > badarg) - self.assertRaises(TypeError, lambda: t1 >= badarg) - self.assertRaises(TypeError, lambda: badarg <= t1) - self.assertRaises(TypeError, lambda: badarg < t1) - self.assertRaises(TypeError, lambda: badarg > t1) - self.assertRaises(TypeError, lambda: badarg >= t1) - - def test_mixed_compare(self): - our = self.theclass(2000, 4, 5) - - # Our class can be compared for equality to other classes - self.assertEqual(our == 1, False) - self.assertEqual(1 == our, False) - self.assertEqual(our != 1, True) - self.assertEqual(1 != our, True) - - # But the ordering is undefined - self.assertRaises(TypeError, lambda: our < 1) - self.assertRaises(TypeError, lambda: 1 < our) - - # Repeat those tests with a different class - - class SomeClass: - pass - - their = SomeClass() - self.assertEqual(our == their, False) - self.assertEqual(their == our, False) - self.assertEqual(our != their, True) - self.assertEqual(their != our, True) - self.assertRaises(TypeError, lambda: our < their) - self.assertRaises(TypeError, lambda: their < our) - - # However, if the other class explicitly defines ordering - # relative to our class, it is allowed to do so - - class LargerThanAnything: - def __lt__(self, other): - return False - - def __le__(self, other): - return isinstance(other, LargerThanAnything) - - def __eq__(self, other): - return isinstance(other, LargerThanAnything) - - def __ne__(self, other): - return not isinstance(other, LargerThanAnything) - - def __gt__(self, other): - return not isinstance(other, LargerThanAnything) - - def __ge__(self, other): - return True - - their = LargerThanAnything() - self.assertEqual(our == their, False) - self.assertEqual(their == our, False) - self.assertEqual(our != their, True) - self.assertEqual(their != our, True) - # self.assertEqual(our < their, True) - self.assertEqual(their < our, False) - - def test_bool(self): - # All dates are considered true. - self.assertTrue(self.theclass.min) - self.assertTrue(self.theclass.max) - - def test_strftime_y2k(self): - for y in (1, 49, 70, 99, 100, 999, 1000, 1970): - d = self.theclass(y, 1, 1) - # Issue 13305: For years < 1000, the value is not always - # padded to 4 digits across platforms. The C standard - # assumes year >= 1900, so it does not specify the number - # of digits. - if d.strftime("%Y") != "%04d" % y: - # Year 42 returns '42', not padded - self.assertEqual(d.strftime("%Y"), "%d" % y) - # '0042' is obtained anyway - self.assertEqual(d.strftime("%4Y"), "%04d" % y) - - def test_replace(self): - cls = self.theclass - args = [1, 2, 3] - base = cls(*args) - self.assertEqual(base, base.replace()) - - i = 0 - for name, newval in (("year", 2), ("month", 3), ("day", 4)): - newargs = args[:] - newargs[i] = newval - expected = cls(*newargs) - got = base.replace(**{name: newval}) - self.assertEqual(expected, got) - i += 1 - - # Out of bounds. - base = cls(2000, 2, 29) - self.assertRaises(ValueError, base.replace, year=2001) - - def test_subclass_date(self): - class C(self.theclass): - theAnswer = 42 - - def __new__(cls, *args, **kws): - temp = kws.copy() - extra = temp.pop("extra") - result = self.theclass.__new__(cls, *args, **temp) - result.extra = extra - return result - - def newmeth(self, start): - return start + self.year + self.month - - args = 2003, 4, 14 - - dt1 = self.theclass(*args) - dt2 = C(*args, **{"extra": 7}) - - self.assertEqual(dt2.__class__, C) - self.assertEqual(dt2.theAnswer, 42) - self.assertEqual(dt2.extra, 7) - self.assertEqual(dt1.toordinal(), dt2.toordinal()) - self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7) - - @unittest.skip("Skip pickling for MicroPython") - def test_pickling_subclass_date(self): - - args = 6, 7, 23 - orig = SubclassDate(*args) - for pickler, unpickler, proto in pickle_choices: - green = pickler.dumps(orig, proto) - derived = unpickler.loads(green) - self.assertEqual(orig, derived) - - def test_backdoor_resistance(self): - # For fast unpickling, the constructor accepts a pickle byte string. - # This is a low-overhead backdoor. A user can (by intent or - # mistake) pass a string directly, which (if it's the right length) - # will get treated like a pickle, and bypass the normal sanity - # checks in the constructor. This can create insane objects. - # The constructor doesn't want to burn the time to validate all - # fields, but does check the month field. This stops, e.g., - # datetime.datetime('1995-03-25') from yielding an insane object. - base = b"1995-03-25" - if not issubclass(self.theclass, datetime): - base = base[:4] - for month_byte in b"9", b"\0", b"\r", b"\xff": - self.assertRaises(TypeError, self.theclass, base[:2] + month_byte + base[3:]) - # Good bytes, but bad tzinfo: - self.assertRaises(TypeError, self.theclass, bytes([1] * len(base)), "EST") - - for ord_byte in range(1, 13): - # This shouldn't blow up because of the month byte alone. If - # the implementation changes to do more-careful checking, it may - # blow up because other fields are insane. - self.theclass(base[:2] + bytes([ord_byte]) + base[3:]) - - -############################################################################# -# datetime tests - - -class SubclassDatetime(datetime): - sub_var = 1 - - -class TestDateTime(TestDate): - - theclass = datetime - - def test_basic_attributes(self): - dt = self.theclass(2002, 3, 1, 12, 0) - self.assertEqual(dt.year, 2002) - self.assertEqual(dt.month, 3) - self.assertEqual(dt.day, 1) - self.assertEqual(dt.hour, 12) - self.assertEqual(dt.minute, 0) - self.assertEqual(dt.second, 0) - self.assertEqual(dt.microsecond, 0) - - def test_basic_attributes_nonzero(self): - # Make sure all attributes are non-zero so bugs in - # bit-shifting access show up. - dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000) - self.assertEqual(dt.year, 2002) - self.assertEqual(dt.month, 3) - self.assertEqual(dt.day, 1) - self.assertEqual(dt.hour, 12) - self.assertEqual(dt.minute, 59) - self.assertEqual(dt.second, 59) - self.assertEqual(dt.microsecond, 8000) - - def test_roundtrip(self): - for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7), self.theclass.now()): - # Verify dt -> string -> datetime identity. - s = repr(dt) - self.assertTrue(s.startswith("datetime.")) - s = s[9:] - dt2 = eval(s) - self.assertEqual(dt, dt2) - - # Verify identity via reconstructing from pieces. - dt2 = self.theclass( - dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond - ) - self.assertEqual(dt, dt2) - - def test_isoformat(self): - t = self.theclass(2, 3, 2, 4, 5, 1, 123) - self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123") - self.assertEqual(t.isoformat("T"), "0002-03-02T04:05:01.000123") - self.assertEqual(t.isoformat(" "), "0002-03-02 04:05:01.000123") - self.assertEqual(t.isoformat("\x00"), "0002-03-02\x0004:05:01.000123") - # str is ISO format with the separator forced to a blank. - self.assertEqual(str(t), "0002-03-02 04:05:01.000123") - - t = self.theclass(2, 3, 2) - self.assertEqual(t.isoformat(), "0002-03-02T00:00:00") - self.assertEqual(t.isoformat("T"), "0002-03-02T00:00:00") - self.assertEqual(t.isoformat(" "), "0002-03-02 00:00:00") - # str is ISO format with the separator forced to a blank. - self.assertEqual(str(t), "0002-03-02 00:00:00") - - def test_format(self): - dt = self.theclass(2007, 9, 10, 4, 5, 1, 123) - self.assertEqual(dt.__format__(""), str(dt)) - - # check that a derived class's __str__() gets called - class A(self.theclass): - def __str__(self): - return "A" - - a = A(2007, 9, 10, 4, 5, 1, 123) - self.assertEqual(a.__format__(""), "A") - - # check that a derived class's strftime gets called - class B(self.theclass): - def strftime(self, format_spec): - return "B" - - b = B(2007, 9, 10, 4, 5, 1, 123) - self.assertEqual(b.__format__(""), str(dt)) - - for fmt in [ - "m:%m d:%d y:%y", - "m:%m d:%d y:%y H:%H M:%M S:%S", - "%z %Z", - ]: - self.assertEqual(dt.__format__(fmt), dt.strftime(fmt)) - self.assertEqual(a.__format__(fmt), dt.strftime(fmt)) - self.assertEqual(b.__format__(fmt), "B") - - @unittest.skip("no time.ctime") - def test_more_ctime(self): - # Test fields that TestDate doesn't touch. - import time - - t = self.theclass(2002, 3, 2, 18, 3, 5, 123) - self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002") - # Oops! The next line fails on Win2K under MSVC 6, so it's commented - # out. The difference is that t.ctime() produces " 2" for the day, - # but platform ctime() produces "02" for the day. According to - # C99, t.ctime() is correct here. - # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple()))) - - # So test a case where that difference doesn't matter. - t = self.theclass(2002, 3, 22, 18, 3, 5, 123) - self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple()))) - - def test_tz_independent_comparing(self): - dt1 = self.theclass(2002, 3, 1, 9, 0, 0) - dt2 = self.theclass(2002, 3, 1, 10, 0, 0) - dt3 = self.theclass(2002, 3, 1, 9, 0, 0) - self.assertEqual(dt1, dt3) - self.assertTrue(dt2 > dt3) - - # Make sure comparison doesn't forget microseconds, and isn't done - # via comparing a float timestamp (an IEEE double doesn't have enough - # precision to span microsecond resolution across years 1 thru 9999, - # so comparing via timestamp necessarily calls some distinct values - # equal). - dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998) - us = timedelta(microseconds=1) - dt2 = dt1 + us - self.assertEqual(dt2 - dt1, us) - self.assertTrue(dt1 < dt2) - - def test_strftime_with_bad_tzname_replace(self): - # verify ok if tzinfo.tzname().replace() returns a non-string - class MyTzInfo(FixedOffset): - def tzname(self, dt): - class MyStr(str): - def replace(self, *args): - return None - - return MyStr("name") - - t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, "name")) - self.assertRaises(TypeError, t.strftime, "%Z") - - def test_bad_constructor_arguments(self): - # bad years - self.theclass(MINYEAR, 1, 1) # no exception - self.theclass(MAXYEAR, 1, 1) # no exception - self.assertRaises(ValueError, self.theclass, MINYEAR - 1, 1, 1) - self.assertRaises(ValueError, self.theclass, MAXYEAR + 1, 1, 1) - # bad months - self.theclass(2000, 1, 1) # no exception - self.theclass(2000, 12, 1) # no exception - self.assertRaises(ValueError, self.theclass, 2000, 0, 1) - self.assertRaises(ValueError, self.theclass, 2000, 13, 1) - # bad days - self.theclass(2000, 2, 29) # no exception - self.theclass(2004, 2, 29) # no exception - self.theclass(2400, 2, 29) # no exception - self.assertRaises(ValueError, self.theclass, 2000, 2, 30) - self.assertRaises(ValueError, self.theclass, 2001, 2, 29) - self.assertRaises(ValueError, self.theclass, 2100, 2, 29) - self.assertRaises(ValueError, self.theclass, 1900, 2, 29) - self.assertRaises(ValueError, self.theclass, 2000, 1, 0) - self.assertRaises(ValueError, self.theclass, 2000, 1, 32) - # bad hours - self.theclass(2000, 1, 31, 0) # no exception - self.theclass(2000, 1, 31, 23) # no exception - self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1) - self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24) - # bad minutes - self.theclass(2000, 1, 31, 23, 0) # no exception - self.theclass(2000, 1, 31, 23, 59) # no exception - self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1) - self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60) - # bad seconds - self.theclass(2000, 1, 31, 23, 59, 0) # no exception - self.theclass(2000, 1, 31, 23, 59, 59) # no exception - self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1) - self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60) - # bad microseconds - self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception - self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception - self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 59, -1) - self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 59, 1000000) - - def test_hash_equality(self): - d = self.theclass(2000, 12, 31, 23, 30, 17) - e = self.theclass(2000, 12, 31, 23, 30, 17) - self.assertEqual(d, e) - self.assertEqual(hash(d), hash(e)) - - dic = {d: 1} - dic[e] = 2 - self.assertEqual(len(dic), 1) - self.assertEqual(dic[d], 2) - self.assertEqual(dic[e], 2) - - d = self.theclass(2001, 1, 1, 0, 5, 17) - e = self.theclass(2001, 1, 1, 0, 5, 17) - self.assertEqual(d, e) - self.assertEqual(hash(d), hash(e)) - - dic = {d: 1} - dic[e] = 2 - self.assertEqual(len(dic), 1) - self.assertEqual(dic[d], 2) - self.assertEqual(dic[e], 2) - - def test_computations(self): - a = self.theclass(2002, 1, 31) - b = self.theclass(1956, 1, 31) - diff = a - b - self.assertEqual(diff.days, 46 * 365 + len(range(1956, 2002, 4))) - self.assertEqual(diff.seconds, 0) - self.assertEqual(diff.microseconds, 0) - a = self.theclass(2002, 3, 2, 17, 6) - millisec = timedelta(0, 0, 1000) - hour = timedelta(0, 3600) - day = timedelta(1) - week = timedelta(7) - self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6)) - self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6)) - self.assertEqual(a + 10 * hour, self.theclass(2002, 3, 3, 3, 6)) - self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6)) - self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6)) - self.assertEqual(a - hour, a + -hour) - self.assertEqual(a - 20 * hour, self.theclass(2002, 3, 1, 21, 6)) - self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6)) - self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6)) - self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6)) - self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6)) - self.assertEqual(a + 52 * week, self.theclass(2003, 3, 1, 17, 6)) - self.assertEqual(a - 52 * week, self.theclass(2001, 3, 3, 17, 6)) - self.assertEqual((a + week) - a, week) - self.assertEqual((a + day) - a, day) - self.assertEqual((a + hour) - a, hour) - self.assertEqual((a + millisec) - a, millisec) - self.assertEqual((a - week) - a, -week) - self.assertEqual((a - day) - a, -day) - self.assertEqual((a - hour) - a, -hour) - self.assertEqual((a - millisec) - a, -millisec) - self.assertEqual(a - (a + week), -week) - self.assertEqual(a - (a + day), -day) - self.assertEqual(a - (a + hour), -hour) - self.assertEqual(a - (a + millisec), -millisec) - self.assertEqual(a - (a - week), week) - self.assertEqual(a - (a - day), day) - self.assertEqual(a - (a - hour), hour) - self.assertEqual(a - (a - millisec), millisec) - self.assertEqual( - a + (week + day + hour + millisec), self.theclass(2002, 3, 10, 18, 6, 0, 1000) - ) - self.assertEqual( - a + (week + day + hour + millisec), (((a + week) + day) + hour) + millisec - ) - self.assertEqual( - a - (week + day + hour + millisec), self.theclass(2002, 2, 22, 16, 5, 59, 999000) - ) - self.assertEqual( - a - (week + day + hour + millisec), (((a - week) - day) - hour) - millisec - ) - # Add/sub ints or floats should be illegal - for i in 1, 1.0: - self.assertRaises(TypeError, lambda: a + i) - self.assertRaises(TypeError, lambda: a - i) - self.assertRaises(TypeError, lambda: i + a) - self.assertRaises(TypeError, lambda: i - a) - - # delta - datetime is senseless. - self.assertRaises(TypeError, lambda: day - a) - # mixing datetime and (delta or datetime) via * or // is senseless - self.assertRaises(TypeError, lambda: day * a) - self.assertRaises(TypeError, lambda: a * day) - self.assertRaises(TypeError, lambda: day // a) - self.assertRaises(TypeError, lambda: a // day) - self.assertRaises(TypeError, lambda: a * a) - self.assertRaises(TypeError, lambda: a // a) - # datetime + datetime is senseless - self.assertRaises(TypeError, lambda: a + a) - - def test_pickling(self): - args = 6, 7, 23, 20, 59, 1, 64**2 - orig = self.theclass(*args) - for pickler, unpickler, proto in pickle_choices: - green = pickler.dumps(orig, proto) - derived = unpickler.loads(green) - self.assertEqual(orig, derived) - - def test_more_pickling(self): - a = self.theclass(2003, 2, 7, 16, 48, 37, 444116) - s = pickle.dumps(a) - b = pickle.loads(s) - self.assertEqual(b.year, 2003) - self.assertEqual(b.month, 2) - self.assertEqual(b.day, 7) - - @unittest.skip("Skip pickling for MicroPython") - def test_pickling_subclass_datetime(self): - args = 6, 7, 23, 20, 59, 1, 64**2 - orig = SubclassDatetime(*args) - for pickler, unpickler, proto in pickle_choices: - green = pickler.dumps(orig, proto) - derived = unpickler.loads(green) - self.assertEqual(orig, derived) - - def test_more_compare(self): - # The test_compare() inherited from TestDate covers the error cases. - # We just want to test lexicographic ordering on the members datetime - # has that date lacks. - args = [2000, 11, 29, 20, 58, 16, 999998] - t1 = self.theclass(*args) - t2 = self.theclass(*args) - self.assertEqual(t1, t2) - self.assertTrue(t1 <= t2) - self.assertTrue(t1 >= t2) - self.assertTrue(not t1 != t2) - self.assertTrue(not t1 < t2) - self.assertTrue(not t1 > t2) - - for i in range(len(args)): - newargs = args[:] - newargs[i] = args[i] + 1 - t2 = self.theclass(*newargs) # this is larger than t1 - self.assertTrue(t1 < t2) - self.assertTrue(t2 > t1) - self.assertTrue(t1 <= t2) - self.assertTrue(t2 >= t1) - self.assertTrue(t1 != t2) - self.assertTrue(t2 != t1) - self.assertTrue(not t1 == t2) - self.assertTrue(not t2 == t1) - self.assertTrue(not t1 > t2) - self.assertTrue(not t2 < t1) - self.assertTrue(not t1 >= t2) - self.assertTrue(not t2 <= t1) - - # A helper for timestamp constructor tests. - def verify_field_equality(self, expected, got): - self.assertEqual(expected.tm_year, got.year) - self.assertEqual(expected.tm_mon, got.month) - self.assertEqual(expected.tm_mday, got.day) - self.assertEqual(expected.tm_hour, got.hour) - self.assertEqual(expected.tm_min, got.minute) - self.assertEqual(expected.tm_sec, got.second) - - def test_fromtimestamp(self): - import time - - ts = time.time() - expected = time.localtime(ts) - got = self.theclass.fromtimestamp(ts) - self.verify_field_equality(expected, got) - - def test_utcfromtimestamp(self): - import time - - ts = time.time() - expected = time.gmtime(ts) - got = self.theclass.utcfromtimestamp(ts) - self.verify_field_equality(expected, got) - - # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in - # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0). - # @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0') - @unittest.skip("no support.run_with_tz") - def test_timestamp_naive(self): - t = self.theclass(1970, 1, 1) - self.assertEqual(t.timestamp(), 18000.0) - t = self.theclass(1970, 1, 1, 1, 2, 3, 4) - self.assertEqual(t.timestamp(), 18000.0 + 3600 + 2 * 60 + 3 + 4 * 1e-6) - # Missing hour may produce platform-dependent result - t = self.theclass(2012, 3, 11, 2, 30) - self.assertIn( - self.theclass.fromtimestamp(t.timestamp()), - [t - timedelta(hours=1), t + timedelta(hours=1)], - ) - # Ambiguous hour defaults to DST - t = self.theclass(2012, 11, 4, 1, 30) - self.assertEqual(self.theclass.fromtimestamp(t.timestamp()), t) - - # Timestamp may raise an overflow error on some platforms - for t in [self.theclass(1, 1, 1), self.theclass(9999, 12, 12)]: - try: - s = t.timestamp() - except OverflowError: - pass - else: - self.assertEqual(self.theclass.fromtimestamp(s), t) - - def test_timestamp_aware(self): - t = self.theclass(1970, 1, 1, tzinfo=timezone.utc) - self.assertEqual(t.timestamp(), 0.0) - t = self.theclass(1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone.utc) - self.assertEqual(t.timestamp(), 3600 + 2 * 60 + 3 + 4 * 1e-6) - t = self.theclass(1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone(timedelta(hours=-5), "EST")) - self.assertEqual(t.timestamp(), 18000 + 3600 + 2 * 60 + 3 + 4 * 1e-6) - - def test_microsecond_rounding(self): - for fts in [self.theclass.fromtimestamp, self.theclass.utcfromtimestamp]: - zero = fts(0) - self.assertEqual(zero.second, 0) - self.assertEqual(zero.microsecond, 0) - try: - minus_one = fts(-1e-6) - except OSError: - # localtime(-1) and gmtime(-1) is not supported on Windows - pass - else: - self.assertEqual(minus_one.second, 59) - self.assertEqual(minus_one.microsecond, 999999) - - t = fts(-1e-8) - self.assertEqual(t, minus_one) - t = fts(-9e-7) - self.assertEqual(t, minus_one) - t = fts(-1e-7) - self.assertEqual(t, minus_one) - - t = fts(1e-7) - self.assertEqual(t, zero) - t = fts(9e-7) - self.assertEqual(t, zero) - t = fts(0.99999949) - self.assertEqual(t.second, 0) - self.assertEqual(t.microsecond, 999999) - t = fts(0.9999999) - self.assertEqual(t.second, 0) - self.assertEqual(t.microsecond, 999999) - - @unittest.skip("Skip for MicroPython") - def test_insane_fromtimestamp(self): - # It's possible that some platform maps time_t to double, - # and that this test will fail there. This test should - # exempt such platforms (provided they return reasonable - # results!). - for insane in -1e200, 1e200: - self.assertRaises(OverflowError, self.theclass.fromtimestamp, insane) - - @unittest.skip("Skip pickling for MicroPython") - def test_insane_utcfromtimestamp(self): - # It's possible that some platform maps time_t to double, - # and that this test will fail there. This test should - # exempt such platforms (provided they return reasonable - # results!). - for insane in -1e200, 1e200: - self.assertRaises(OverflowError, self.theclass.utcfromtimestamp, insane) - - @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps") - def test_negative_float_fromtimestamp(self): - # The result is tz-dependent; at least test that this doesn't - # fail (like it did before bug 1646728 was fixed). - self.theclass.fromtimestamp(-1.05) - - @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps") - def test_negative_float_utcfromtimestamp(self): - d = self.theclass.utcfromtimestamp(-1.05) - self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000)) - - def test_utcnow(self): - import time - - # Call it a success if utcnow() and utcfromtimestamp() are within - # a second of each other. - tolerance = timedelta(seconds=1) - for dummy in range(3): - from_now = self.theclass.utcnow() - from_timestamp = self.theclass.utcfromtimestamp(time.time()) - if abs(from_timestamp - from_now) <= tolerance: - break - # Else try again a few times. - self.assertTrue(abs(from_timestamp - from_now) <= tolerance) - - @unittest.skip("no _strptime module") - def test_strptime(self): - string = "2004-12-01 13:02:47.197" - format = "%Y-%m-%d %H:%M:%S.%f" - expected = _strptime._strptime_datetime(self.theclass, string, format) - got = self.theclass.strptime(string, format) - self.assertEqual(expected, got) - self.assertIs(type(expected), self.theclass) - self.assertIs(type(got), self.theclass) - - strptime = self.theclass.strptime - self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE) - self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE) - # Only local timezone and UTC are supported - for tzseconds, tzname in ((0, "UTC"), (0, "GMT"), (-_time.timezone, _time.tzname[0])): - if tzseconds < 0: - sign = "-" - seconds = -tzseconds - else: - sign = "+" - seconds = tzseconds - hours, minutes = divmod(seconds // 60, 60) - dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname) - dt = strptime(dtstr, "%z %Z") - self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds)) - self.assertEqual(dt.tzname(), tzname) - # Can produce inconsistent datetime - dtstr, fmt = "+1234 UTC", "%z %Z" - dt = strptime(dtstr, fmt) - self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE) - self.assertEqual(dt.tzname(), "UTC") - # yet will roundtrip - self.assertEqual(dt.strftime(fmt), dtstr) - - # Produce naive datetime if no %z is provided - self.assertEqual(strptime("UTC", "%Z").tzinfo, None) - - with self.assertRaises(ValueError): - strptime("-2400", "%z") - with self.assertRaises(ValueError): - strptime("-000", "%z") - - def test_more_timetuple(self): - # This tests fields beyond those tested by the TestDate.test_timetuple. - t = self.theclass(2004, 12, 31, 6, 22, 33) - self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1)) - self.assertEqual( - t.timetuple(), - ( - t.year, - t.month, - t.day, - t.hour, - t.minute, - t.second, - t.weekday(), - t.toordinal() - date(t.year, 1, 1).toordinal() + 1, - -1, - ), - ) - tt = t.timetuple() - self.assertEqual(tt.tm_year, t.year) - self.assertEqual(tt.tm_mon, t.month) - self.assertEqual(tt.tm_mday, t.day) - self.assertEqual(tt.tm_hour, t.hour) - self.assertEqual(tt.tm_min, t.minute) - self.assertEqual(tt.tm_sec, t.second) - self.assertEqual(tt.tm_wday, t.weekday()) - self.assertEqual(tt.tm_yday, t.toordinal() - date(t.year, 1, 1).toordinal() + 1) - self.assertEqual(tt.tm_isdst, -1) - - def test_more_strftime(self): - # This tests fields beyond those tested by the TestDate.test_strftime. - t = self.theclass(2004, 12, 31, 6, 22, 33, 47) - self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"), "12 31 04 000047 33 22 06 366") - - def test_extract(self): - dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234) - self.assertEqual(dt.date(), date(2002, 3, 4)) - self.assertEqual(dt.time(), time(18, 45, 3, 1234)) - - def test_combine(self): - d = date(2002, 3, 4) - t = time(18, 45, 3, 1234) - expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234) - combine = self.theclass.combine - dt = combine(d, t) - self.assertEqual(dt, expected) - - dt = combine(time=t, date=d) - self.assertEqual(dt, expected) - - self.assertEqual(d, dt.date()) - self.assertEqual(t, dt.time()) - self.assertEqual(dt, combine(dt.date(), dt.time())) - - self.assertRaises(TypeError, combine) # need an arg - self.assertRaises(TypeError, combine, d) # need two args - self.assertRaises(TypeError, combine, t, d) # args reversed - self.assertRaises(TypeError, combine, d, t, 1) # too many args - self.assertRaises(TypeError, combine, "date", "time") # wrong types - self.assertRaises(TypeError, combine, d, "time") # wrong type - self.assertRaises(TypeError, combine, "date", t) # wrong type - - def test_replace(self): - cls = self.theclass - args = [1, 2, 3, 4, 5, 6, 7] - base = cls(*args) - self.assertEqual(base, base.replace()) - - i = 0 - for name, newval in ( - ("year", 2), - ("month", 3), - ("day", 4), - ("hour", 5), - ("minute", 6), - ("second", 7), - ("microsecond", 8), - ): - newargs = args[:] - newargs[i] = newval - expected = cls(*newargs) - got = base.replace(**{name: newval}) - self.assertEqual(expected, got) - i += 1 - - # Out of bounds. - base = cls(2000, 2, 29) - self.assertRaises(ValueError, base.replace, year=2001) - - def test_astimezone(self): - # Pretty boring! The TZ test is more interesting here. astimezone() - # simply can't be applied to a naive object. - dt = self.theclass.now() - f = FixedOffset(44, "") - self.assertRaises(ValueError, dt.astimezone) # naive - self.assertRaises(TypeError, dt.astimezone, f, f) # too many args - self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type - self.assertRaises(ValueError, dt.astimezone, f) # naive - self.assertRaises(ValueError, dt.astimezone, tz=f) # naive - - class Bogus(tzinfo): - def utcoffset(self, dt): - return None - - def dst(self, dt): - return timedelta(0) - - bog = Bogus() - self.assertRaises(ValueError, dt.astimezone, bog) # naive - self.assertRaises(ValueError, dt.replace(tzinfo=bog).astimezone, f) - - class AlsoBogus(tzinfo): - def utcoffset(self, dt): - return timedelta(0) - - def dst(self, dt): - return None - - alsobog = AlsoBogus() - self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive - - def test_subclass_datetime(self): - class C(self.theclass): - theAnswer = 42 - - def __new__(cls, *args, **kws): - temp = kws.copy() - extra = temp.pop("extra") - result = self.theclass.__new__(cls, *args, **temp) - result.extra = extra - return result - - def newmeth(self, start): - return start + self.year + self.month + self.second - - args = 2003, 4, 14, 12, 13, 41 - - dt1 = self.theclass(*args) - dt2 = C(*args, **{"extra": 7}) - - self.assertEqual(dt2.__class__, C) - self.assertEqual(dt2.theAnswer, 42) - self.assertEqual(dt2.extra, 7) - self.assertEqual(dt1.toordinal(), dt2.toordinal()) - self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month + dt1.second - 7) - - -class TestSubclassDateTime(TestDateTime): - theclass = SubclassDatetime - # Override tests not designed for subclass - def test_roundtrip(self): - pass - - -class SubclassTime(time): - sub_var = 1 - - -class TestTime(HarmlessMixedComparison, unittest.TestCase): - - theclass = time - - def test_basic_attributes(self): - t = self.theclass(12, 0) - self.assertEqual(t.hour, 12) - self.assertEqual(t.minute, 0) - self.assertEqual(t.second, 0) - self.assertEqual(t.microsecond, 0) - - def test_basic_attributes_nonzero(self): - # Make sure all attributes are non-zero so bugs in - # bit-shifting access show up. - t = self.theclass(12, 59, 59, 8000) - self.assertEqual(t.hour, 12) - self.assertEqual(t.minute, 59) - self.assertEqual(t.second, 59) - self.assertEqual(t.microsecond, 8000) - - def test_roundtrip(self): - t = self.theclass(1, 2, 3, 4) - - # Verify t -> string -> time identity. - s = repr(t) - self.assertTrue(s.startswith("datetime.")) - s = s[9:] - t2 = eval(s) - self.assertEqual(t, t2) - - # Verify identity via reconstructing from pieces. - t2 = self.theclass(t.hour, t.minute, t.second, t.microsecond) - self.assertEqual(t, t2) - - def test_comparing(self): - args = [1, 2, 3, 4] - t1 = self.theclass(*args) - t2 = self.theclass(*args) - self.assertEqual(t1, t2) - self.assertTrue(t1 <= t2) - self.assertTrue(t1 >= t2) - self.assertTrue(not t1 != t2) - self.assertTrue(not t1 < t2) - self.assertTrue(not t1 > t2) - - for i in range(len(args)): - newargs = args[:] - newargs[i] = args[i] + 1 - t2 = self.theclass(*newargs) # this is larger than t1 - self.assertTrue(t1 < t2) - self.assertTrue(t2 > t1) - self.assertTrue(t1 <= t2) - self.assertTrue(t2 >= t1) - self.assertTrue(t1 != t2) - self.assertTrue(t2 != t1) - self.assertTrue(not t1 == t2) - self.assertTrue(not t2 == t1) - self.assertTrue(not t1 > t2) - self.assertTrue(not t2 < t1) - self.assertTrue(not t1 >= t2) - self.assertTrue(not t2 <= t1) - - for badarg in OTHERSTUFF: - self.assertEqual(t1 == badarg, False) - self.assertEqual(t1 != badarg, True) - self.assertEqual(badarg == t1, False) - self.assertEqual(badarg != t1, True) - - self.assertRaises(TypeError, lambda: t1 <= badarg) - self.assertRaises(TypeError, lambda: t1 < badarg) - self.assertRaises(TypeError, lambda: t1 > badarg) - self.assertRaises(TypeError, lambda: t1 >= badarg) - self.assertRaises(TypeError, lambda: badarg <= t1) - self.assertRaises(TypeError, lambda: badarg < t1) - self.assertRaises(TypeError, lambda: badarg > t1) - self.assertRaises(TypeError, lambda: badarg >= t1) - - def test_bad_constructor_arguments(self): - # bad hours - self.theclass(0, 0) # no exception - self.theclass(23, 0) # no exception - self.assertRaises(ValueError, self.theclass, -1, 0) - self.assertRaises(ValueError, self.theclass, 24, 0) - # bad minutes - self.theclass(23, 0) # no exception - self.theclass(23, 59) # no exception - self.assertRaises(ValueError, self.theclass, 23, -1) - self.assertRaises(ValueError, self.theclass, 23, 60) - # bad seconds - self.theclass(23, 59, 0) # no exception - self.theclass(23, 59, 59) # no exception - self.assertRaises(ValueError, self.theclass, 23, 59, -1) - self.assertRaises(ValueError, self.theclass, 23, 59, 60) - # bad microseconds - self.theclass(23, 59, 59, 0) # no exception - self.theclass(23, 59, 59, 999999) # no exception - self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1) - self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000) - - def test_hash_equality(self): - d = self.theclass(23, 30, 17) - e = self.theclass(23, 30, 17) - self.assertEqual(d, e) - self.assertEqual(hash(d), hash(e)) - - dic = {d: 1} - dic[e] = 2 - self.assertEqual(len(dic), 1) - self.assertEqual(dic[d], 2) - self.assertEqual(dic[e], 2) - - d = self.theclass(0, 5, 17) - e = self.theclass(0, 5, 17) - self.assertEqual(d, e) - self.assertEqual(hash(d), hash(e)) - - dic = {d: 1} - dic[e] = 2 - self.assertEqual(len(dic), 1) - self.assertEqual(dic[d], 2) - self.assertEqual(dic[e], 2) - - def test_isoformat(self): - t = self.theclass(4, 5, 1, 123) - self.assertEqual(t.isoformat(), "04:05:01.000123") - self.assertEqual(t.isoformat(), str(t)) - - t = self.theclass() - self.assertEqual(t.isoformat(), "00:00:00") - self.assertEqual(t.isoformat(), str(t)) - - t = self.theclass(microsecond=1) - self.assertEqual(t.isoformat(), "00:00:00.000001") - self.assertEqual(t.isoformat(), str(t)) - - t = self.theclass(microsecond=10) - self.assertEqual(t.isoformat(), "00:00:00.000010") - self.assertEqual(t.isoformat(), str(t)) - - t = self.theclass(microsecond=100) - self.assertEqual(t.isoformat(), "00:00:00.000100") - self.assertEqual(t.isoformat(), str(t)) - - t = self.theclass(microsecond=1000) - self.assertEqual(t.isoformat(), "00:00:00.001000") - self.assertEqual(t.isoformat(), str(t)) - - t = self.theclass(microsecond=10000) - self.assertEqual(t.isoformat(), "00:00:00.010000") - self.assertEqual(t.isoformat(), str(t)) - - t = self.theclass(microsecond=100000) - self.assertEqual(t.isoformat(), "00:00:00.100000") - self.assertEqual(t.isoformat(), str(t)) - - def test_1653736(self): - # verify it doesn't accept extra keyword arguments - t = self.theclass(second=1) - self.assertRaises(TypeError, t.isoformat, foo=3) - - def test_strftime(self): - t = self.theclass(1, 2, 3, 4) - self.assertEqual(t.strftime("%H %M %S %f"), "01 02 03 000004") - # A naive object replaces %z and %Z with empty strings. - self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''") - - def test_format(self): - t = self.theclass(1, 2, 3, 4) - self.assertEqual(t.__format__(""), str(t)) - - # check that a derived class's __str__() gets called - class A(self.theclass): - def __str__(self): - return "A" - - a = A(1, 2, 3, 4) - self.assertEqual(a.__format__(""), "A") - - # check that a derived class's strftime gets called - class B(self.theclass): - def strftime(self, format_spec): - return "B" - - b = B(1, 2, 3, 4) - self.assertEqual(b.__format__(""), str(t)) - - for fmt in [ - "%H %M %S", - ]: - self.assertEqual(t.__format__(fmt), t.strftime(fmt)) - self.assertEqual(a.__format__(fmt), t.strftime(fmt)) - self.assertEqual(b.__format__(fmt), "B") - - def test_str(self): - self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004") - self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000") - self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000") - self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03") - self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00") - - def test_repr(self): - name = "datetime." + self.theclass.__name__ - self.assertEqual(repr(self.theclass(1, 2, 3, 4)), "%s(1, 2, 3, 4)" % name) - self.assertEqual(repr(self.theclass(10, 2, 3, 4000)), "%s(10, 2, 3, 4000)" % name) - self.assertEqual(repr(self.theclass(0, 2, 3, 400000)), "%s(0, 2, 3, 400000)" % name) - self.assertEqual(repr(self.theclass(12, 2, 3, 0)), "%s(12, 2, 3)" % name) - self.assertEqual(repr(self.theclass(23, 15, 0, 0)), "%s(23, 15)" % name) - - def test_resolution_info(self): - self.assertIsInstance(self.theclass.min, self.theclass) - self.assertIsInstance(self.theclass.max, self.theclass) - self.assertIsInstance(self.theclass.resolution, timedelta) - self.assertTrue(self.theclass.max > self.theclass.min) - - def test_pickling(self): - args = 20, 59, 16, 64**2 - orig = self.theclass(*args) - for pickler, unpickler, proto in pickle_choices: - green = pickler.dumps(orig, proto) - derived = unpickler.loads(green) - self.assertEqual(orig, derived) - - @unittest.skip("Skip pickling for MicroPython") - def test_pickling_subclass_time(self): - args = 20, 59, 16, 64**2 - orig = SubclassTime(*args) - for pickler, unpickler, proto in pickle_choices: - green = pickler.dumps(orig, proto) - derived = unpickler.loads(green) - self.assertEqual(orig, derived) - - def test_bool(self): - cls = self.theclass - self.assertTrue(cls(1)) - self.assertTrue(cls(0, 1)) - self.assertTrue(cls(0, 0, 1)) - self.assertTrue(cls(0, 0, 0, 1)) - self.assertTrue(not cls(0)) - self.assertTrue(not cls()) - - def test_replace(self): - cls = self.theclass - args = [1, 2, 3, 4] - base = cls(*args) - self.assertEqual(base, base.replace()) - - i = 0 - for name, newval in (("hour", 5), ("minute", 6), ("second", 7), ("microsecond", 8)): - newargs = args[:] - newargs[i] = newval - expected = cls(*newargs) - got = base.replace(**{name: newval}) - self.assertEqual(expected, got) - i += 1 - - # Out of bounds. - base = cls(1) - self.assertRaises(ValueError, base.replace, hour=24) - self.assertRaises(ValueError, base.replace, minute=-1) - self.assertRaises(ValueError, base.replace, second=100) - self.assertRaises(ValueError, base.replace, microsecond=1000000) - - def test_subclass_time(self): - class C(self.theclass): - theAnswer = 42 - - def __new__(cls, *args, **kws): - temp = kws.copy() - extra = temp.pop("extra") - result = self.theclass.__new__(cls, *args, **temp) - result.extra = extra - return result - - def newmeth(self, start): - return start + self.hour + self.second - - args = 4, 5, 6 - - dt1 = self.theclass(*args) - dt2 = C(*args, **{"extra": 7}) - - self.assertEqual(dt2.__class__, C) - self.assertEqual(dt2.theAnswer, 42) - self.assertEqual(dt2.extra, 7) - self.assertEqual(dt1.isoformat(), dt2.isoformat()) - self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7) - - def test_backdoor_resistance(self): - # see TestDate.test_backdoor_resistance(). - base = "2:59.0" - for hour_byte in " ", "9", chr(24), "\xff": - self.assertRaises(TypeError, self.theclass, hour_byte + base[1:]) - - -# A mixin for classes with a tzinfo= argument. Subclasses must define -# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever) -# must be legit (which is true for time and datetime). -class TZInfoBase: - def test_argument_passing(self): - cls = self.theclass - # A datetime passes itself on, a time passes None. - class introspective(tzinfo): - def tzname(self, dt): - return dt and "real" or "none" - - def utcoffset(self, dt): - return timedelta(minutes=dt and 42 or -42) - - dst = utcoffset - - obj = cls(1, 2, 3, tzinfo=introspective()) - - expected = cls is time and "none" or "real" - self.assertEqual(obj.tzname(), expected) - - expected = timedelta(minutes=(cls is time and -42 or 42)) - self.assertEqual(obj.utcoffset(), expected) - self.assertEqual(obj.dst(), expected) - - def test_bad_tzinfo_classes(self): - cls = self.theclass - self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12) - - class NiceTry(object): - def __init__(self): - pass - - def utcoffset(self, dt): - pass - - self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry) - - class BetterTry(tzinfo): - def __init__(self): - pass - - def utcoffset(self, dt): - pass - - b = BetterTry() - t = cls(1, 1, 1, tzinfo=b) - self.assertTrue(t.tzinfo is b) - - def test_utc_offset_out_of_bounds(self): - class Edgy(tzinfo): - def __init__(self, offset): - self.offset = timedelta(minutes=offset) - - def utcoffset(self, dt): - return self.offset - - cls = self.theclass - for offset, legit in ((-1440, False), (-1439, True), (1439, True), (1440, False)): - if cls is time: - t = cls(1, 2, 3, tzinfo=Edgy(offset)) - elif cls is datetime: - t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset)) - else: - assert 0, "impossible" - if legit: - aofs = abs(offset) - h, m = divmod(aofs, 60) - tag = "%c%02d:%02d" % (offset < 0 and "-" or "+", h, m) - if isinstance(t, datetime): - t = t.timetz() - self.assertEqual(str(t), "01:02:03" + tag) - else: - self.assertRaises(ValueError, str, t) - - def test_tzinfo_classes(self): - cls = self.theclass - - class C1(tzinfo): - def utcoffset(self, dt): - return None - - def dst(self, dt): - return None - - def tzname(self, dt): - return None - - for t in (cls(1, 1, 1), cls(1, 1, 1, tzinfo=None), cls(1, 1, 1, tzinfo=C1())): - self.assertTrue(t.utcoffset() is None) - self.assertTrue(t.dst() is None) - self.assertTrue(t.tzname() is None) - - class C3(tzinfo): - def utcoffset(self, dt): - return timedelta(minutes=-1439) - - def dst(self, dt): - return timedelta(minutes=1439) - - def tzname(self, dt): - return "aname" - - t = cls(1, 1, 1, tzinfo=C3()) - self.assertEqual(t.utcoffset(), timedelta(minutes=-1439)) - self.assertEqual(t.dst(), timedelta(minutes=1439)) - self.assertEqual(t.tzname(), "aname") - - # Wrong types. - class C4(tzinfo): - def utcoffset(self, dt): - return "aname" - - def dst(self, dt): - return 7 - - def tzname(self, dt): - return 0 - - t = cls(1, 1, 1, tzinfo=C4()) - self.assertRaises(TypeError, t.utcoffset) - self.assertRaises(TypeError, t.dst) - self.assertRaises(TypeError, t.tzname) - - # Offset out of range. - class C6(tzinfo): - def utcoffset(self, dt): - return timedelta(hours=-24) - - def dst(self, dt): - return timedelta(hours=24) - - t = cls(1, 1, 1, tzinfo=C6()) - self.assertRaises(ValueError, t.utcoffset) - self.assertRaises(ValueError, t.dst) - - # Not a whole number of minutes. - class C7(tzinfo): - def utcoffset(self, dt): - return timedelta(seconds=61) - - def dst(self, dt): - return timedelta(microseconds=-81) - - t = cls(1, 1, 1, tzinfo=C7()) - self.assertRaises(ValueError, t.utcoffset) - self.assertRaises(ValueError, t.dst) - - def test_aware_compare(self): - cls = self.theclass - - # Ensure that utcoffset() gets ignored if the comparands have - # the same tzinfo member. - class OperandDependentOffset(tzinfo): - def utcoffset(self, t): - if t.minute < 10: - # d0 and d1 equal after adjustment - return timedelta(minutes=t.minute) - else: - # d2 off in the weeds - return timedelta(minutes=59) - - base = cls(8, 9, 10, tzinfo=OperandDependentOffset()) - d0 = base.replace(minute=3) - d1 = base.replace(minute=9) - d2 = base.replace(minute=11) - for x in d0, d1, d2: - for y in d0, d1, d2: - for op in lt, le, gt, ge, eq, ne: - got = op(x, y) - expected = op(x.minute, y.minute) - self.assertEqual(got, expected) - - # However, if they're different members, uctoffset is not ignored. - # Note that a time can't actually have an operand-depedent offset, - # though (and time.utcoffset() passes None to tzinfo.utcoffset()), - # so skip this test for time. - if cls is not time: - d0 = base.replace(minute=3, tzinfo=OperandDependentOffset()) - d1 = base.replace(minute=9, tzinfo=OperandDependentOffset()) - d2 = base.replace(minute=11, tzinfo=OperandDependentOffset()) - for x in d0, d1, d2: - for y in d0, d1, d2: - got = (x > y) - (x < y) - if (x is d0 or x is d1) and (y is d0 or y is d1): - expected = 0 - elif x is y is d2: - expected = 0 - elif x is d2: - expected = -1 - else: - assert y is d2 - expected = 1 - self.assertEqual(got, expected) - - -# Testing time objects with a non-None tzinfo. -class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase): - theclass = time - - def test_empty(self): - t = self.theclass() - self.assertEqual(t.hour, 0) - self.assertEqual(t.minute, 0) - self.assertEqual(t.second, 0) - self.assertEqual(t.microsecond, 0) - self.assertTrue(t.tzinfo is None) - - def test_zones(self): - est = FixedOffset(-300, "EST", 1) - utc = FixedOffset(0, "UTC", -2) - met = FixedOffset(60, "MET", 3) - t1 = time(7, 47, tzinfo=est) - t2 = time(12, 47, tzinfo=utc) - t3 = time(13, 47, tzinfo=met) - t4 = time(microsecond=40) - t5 = time(microsecond=40, tzinfo=utc) - - self.assertEqual(t1.tzinfo, est) - self.assertEqual(t2.tzinfo, utc) - self.assertEqual(t3.tzinfo, met) - self.assertTrue(t4.tzinfo is None) - self.assertEqual(t5.tzinfo, utc) - - self.assertEqual(t1.utcoffset(), timedelta(minutes=-300)) - self.assertEqual(t2.utcoffset(), timedelta(minutes=0)) - self.assertEqual(t3.utcoffset(), timedelta(minutes=60)) - self.assertTrue(t4.utcoffset() is None) - self.assertRaises(TypeError, t1.utcoffset, "no args") - - self.assertEqual(t1.tzname(), "EST") - self.assertEqual(t2.tzname(), "UTC") - self.assertEqual(t3.tzname(), "MET") - self.assertTrue(t4.tzname() is None) - self.assertRaises(TypeError, t1.tzname, "no args") - - self.assertEqual(t1.dst(), timedelta(minutes=1)) - self.assertEqual(t2.dst(), timedelta(minutes=-2)) - self.assertEqual(t3.dst(), timedelta(minutes=3)) - self.assertTrue(t4.dst() is None) - self.assertRaises(TypeError, t1.dst, "no args") - - self.assertEqual(hash(t1), hash(t2)) - self.assertEqual(hash(t1), hash(t3)) - self.assertEqual(hash(t2), hash(t3)) - - self.assertEqual(t1, t2) - self.assertEqual(t1, t3) - self.assertEqual(t2, t3) - self.assertNotEqual(t4, t5) # mixed tz-aware & naive - self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive - self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive - - self.assertEqual(str(t1), "07:47:00-05:00") - self.assertEqual(str(t2), "12:47:00+00:00") - self.assertEqual(str(t3), "13:47:00+01:00") - self.assertEqual(str(t4), "00:00:00.000040") - self.assertEqual(str(t5), "00:00:00.000040+00:00") - - self.assertEqual(t1.isoformat(), "07:47:00-05:00") - self.assertEqual(t2.isoformat(), "12:47:00+00:00") - self.assertEqual(t3.isoformat(), "13:47:00+01:00") - self.assertEqual(t4.isoformat(), "00:00:00.000040") - self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00") - - d = "datetime.time" - self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)") - self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)") - self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)") - self.assertEqual(repr(t4), d + "(0, 0, 0, 40)") - self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)") - - self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"), "07:47:00 %Z=EST %z=-0500") - self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000") - self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100") - - yuck = FixedOffset(-1439, "%z %Z %%z%%Z") - t1 = time(23, 59, tzinfo=yuck) - # self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"), - # "23:59 %Z='%z %Z %%z%%Z' %z='-2359'") - - # Check that an invalid tzname result raises an exception. - class Badtzname(tzinfo): - tz = 42 - - def tzname(self, dt): - return self.tz - - t = time(2, 3, 4, tzinfo=Badtzname()) - self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04") - self.assertRaises(TypeError, t.strftime, "%Z") - - # Issue #6697: - if "_Fast" in str(type(self)): - Badtzname.tz = "\ud800" - self.assertRaises(ValueError, t.strftime, "%Z") - - def test_hash_edge_cases(self): - # Offsets that overflow a basic time. - t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, "")) - t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, "")) - self.assertEqual(hash(t1), hash(t2)) - - t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, "")) - t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, "")) - self.assertEqual(hash(t1), hash(t2)) - - def test_pickling(self): - # Try one without a tzinfo. - args = 20, 59, 16, 64**2 - orig = self.theclass(*args) - for pickler, unpickler, proto in pickle_choices: - green = pickler.dumps(orig, proto) - derived = unpickler.loads(green) - self.assertEqual(orig, derived) - - def _test_pickling2(self): - # Try one with a tzinfo. - tinfo = PicklableFixedOffset(-300, "cookie") - orig = self.theclass(5, 6, 7, tzinfo=tinfo) - for pickler, unpickler, proto in pickle_choices: - green = pickler.dumps(orig, proto) - derived = unpickler.loads(green) - self.assertEqual(orig, derived) - self.assertIsInstance(derived.tzinfo, PicklableFixedOffset) - self.assertEqual(derived.utcoffset(), timedelta(minutes=-300)) - self.assertEqual(derived.tzname(), "cookie") - - def test_more_bool(self): - # Test cases with non-None tzinfo. - cls = self.theclass - - t = cls(0, tzinfo=FixedOffset(-300, "")) - self.assertTrue(t) - - t = cls(5, tzinfo=FixedOffset(-300, "")) - self.assertTrue(t) - - t = cls(5, tzinfo=FixedOffset(300, "")) - self.assertTrue(not t) - - t = cls(23, 59, tzinfo=FixedOffset(23 * 60 + 59, "")) - self.assertTrue(not t) - - # Mostly ensuring this doesn't overflow internally. - t = cls(0, tzinfo=FixedOffset(23 * 60 + 59, "")) - self.assertTrue(t) - - # But this should yield a value error -- the utcoffset is bogus. - t = cls(0, tzinfo=FixedOffset(24 * 60, "")) - self.assertRaises(ValueError, lambda: bool(t)) - - # Likewise. - t = cls(0, tzinfo=FixedOffset(-24 * 60, "")) - self.assertRaises(ValueError, lambda: bool(t)) - - def test_replace(self): - cls = self.theclass - z100 = FixedOffset(100, "+100") - zm200 = FixedOffset(timedelta(minutes=-200), "-200") - args = [1, 2, 3, 4, z100] - base = cls(*args) - self.assertEqual(base, base.replace()) - - i = 0 - for name, newval in ( - ("hour", 5), - ("minute", 6), - ("second", 7), - ("microsecond", 8), - ("tzinfo", zm200), - ): - newargs = args[:] - newargs[i] = newval - expected = cls(*newargs) - got = base.replace(**{name: newval}) - self.assertEqual(expected, got) - i += 1 - - # Ensure we can get rid of a tzinfo. - self.assertEqual(base.tzname(), "+100") - base2 = base.replace(tzinfo=None) - self.assertTrue(base2.tzinfo is None) - self.assertTrue(base2.tzname() is None) - - # Ensure we can add one. - base3 = base2.replace(tzinfo=z100) - self.assertEqual(base, base3) - self.assertTrue(base.tzinfo is base3.tzinfo) - - # Out of bounds. - base = cls(1) - self.assertRaises(ValueError, base.replace, hour=24) - self.assertRaises(ValueError, base.replace, minute=-1) - self.assertRaises(ValueError, base.replace, second=100) - self.assertRaises(ValueError, base.replace, microsecond=1000000) - - def test_mixed_compare(self): - t1 = time(1, 2, 3) - t2 = time(1, 2, 3) - self.assertEqual(t1, t2) - t2 = t2.replace(tzinfo=None) - self.assertEqual(t1, t2) - t2 = t2.replace(tzinfo=FixedOffset(None, "")) - self.assertEqual(t1, t2) - t2 = t2.replace(tzinfo=FixedOffset(0, "")) - self.assertNotEqual(t1, t2) - - # In time w/ identical tzinfo objects, utcoffset is ignored. - class Varies(tzinfo): - def __init__(self): - self.offset = timedelta(minutes=22) - - def utcoffset(self, t): - self.offset += timedelta(minutes=1) - return self.offset - - v = Varies() - t1 = t2.replace(tzinfo=v) - t2 = t2.replace(tzinfo=v) - self.assertEqual(t1.utcoffset(), timedelta(minutes=23)) - self.assertEqual(t2.utcoffset(), timedelta(minutes=24)) - self.assertEqual(t1, t2) - - # But if they're not identical, it isn't ignored. - t2 = t2.replace(tzinfo=Varies()) - self.assertTrue(t1 < t2) # t1's offset counter still going up - - def test_subclass_timetz(self): - class C(self.theclass): - theAnswer = 42 - - def __new__(cls, *args, **kws): - temp = kws.copy() - extra = temp.pop("extra") - result = self.theclass.__new__(cls, *args, **temp) - result.extra = extra - return result - - def newmeth(self, start): - return start + self.hour + self.second - - args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1) - - dt1 = self.theclass(*args) - dt2 = C(*args, **{"extra": 7}) - - self.assertEqual(dt2.__class__, C) - self.assertEqual(dt2.theAnswer, 42) - self.assertEqual(dt2.extra, 7) - self.assertEqual(dt1.utcoffset(), dt2.utcoffset()) - self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7) - - -# Testing datetime objects with a non-None tzinfo. - - -class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase): - theclass = datetime - - def test_trivial(self): - dt = self.theclass(1, 2, 3, 4, 5, 6, 7) - self.assertEqual(dt.year, 1) - self.assertEqual(dt.month, 2) - self.assertEqual(dt.day, 3) - self.assertEqual(dt.hour, 4) - self.assertEqual(dt.minute, 5) - self.assertEqual(dt.second, 6) - self.assertEqual(dt.microsecond, 7) - self.assertEqual(dt.tzinfo, None) - - def test_even_more_compare(self): - # The test_compare() and test_more_compare() inherited from TestDate - # and TestDateTime covered non-tzinfo cases. - - # Smallest possible after UTC adjustment. - t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "")) - # Largest possible after UTC adjustment. - t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999, tzinfo=FixedOffset(-1439, "")) - - # Make sure those compare correctly, and w/o overflow. - self.assertTrue(t1 < t2) - self.assertTrue(t1 != t2) - self.assertTrue(t2 > t1) - - self.assertEqual(t1, t1) - self.assertEqual(t2, t2) - - # Equal afer adjustment. - t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, "")) - t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3 * 60 + 13 + 2, "")) - self.assertEqual(t1, t2) - - # Change t1 not to subtract a minute, and t1 should be larger. - t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, "")) - self.assertTrue(t1 > t2) - - # Change t1 to subtract 2 minutes, and t1 should be smaller. - t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, "")) - self.assertTrue(t1 < t2) - - # Back to the original t1, but make seconds resolve it. - t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""), second=1) - self.assertTrue(t1 > t2) - - # Likewise, but make microseconds resolve it. - t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""), microsecond=1) - self.assertTrue(t1 > t2) - - # Make t2 naive and it should differ. - t2 = self.theclass.min - self.assertNotEqual(t1, t2) - self.assertEqual(t2, t2) - - # It's also naive if it has tzinfo but tzinfo.utcoffset() is None. - class Naive(tzinfo): - def utcoffset(self, dt): - return None - - t2 = self.theclass(5, 6, 7, tzinfo=Naive()) - self.assertNotEqual(t1, t2) - self.assertEqual(t2, t2) - - # OTOH, it's OK to compare two of these mixing the two ways of being - # naive. - t1 = self.theclass(5, 6, 7) - self.assertEqual(t1, t2) - - # Try a bogus uctoffset. - class Bogus(tzinfo): - def utcoffset(self, dt): - return timedelta(minutes=1440) # out of bounds - - t1 = self.theclass(2, 2, 2, tzinfo=Bogus()) - t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, "")) - self.assertRaises(ValueError, lambda: t1 == t2) - - def test_pickling(self): - # Try one without a tzinfo. - args = 6, 7, 23, 20, 59, 1, 64**2 - orig = self.theclass(*args) - for pickler, unpickler, proto in pickle_choices: - green = pickler.dumps(orig, proto) - derived = unpickler.loads(green) - self.assertEqual(orig, derived) - - def _test_pickling2(self): - # Try one with a tzinfo. - tinfo = PicklableFixedOffset(-300, "cookie") - orig = self.theclass(*args, **{"tzinfo": tinfo}) - derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0)) - for pickler, unpickler, proto in pickle_choices: - green = pickler.dumps(orig, proto) - derived = unpickler.loads(green) - self.assertEqual(orig, derived) - self.assertIsInstance(derived.tzinfo, PicklableFixedOffset) - self.assertEqual(derived.utcoffset(), timedelta(minutes=-300)) - self.assertEqual(derived.tzname(), "cookie") - - def test_extreme_hashes(self): - # If an attempt is made to hash these via subtracting the offset - # then hashing a datetime object, OverflowError results. The - # Python implementation used to blow up here. - t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "")) - hash(t) - t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999, tzinfo=FixedOffset(-1439, "")) - hash(t) - - # OTOH, an OOB offset should blow up. - t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, "")) - self.assertRaises(ValueError, hash, t) - - def test_zones(self): - est = FixedOffset(-300, "EST") - utc = FixedOffset(0, "UTC") - met = FixedOffset(60, "MET") - t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est) - t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc) - t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met) - self.assertEqual(t1.tzinfo, est) - self.assertEqual(t2.tzinfo, utc) - self.assertEqual(t3.tzinfo, met) - self.assertEqual(t1.utcoffset(), timedelta(minutes=-300)) - self.assertEqual(t2.utcoffset(), timedelta(minutes=0)) - self.assertEqual(t3.utcoffset(), timedelta(minutes=60)) - self.assertEqual(t1.tzname(), "EST") - self.assertEqual(t2.tzname(), "UTC") - self.assertEqual(t3.tzname(), "MET") - self.assertEqual(hash(t1), hash(t2)) - self.assertEqual(hash(t1), hash(t3)) - self.assertEqual(hash(t2), hash(t3)) - self.assertEqual(t1, t2) - self.assertEqual(t1, t3) - self.assertEqual(t2, t3) - self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00") - self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00") - self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00") - d = "datetime.datetime(2002, 3, 19, " - self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)") - self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)") - self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)") - - def test_combine(self): - met = FixedOffset(60, "MET") - d = date(2002, 3, 4) - tz = time(18, 45, 3, 1234, tzinfo=met) - dt = datetime.combine(d, tz) - self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)) - - def test_extract(self): - met = FixedOffset(60, "MET") - dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met) - self.assertEqual(dt.date(), date(2002, 3, 4)) - self.assertEqual(dt.time(), time(18, 45, 3, 1234)) - self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met)) - - def test_tz_aware_arithmetic(self): - import random - - now = self.theclass.now() - tz55 = FixedOffset(-330, "west 5:30") - timeaware = now.time().replace(tzinfo=tz55) - nowaware = self.theclass.combine(now.date(), timeaware) - self.assertTrue(nowaware.tzinfo is tz55) - self.assertEqual(nowaware.timetz(), timeaware) - - # Can't mix aware and non-aware. - self.assertRaises(TypeError, lambda: now - nowaware) - self.assertRaises(TypeError, lambda: nowaware - now) - - # And adding datetime's doesn't make sense, aware or not. - self.assertRaises(TypeError, lambda: now + nowaware) - self.assertRaises(TypeError, lambda: nowaware + now) - self.assertRaises(TypeError, lambda: nowaware + nowaware) - - # Subtracting should yield 0. - self.assertEqual(now - now, timedelta(0)) - self.assertEqual(nowaware - nowaware, timedelta(0)) - - # Adding a delta should preserve tzinfo. - delta = timedelta(weeks=1, minutes=12, microseconds=5678) - nowawareplus = nowaware + delta - self.assertTrue(nowaware.tzinfo is tz55) - nowawareplus2 = delta + nowaware - self.assertTrue(nowawareplus2.tzinfo is tz55) - self.assertEqual(nowawareplus, nowawareplus2) - - # that - delta should be what we started with, and that - what we - # started with should be delta. - diff = nowawareplus - delta - self.assertTrue(diff.tzinfo is tz55) - self.assertEqual(nowaware, diff) - self.assertRaises(TypeError, lambda: delta - nowawareplus) - self.assertEqual(nowawareplus - nowaware, delta) - - # Make up a random timezone. - tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone") - # Attach it to nowawareplus. - nowawareplus = nowawareplus.replace(tzinfo=tzr) - self.assertTrue(nowawareplus.tzinfo is tzr) - # Make sure the difference takes the timezone adjustments into account. - got = nowaware - nowawareplus - # Expected: (nowaware base - nowaware offset) - - # (nowawareplus base - nowawareplus offset) = - # (nowaware base - nowawareplus base) + - # (nowawareplus offset - nowaware offset) = - # -delta + nowawareplus offset - nowaware offset - expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta - self.assertEqual(got, expected) - - # Try max possible difference. - min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min")) - max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999, tzinfo=FixedOffset(-1439, "max")) - maxdiff = max - min - self.assertEqual( - maxdiff, self.theclass.max - self.theclass.min + timedelta(minutes=2 * 1439) - ) - # Different tzinfo, but the same offset - tza = timezone(HOUR, "A") - tzb = timezone(HOUR, "B") - delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb) - self.assertEqual(delta, self.theclass.min - self.theclass.max) - - def test_tzinfo_now(self): - meth = self.theclass.now - # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up). - base = meth() - # Try with and without naming the keyword. - off42 = FixedOffset(42, "42") - another = meth(off42) - again = meth(tz=off42) - self.assertTrue(another.tzinfo is again.tzinfo) - self.assertEqual(another.utcoffset(), timedelta(minutes=42)) - # Bad argument with and w/o naming the keyword. - self.assertRaises(TypeError, meth, 16) - self.assertRaises(TypeError, meth, tzinfo=16) - # Bad keyword name. - self.assertRaises(TypeError, meth, tinfo=off42) - # Too many args. - self.assertRaises(TypeError, meth, off42, off42) - - # We don't know which time zone we're in, and don't have a tzinfo - # class to represent it, so seeing whether a tz argument actually - # does a conversion is tricky. - utc = FixedOffset(0, "utc", 0) - for weirdtz in [ - FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0), - timezone(timedelta(hours=15, minutes=58), "weirdtz"), - ]: - for dummy in range(3): - now = datetime.now(weirdtz) - self.assertTrue(now.tzinfo is weirdtz) - utcnow = datetime.utcnow().replace(tzinfo=utc) - now2 = utcnow.astimezone(weirdtz) - if abs(now - now2) < timedelta(seconds=30): - break - # Else the code is broken, or more than 30 seconds passed between - # calls; assuming the latter, just try again. - else: - # Three strikes and we're out. - self.fail("utcnow(), now(tz), or astimezone() may be broken") - - def test_tzinfo_fromtimestamp(self): - import time - - meth = self.theclass.fromtimestamp - ts = time.time() - # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up). - base = meth(ts) - # Try with and without naming the keyword. - off42 = FixedOffset(42, "42") - another = meth(ts, off42) - again = meth(ts, tz=off42) - self.assertTrue(another.tzinfo is again.tzinfo) - self.assertEqual(another.utcoffset(), timedelta(minutes=42)) - # Bad argument with and w/o naming the keyword. - self.assertRaises(TypeError, meth, ts, 16) - self.assertRaises(TypeError, meth, ts, tzinfo=16) - # Bad keyword name. - self.assertRaises(TypeError, meth, ts, tinfo=off42) - # Too many args. - self.assertRaises(TypeError, meth, ts, off42, off42) - # Too few args. - self.assertRaises(TypeError, meth) - - # Try to make sure tz= actually does some conversion. - timestamp = 1000000000 - utcdatetime = datetime.utcfromtimestamp(timestamp) - # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take. - # But on some flavor of Mac, it's nowhere near that. So we can't have - # any idea here what time that actually is, we can only test that - # relative changes match. - utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero - tz = FixedOffset(utcoffset, "tz", 0) - expected = utcdatetime + utcoffset - got = datetime.fromtimestamp(timestamp, tz) - self.assertEqual(expected, got.replace(tzinfo=None)) - - def test_tzinfo_utcnow(self): - meth = self.theclass.utcnow - # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up). - base = meth() - # Try with and without naming the keyword; for whatever reason, - # utcnow() doesn't accept a tzinfo argument. - off42 = FixedOffset(42, "42") - self.assertRaises(TypeError, meth, off42) - self.assertRaises(TypeError, meth, tzinfo=off42) - - def test_tzinfo_utcfromtimestamp(self): - import time - - meth = self.theclass.utcfromtimestamp - ts = time.time() - # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up). - base = meth(ts) - # Try with and without naming the keyword; for whatever reason, - # utcfromtimestamp() doesn't accept a tzinfo argument. - off42 = FixedOffset(42, "42") - self.assertRaises(TypeError, meth, ts, off42) - self.assertRaises(TypeError, meth, ts, tzinfo=off42) - - def test_tzinfo_timetuple(self): - # TestDateTime tested most of this. datetime adds a twist to the - # DST flag. - class DST(tzinfo): - def __init__(self, dstvalue): - if isinstance(dstvalue, int): - dstvalue = timedelta(minutes=dstvalue) - self.dstvalue = dstvalue - - def dst(self, dt): - return self.dstvalue - - cls = self.theclass - for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1): - d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue)) - t = d.timetuple() - self.assertEqual(1, t.tm_year) - self.assertEqual(1, t.tm_mon) - self.assertEqual(1, t.tm_mday) - self.assertEqual(10, t.tm_hour) - self.assertEqual(20, t.tm_min) - self.assertEqual(30, t.tm_sec) - self.assertEqual(0, t.tm_wday) - self.assertEqual(1, t.tm_yday) - self.assertEqual(flag, t.tm_isdst) - - # dst() returns wrong type. - self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple) - - # dst() at the edge. - self.assertEqual(cls(1, 1, 1, tzinfo=DST(1439)).timetuple().tm_isdst, 1) - self.assertEqual(cls(1, 1, 1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1) - - # dst() out of range. - self.assertRaises(ValueError, cls(1, 1, 1, tzinfo=DST(1440)).timetuple) - self.assertRaises(ValueError, cls(1, 1, 1, tzinfo=DST(-1440)).timetuple) - - def test_utctimetuple(self): - class DST(tzinfo): - def __init__(self, dstvalue=0): - if isinstance(dstvalue, int): - dstvalue = timedelta(minutes=dstvalue) - self.dstvalue = dstvalue - - def dst(self, dt): - return self.dstvalue - - cls = self.theclass - # This can't work: DST didn't implement utcoffset. - self.assertRaises(NotImplementedError, cls(1, 1, 1, tzinfo=DST(0)).utcoffset) - - class UOFS(DST): - def __init__(self, uofs, dofs=None): - DST.__init__(self, dofs) - self.uofs = timedelta(minutes=uofs) - - def utcoffset(self, dt): - return self.uofs - - for dstvalue in -33, 33, 0, None: - d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue)) - t = d.utctimetuple() - self.assertEqual(d.year, t.tm_year) - self.assertEqual(d.month, t.tm_mon) - self.assertEqual(d.day, t.tm_mday) - self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm - self.assertEqual(13, t.tm_min) - self.assertEqual(d.second, t.tm_sec) - self.assertEqual(d.weekday(), t.tm_wday) - self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1, t.tm_yday) - # Ensure tm_isdst is 0 regardless of what dst() says: DST - # is never in effect for a UTC time. - self.assertEqual(0, t.tm_isdst) - - # For naive datetime, utctimetuple == timetuple except for isdst - d = cls(1, 2, 3, 10, 20, 30, 40) - t = d.utctimetuple() - self.assertEqual(t[:-1], d.timetuple()[:-1]) - self.assertEqual(0, t.tm_isdst) - # Same if utcoffset is None - class NOFS(DST): - def utcoffset(self, dt): - return None - - d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS()) - t = d.utctimetuple() - self.assertEqual(t[:-1], d.timetuple()[:-1]) - self.assertEqual(0, t.tm_isdst) - # Check that bad tzinfo is detected - class BOFS(DST): - def utcoffset(self, dt): - return "EST" - - d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS()) - self.assertRaises(TypeError, d.utctimetuple) - - # Check that utctimetuple() is the same as - # astimezone(utc).timetuple() - d = cls(2010, 11, 13, 14, 15, 16, 171819) - for tz in [timezone.min, timezone.utc, timezone.max]: - dtz = d.replace(tzinfo=tz) - self.assertEqual( - dtz.utctimetuple()[:-1], dtz.astimezone(timezone.utc).timetuple()[:-1] - ) - # At the edges, UTC adjustment can produce years out-of-range - # for a datetime object. Ensure that an OverflowError is - # raised. - tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439)) - # That goes back 1 minute less than a full day. - self.assertRaises(OverflowError, tiny.utctimetuple) - - huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439)) - # That goes forward 1 minute less than a full day. - self.assertRaises(OverflowError, huge.utctimetuple) - # More overflow cases - tiny = cls.min.replace(tzinfo=timezone(MINUTE)) - self.assertRaises(OverflowError, tiny.utctimetuple) - huge = cls.max.replace(tzinfo=timezone(-MINUTE)) - self.assertRaises(OverflowError, huge.utctimetuple) - - def test_tzinfo_isoformat(self): - zero = FixedOffset(0, "+00:00") - plus = FixedOffset(220, "+03:40") - minus = FixedOffset(-231, "-03:51") - unknown = FixedOffset(None, "") - - cls = self.theclass - datestr = "0001-02-03" - for ofs in None, zero, plus, minus, unknown: - for us in 0, 987001: - d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs) - timestr = "04:05:59" + (us and ".987001" or "") - ofsstr = ofs is not None and d.tzname() or "" - tailstr = timestr + ofsstr - iso = d.isoformat() - self.assertEqual(iso, datestr + "T" + tailstr) - self.assertEqual(iso, d.isoformat("T")) - self.assertEqual(d.isoformat("k"), datestr + "k" + tailstr) - self.assertEqual(d.isoformat("\u1234"), datestr + "\u1234" + tailstr) - self.assertEqual(str(d), datestr + " " + tailstr) - - def test_replace(self): - cls = self.theclass - z100 = FixedOffset(100, "+100") - zm200 = FixedOffset(timedelta(minutes=-200), "-200") - args = [1, 2, 3, 4, 5, 6, 7, z100] - base = cls(*args) - self.assertEqual(base, base.replace()) - - i = 0 - for name, newval in ( - ("year", 2), - ("month", 3), - ("day", 4), - ("hour", 5), - ("minute", 6), - ("second", 7), - ("microsecond", 8), - ("tzinfo", zm200), - ): - newargs = args[:] - newargs[i] = newval - expected = cls(*newargs) - got = base.replace(**{name: newval}) - self.assertEqual(expected, got) - i += 1 - - # Ensure we can get rid of a tzinfo. - self.assertEqual(base.tzname(), "+100") - base2 = base.replace(tzinfo=None) - self.assertTrue(base2.tzinfo is None) - self.assertTrue(base2.tzname() is None) - - # Ensure we can add one. - base3 = base2.replace(tzinfo=z100) - self.assertEqual(base, base3) - self.assertTrue(base.tzinfo is base3.tzinfo) - - # Out of bounds. - base = cls(2000, 2, 29) - self.assertRaises(ValueError, base.replace, year=2001) - - def test_more_astimezone(self): - # The inherited test_astimezone covered some trivial and error cases. - fnone = FixedOffset(None, "None") - f44m = FixedOffset(44, "44") - fm5h = FixedOffset(-timedelta(hours=5), "m300") - - dt = self.theclass.now(tz=f44m) - self.assertTrue(dt.tzinfo is f44m) - # Replacing with degenerate tzinfo raises an exception. - self.assertRaises(ValueError, dt.astimezone, fnone) - # Replacing with same tzinfo makes no change. - x = dt.astimezone(dt.tzinfo) - self.assertTrue(x.tzinfo is f44m) - self.assertEqual(x.date(), dt.date()) - self.assertEqual(x.time(), dt.time()) - - # Replacing with different tzinfo does adjust. - got = dt.astimezone(fm5h) - self.assertTrue(got.tzinfo is fm5h) - self.assertEqual(got.utcoffset(), timedelta(hours=-5)) - expected = dt - dt.utcoffset() # in effect, convert to UTC - expected += fm5h.utcoffset(dt) # and from there to local time - expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo - self.assertEqual(got.date(), expected.date()) - self.assertEqual(got.time(), expected.time()) - self.assertEqual(got.timetz(), expected.timetz()) - self.assertTrue(got.tzinfo is expected.tzinfo) - self.assertEqual(got, expected) - - # @support.run_with_tz('UTC') - def test_astimezone_default_utc(self): - dt = self.theclass.now(timezone.utc) - self.assertEqual(dt.astimezone(None), dt) - self.assertEqual(dt.astimezone(), dt) - - # Note that offset in TZ variable has the opposite sign to that - # produced by %z directive. - # @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0') - @unittest.skip("no support.run_with_tz") - def test_astimezone_default_eastern(self): - dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc) - local = dt.astimezone() - self.assertEqual(dt, local) - self.assertEqual(local.strftime("%z %Z"), "-0500 EST") - dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc) - local = dt.astimezone() - self.assertEqual(dt, local) - self.assertEqual(local.strftime("%z %Z"), "-0400 EDT") - - def test_aware_subtract(self): - cls = self.theclass - - # Ensure that utcoffset() is ignored when the operands have the - # same tzinfo member. - class OperandDependentOffset(tzinfo): - def utcoffset(self, t): - if t.minute < 10: - # d0 and d1 equal after adjustment - return timedelta(minutes=t.minute) - else: - # d2 off in the weeds - return timedelta(minutes=59) - - base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset()) - d0 = base.replace(minute=3) - d1 = base.replace(minute=9) - d2 = base.replace(minute=11) - for x in d0, d1, d2: - for y in d0, d1, d2: - got = x - y - expected = timedelta(minutes=x.minute - y.minute) - self.assertEqual(got, expected) - - # OTOH, if the tzinfo members are distinct, utcoffsets aren't - # ignored. - base = cls(8, 9, 10, 11, 12, 13, 14) - d0 = base.replace(minute=3, tzinfo=OperandDependentOffset()) - d1 = base.replace(minute=9, tzinfo=OperandDependentOffset()) - d2 = base.replace(minute=11, tzinfo=OperandDependentOffset()) - for x in d0, d1, d2: - for y in d0, d1, d2: - got = x - y - if (x is d0 or x is d1) and (y is d0 or y is d1): - expected = timedelta(0) - elif x is y is d2: - expected = timedelta(0) - elif x is d2: - expected = timedelta(minutes=(11 - 59) - 0) - else: - assert y is d2 - expected = timedelta(minutes=0 - (11 - 59)) - self.assertEqual(got, expected) - - def test_mixed_compare(self): - t1 = datetime(1, 2, 3, 4, 5, 6, 7) - t2 = datetime(1, 2, 3, 4, 5, 6, 7) - self.assertEqual(t1, t2) - t2 = t2.replace(tzinfo=None) - self.assertEqual(t1, t2) - t2 = t2.replace(tzinfo=FixedOffset(None, "")) - self.assertEqual(t1, t2) - t2 = t2.replace(tzinfo=FixedOffset(0, "")) - self.assertNotEqual(t1, t2) - - # In datetime w/ identical tzinfo objects, utcoffset is ignored. - class Varies(tzinfo): - def __init__(self): - self.offset = timedelta(minutes=22) - - def utcoffset(self, t): - self.offset += timedelta(minutes=1) - return self.offset - - v = Varies() - t1 = t2.replace(tzinfo=v) - t2 = t2.replace(tzinfo=v) - self.assertEqual(t1.utcoffset(), timedelta(minutes=23)) - self.assertEqual(t2.utcoffset(), timedelta(minutes=24)) - self.assertEqual(t1, t2) - - # But if they're not identical, it isn't ignored. - t2 = t2.replace(tzinfo=Varies()) - self.assertTrue(t1 < t2) # t1's offset counter still going up - - def test_subclass_datetimetz(self): - class C(self.theclass): - theAnswer = 42 - - def __new__(cls, *args, **kws): - temp = kws.copy() - extra = temp.pop("extra") - result = self.theclass.__new__(cls, *args, **temp) - result.extra = extra - return result - - def newmeth(self, start): - return start + self.hour + self.year - - args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1) - - dt1 = self.theclass(*args) - dt2 = C(*args, **{"extra": 7}) - - self.assertEqual(dt2.__class__, C) - self.assertEqual(dt2.theAnswer, 42) - self.assertEqual(dt2.extra, 7) - self.assertEqual(dt1.utcoffset(), dt2.utcoffset()) - self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7) - - -# Pain to set up DST-aware tzinfo classes. - - -def first_sunday_on_or_after(dt): - days_to_go = 6 - dt.weekday() - if days_to_go: - dt += timedelta(days_to_go) - return dt - - -ZERO = timedelta(0) -MINUTE = timedelta(minutes=1) -HOUR = timedelta(hours=1) -DAY = timedelta(days=1) -# In the US, DST starts at 2am (standard time) on the first Sunday in April. -DSTSTART = datetime(1, 4, 1, 2) -# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct, -# which is the first Sunday on or after Oct 25. Because we view 1:MM as -# being standard time on that day, there is no spelling in local time of -# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time). -DSTEND = datetime(1, 10, 25, 1) - - -class USTimeZone(tzinfo): - def __init__(self, hours, reprname, stdname, dstname): - self.stdoffset = timedelta(hours=hours) - self.reprname = reprname - self.stdname = stdname - self.dstname = dstname - - def __repr__(self): - return self.reprname - - def tzname(self, dt): - if self.dst(dt): - return self.dstname - else: - return self.stdname - - def utcoffset(self, dt): - return self.stdoffset + self.dst(dt) - - def dst(self, dt): - if dt is None or dt.tzinfo is None: - # An exception instead may be sensible here, in one or more of - # the cases. - return ZERO - assert dt.tzinfo is self - - # Find first Sunday in April. - start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year)) - assert start.weekday() == 6 and start.month == 4 and start.day <= 7 - - # Find last Sunday in October. - end = first_sunday_on_or_after(DSTEND.replace(year=dt.year)) - assert end.weekday() == 6 and end.month == 10 and end.day >= 25 - - # Can't compare naive to aware objects, so strip the timezone from - # dt first. - if start <= dt.replace(tzinfo=None) < end: - return HOUR - else: - return ZERO - - -Eastern = USTimeZone(-5, "Eastern", "EST", "EDT") -Central = USTimeZone(-6, "Central", "CST", "CDT") -Mountain = USTimeZone(-7, "Mountain", "MST", "MDT") -Pacific = USTimeZone(-8, "Pacific", "PST", "PDT") -utc_real = FixedOffset(0, "UTC", 0) -# For better test coverage, we want another flavor of UTC that's west of -# the Eastern and Pacific timezones. -utc_fake = FixedOffset(-12 * 60, "UTCfake", 0) - - -class TestTimezoneConversions(unittest.TestCase): - # The DST switch times for 2002, in std time. - dston = datetime(2002, 4, 7, 2) - dstoff = datetime(2002, 10, 27, 1) - - theclass = datetime - - # Check a time that's inside DST. - def checkinside(self, dt, tz, utc, dston, dstoff): - self.assertEqual(dt.dst(), HOUR) - - # Conversion to our own timezone is always an identity. - self.assertEqual(dt.astimezone(tz), dt) - - asutc = dt.astimezone(utc) - there_and_back = asutc.astimezone(tz) - - # Conversion to UTC and back isn't always an identity here, - # because there are redundant spellings (in local time) of - # UTC time when DST begins: the clock jumps from 1:59:59 - # to 3:00:00, and a local time of 2:MM:SS doesn't really - # make sense then. The classes above treat 2:MM:SS as - # daylight time then (it's "after 2am"), really an alias - # for 1:MM:SS standard time. The latter form is what - # conversion back from UTC produces. - if dt.date() == dston.date() and dt.hour == 2: - # We're in the redundant hour, and coming back from - # UTC gives the 1:MM:SS standard-time spelling. - self.assertEqual(there_and_back + HOUR, dt) - # Although during was considered to be in daylight - # time, there_and_back is not. - self.assertEqual(there_and_back.dst(), ZERO) - # They're the same times in UTC. - self.assertEqual(there_and_back.astimezone(utc), dt.astimezone(utc)) - else: - # We're not in the redundant hour. - self.assertEqual(dt, there_and_back) - - # Because we have a redundant spelling when DST begins, there is - # (unfortunately) an hour when DST ends that can't be spelled at all in - # local time. When DST ends, the clock jumps from 1:59 back to 1:00 - # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be - # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be - # daylight time. The hour 1:MM daylight == 0:MM standard can't be - # expressed in local time. Nevertheless, we want conversion back - # from UTC to mimic the local clock's "repeat an hour" behavior. - nexthour_utc = asutc + HOUR - nexthour_tz = nexthour_utc.astimezone(tz) - if dt.date() == dstoff.date() and dt.hour == 0: - # We're in the hour before the last DST hour. The last DST hour - # is ineffable. We want the conversion back to repeat 1:MM. - self.assertEqual(nexthour_tz, dt.replace(hour=1)) - nexthour_utc += HOUR - nexthour_tz = nexthour_utc.astimezone(tz) - self.assertEqual(nexthour_tz, dt.replace(hour=1)) - else: - self.assertEqual(nexthour_tz - dt, HOUR) - - # Check a time that's outside DST. - def checkoutside(self, dt, tz, utc): - self.assertEqual(dt.dst(), ZERO) - - # Conversion to our own timezone is always an identity. - self.assertEqual(dt.astimezone(tz), dt) - - # Converting to UTC and back is an identity too. - asutc = dt.astimezone(utc) - there_and_back = asutc.astimezone(tz) - self.assertEqual(dt, there_and_back) - - def convert_between_tz_and_utc(self, tz, utc): - dston = self.dston.replace(tzinfo=tz) - # Because 1:MM on the day DST ends is taken as being standard time, - # there is no spelling in tz for the last hour of daylight time. - # For purposes of the test, the last hour of DST is 0:MM, which is - # taken as being daylight time (and 1:MM is taken as being standard - # time). - dstoff = self.dstoff.replace(tzinfo=tz) - for delta in ( - timedelta(weeks=13), - DAY, - HOUR, - timedelta(minutes=1), - timedelta(microseconds=1), - ): - - self.checkinside(dston, tz, utc, dston, dstoff) - for during in dston + delta, dstoff - delta: - self.checkinside(during, tz, utc, dston, dstoff) - - self.checkoutside(dstoff, tz, utc) - for outside in dston - delta, dstoff + delta: - self.checkoutside(outside, tz, utc) - - def test_easy(self): - # Despite the name of this test, the endcases are excruciating. - self.convert_between_tz_and_utc(Eastern, utc_real) - self.convert_between_tz_and_utc(Pacific, utc_real) - self.convert_between_tz_and_utc(Eastern, utc_fake) - self.convert_between_tz_and_utc(Pacific, utc_fake) - # The next is really dancing near the edge. It works because - # Pacific and Eastern are far enough apart that their "problem - # hours" don't overlap. - self.convert_between_tz_and_utc(Eastern, Pacific) - self.convert_between_tz_and_utc(Pacific, Eastern) - # OTOH, these fail! Don't enable them. The difficulty is that - # the edge case tests assume that every hour is representable in - # the "utc" class. This is always true for a fixed-offset tzinfo - # class (lke utc_real and utc_fake), but not for Eastern or Central. - # For these adjacent DST-aware time zones, the range of time offsets - # tested ends up creating hours in the one that aren't representable - # in the other. For the same reason, we would see failures in the - # Eastern vs Pacific tests too if we added 3*HOUR to the list of - # offset deltas in convert_between_tz_and_utc(). - # - # self.convert_between_tz_and_utc(Eastern, Central) # can't work - # self.convert_between_tz_and_utc(Central, Eastern) # can't work - - def test_tricky(self): - # 22:00 on day before daylight starts. - fourback = self.dston - timedelta(hours=4) - ninewest = FixedOffset(-9 * 60, "-0900", 0) - fourback = fourback.replace(tzinfo=ninewest) - # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after - # 2", we should get the 3 spelling. - # If we plug 22:00 the day before into Eastern, it "looks like std - # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4 - # to 22:00 lands on 2:00, which makes no sense in local time (the - # local clock jumps from 1 to 3). The point here is to make sure we - # get the 3 spelling. - expected = self.dston.replace(hour=3) - got = fourback.astimezone(Eastern).replace(tzinfo=None) - self.assertEqual(expected, got) - - # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that - # case we want the 1:00 spelling. - sixutc = self.dston.replace(hour=6, tzinfo=utc_real) - # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4, - # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST - # spelling. - expected = self.dston.replace(hour=1) - got = sixutc.astimezone(Eastern).replace(tzinfo=None) - self.assertEqual(expected, got) - - # Now on the day DST ends, we want "repeat an hour" behavior. - # UTC 4:MM 5:MM 6:MM 7:MM checking these - # EST 23:MM 0:MM 1:MM 2:MM - # EDT 0:MM 1:MM 2:MM 3:MM - # wall 0:MM 1:MM 1:MM 2:MM against these - for utc in utc_real, utc_fake: - for tz in Eastern, Pacific: - first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM - # Convert that to UTC. - first_std_hour -= tz.utcoffset(None) - # Adjust for possibly fake UTC. - asutc = first_std_hour + utc.utcoffset(None) - # First UTC hour to convert; this is 4:00 when utc=utc_real & - # tz=Eastern. - asutcbase = asutc.replace(tzinfo=utc) - for tzhour in (0, 1, 1, 2): - expectedbase = self.dstoff.replace(hour=tzhour) - for minute in 0, 30, 59: - expected = expectedbase.replace(minute=minute) - asutc = asutcbase.replace(minute=minute) - astz = asutc.astimezone(tz) - self.assertEqual(astz.replace(tzinfo=None), expected) - asutcbase += HOUR - - def test_bogus_dst(self): - class ok(tzinfo): - def utcoffset(self, dt): - return HOUR - - def dst(self, dt): - return HOUR - - now = self.theclass.now().replace(tzinfo=utc_real) - # Doesn't blow up. - now.astimezone(ok()) - - # Does blow up. - class notok(ok): - def dst(self, dt): - return None - - self.assertRaises(ValueError, now.astimezone, notok()) - - # Sometimes blow up. In the following, tzinfo.dst() - # implementation may return None or not None depending on - # whether DST is assumed to be in effect. In this situation, - # a ValueError should be raised by astimezone(). - class tricky_notok(ok): - def dst(self, dt): - if dt.year == 2000: - return None - else: - return 10 * HOUR - - dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real) - self.assertRaises(ValueError, dt.astimezone, tricky_notok()) - - def test_fromutc(self): - self.assertRaises(TypeError, Eastern.fromutc) # not enough args - now = datetime.utcnow().replace(tzinfo=utc_real) - self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo - now = now.replace(tzinfo=Eastern) # insert correct tzinfo - enow = Eastern.fromutc(now) # doesn't blow up - self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member - self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args - self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type - - # Always converts UTC to standard time. - class FauxUSTimeZone(USTimeZone): - def fromutc(self, dt): - return dt + self.stdoffset - - FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT") - - # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM - # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM - # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM - - # Check around DST start. - start = self.dston.replace(hour=4, tzinfo=Eastern) - fstart = start.replace(tzinfo=FEastern) - for wall in 23, 0, 1, 3, 4, 5: - expected = start.replace(hour=wall) - if wall == 23: - expected -= timedelta(days=1) - got = Eastern.fromutc(start) - self.assertEqual(expected, got) - - expected = fstart + FEastern.stdoffset - got = FEastern.fromutc(fstart) - self.assertEqual(expected, got) - - # Ensure astimezone() calls fromutc() too. - got = fstart.replace(tzinfo=utc_real).astimezone(FEastern) - self.assertEqual(expected, got) - - start += HOUR - fstart += HOUR - - # Check around DST end. - start = self.dstoff.replace(hour=4, tzinfo=Eastern) - fstart = start.replace(tzinfo=FEastern) - for wall in 0, 1, 1, 2, 3, 4: - expected = start.replace(hour=wall) - got = Eastern.fromutc(start) - self.assertEqual(expected, got) - - expected = fstart + FEastern.stdoffset - got = FEastern.fromutc(fstart) - self.assertEqual(expected, got) - - # Ensure astimezone() calls fromutc() too. - got = fstart.replace(tzinfo=utc_real).astimezone(FEastern) - self.assertEqual(expected, got) - - start += HOUR - fstart += HOUR - - -############################################################################# -# oddballs - - -class Oddballs(unittest.TestCase): - @unittest.skip( - "MicroPython doesn't implement special subclass handling from https://docs.python.org/3/reference/datamodel.html#object.__ror" - ) - def test_bug_1028306(self): - # Trying to compare a date to a datetime should act like a mixed- - # type comparison, despite that datetime is a subclass of date. - as_date = date.today() - as_datetime = datetime.combine(as_date, time()) - self.assertTrue(as_date != as_datetime) - self.assertTrue(as_datetime != as_date) - self.assertTrue(not as_date == as_datetime) - self.assertTrue(not as_datetime == as_date) - self.assertRaises(TypeError, lambda: as_date < as_datetime) - self.assertRaises(TypeError, lambda: as_datetime < as_date) - self.assertRaises(TypeError, lambda: as_date <= as_datetime) - self.assertRaises(TypeError, lambda: as_datetime <= as_date) - self.assertRaises(TypeError, lambda: as_date > as_datetime) - self.assertRaises(TypeError, lambda: as_datetime > as_date) - self.assertRaises(TypeError, lambda: as_date >= as_datetime) - self.assertRaises(TypeError, lambda: as_datetime >= as_date) - - # Neverthelss, comparison should work with the base-class (date) - # projection if use of a date method is forced. - self.assertEqual(as_date.__eq__(as_datetime), True) - different_day = (as_date.day + 1) % 20 + 1 - as_different = as_datetime.replace(day=different_day) - self.assertEqual(as_date.__eq__(as_different), False) - - # And date should compare with other subclasses of date. If a - # subclass wants to stop this, it's up to the subclass to do so. - date_sc = SubclassDate(as_date.year, as_date.month, as_date.day) - self.assertEqual(as_date, date_sc) - self.assertEqual(date_sc, as_date) - - # Ditto for datetimes. - datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month, as_date.day, 0, 0, 0) - self.assertEqual(as_datetime, datetime_sc) - self.assertEqual(datetime_sc, as_datetime) - - -def test_main(): - support.run_unittest(__name__) - - -if __name__ == "__main__": - test_main()