From b8caa306b151256391d02b7f9cc0602e3253f8d7 Mon Sep 17 00:00:00 2001 From: Lorenzo Cappelletti Date: Sun, 26 Sep 2021 21:57:49 +0200 Subject: [PATCH 01/14] python-stdlib/datetime: add new module Signed-off-by: Lorenzo Cappelletti --- python-stdlib/datetime/datetime.py | 445 +++ python-stdlib/datetime/metadata.txt | 4 + python-stdlib/datetime/setup.py | 24 + python-stdlib/datetime/test_datetime.py | 704 ++++ 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 ----------------------- 8 files changed, 1177 insertions(+), 6184 deletions(-) create mode 100644 python-stdlib/datetime/datetime.py 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 000000000..96751efe6 --- /dev/null +++ b/python-stdlib/datetime/datetime.py @@ -0,0 +1,445 @@ +# datetime.py + +__version__ = "2.0.0" + +# The following functions were (stolen and) adapted from Python's datetime. +def _is_leap(year): + 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. + if month == 2 and _is_leap(year): + return 29 + return (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)[month] + + +def _days_before_month(year, month): + # year, month -> number of days in year preceding first day of month. + return (0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334)[month] + ( + month > 2 and _is_leap(year) + ) + + +def _ymd2ord(year, month, day): + # year, month, day -> ordinal, considering 01-Jan-0001 as day 1. + return _days_before_year(year) + _days_before_month(year, month) + day + + +def _ord2ymd(n): + # ordinal -> (year, month, day), considering 01-Jan-0001 as day 1. + n -= 1 + n400, n = divmod(n, 146097) + year = n400 * 400 + 1 + n100, n = divmod(n, 36524) + n4, n = divmod(n, 1461) + n1, n = divmod(n, 365) + year += n100 * 100 + n4 * 4 + n1 + if n1 == 4 or n100 == 4: + return year - 1, 12, 31 + month = (n + 50) >> 5 + preceding = _days_before_month(year, month) + if preceding > n: + month -= 1 + preceding -= _days_in_month(year, month) + n -= preceding + return year, month, n + 1 + + +class timedelta: + MINYEAR = -68 # timedelta( 0, 0, 0, -365*68).total_seconds() >= -2**31 + MAXYEAR = 68 # timedelta(23, 59, 59, 365*68).total_seconds() <= 2**31 - 1 + + def __init__(self, hours=0, minutes=0, seconds=0, days=0, weeks=0): + self._s = round((((weeks * 7 + days) * 24 + hours) * 60 + minutes) * 60 + seconds) + + def total_seconds(self): + return self._s + + def __add__(self, other): + if isinstance(other, datetime): + return other + self + return timedelta(seconds=self._s + other._s) + + def __sub__(self, other): + return timedelta(seconds=self._s - other._s) + + def __mul__(self, other): + return timedelta(seconds=round(other * self._s)) + + __rmul__ = __mul__ + + def __truediv__(self, other): + if isinstance(other, timedelta): + return self._s / other._s + else: + return timedelta(seconds=round(self._s / other)) + + def __floordiv__(self, other): + if isinstance(other, timedelta): + return self._s // other._s + else: + return timedelta(seconds=int(self._s // other)) + + def __mod__(self, other): + return timedelta(seconds=self._s % other._s) + + def __divmod__(self, other): + q, r = divmod(self._s, other._s) + return q, timedelta(seconds=r) + + def __neg__(self): + return timedelta(seconds=-self._s) + + def __pos__(self): + return self + + def __eq__(self, other): + return self._s == other._s + + def __le__(self, other): + return self._s <= other._s + + def __lt__(self, other): + return self._s < other._s + + def __ge__(self, other): + return self._s >= other._s + + def __gt__(self, other): + return self._s > other._s + + def __bool__(self): + return self._s != 0 + + def __neg__(self): + return timedelta(seconds=-self._s) + + def __pos__(self): + return self + + def __abs__(self): + return timedelta(seconds=abs(self._s)) + + def __repr__(self): + return "datetime.timedelta(seconds=%d)" % self._s + + def __str__(self): + return self.isoformat() + + def isoformat(self): + t = self.tuple() + if 0 <= self._s < 86400: + return "%02d:%02d:%02d" % t[2:] + else: + return "%s%dd %02d:%02d:%02d" % t + + def tuple(self, sign_pos=""): + s = self._s + if s < 0: + s *= -1 + g = "-" + else: + g = sign_pos + m, s = divmod(s, 60) + h, m = divmod(m, 60) + d, h = divmod(h, 24) + return g, d, h, m, s + + +timedelta.min = timedelta(seconds=-2147483648) # -2**31 +timedelta.max = timedelta(seconds=2147483647) # 2**31 - 1 +timedelta.resolution = timedelta(seconds=1) + + +class timezone: + def __init__(self, offset, name=None): + if not (timedelta(hours=-24) < offset < timedelta(hours=24)): + raise ValueError + self._offset = offset + self._name = name + + def __str__(self): + return self.tzname(None) + + def utcoffset(self, dt): + dst = self.dst(dt) + if dst is None or not dst: + return self._offset + return self._offset + dst + + def dst(self, dt): + return None + + def tzname(self, dt): + if self._name != None: + return self._name + return self.isoformat(dt) + + def isoformat(self, dt, *, utc=True): + td = self.utcoffset(dt) + if utc and not td: + return "UTC" + sign, day, hour, minute, second = td.tuple("+") + return "%s%s%02d:%02d" % ("UTC" if utc else "", sign, hour, minute) + + +timezone.utc = timezone(timedelta(0)) + + +class datetime: + MINYEAR = 1 + MAXYEAR = 9999 + + def __init__(self, year, month, day, hour=0, minute=0, second=0, tzinfo=None): + if year == 0 and month == 0 and day > 0: + self._ord = day + elif ( + self.MINYEAR <= year <= self.MAXYEAR + and 1 <= month <= 12 + and 1 <= day <= _days_in_month(year, month) + and 0 <= hour < 24 + and 0 <= minute < 60 + and 0 <= second < 60 + ): + self._ord = _ymd2ord(year, month, day) + else: + raise ValueError + self._time = timedelta(hour, minute, second) + self._tz = tzinfo + + def __add__(self, other): + time = self._time + other + sign, days, hour, minute, second = time.tuple() + if sign == "-": + days += 1 + time += timedelta(days=days) + days = -days + year, month, day, hour, minute, second, tz = self._tuple(self._ord + days, time, self._tz)[:7] + return datetime(year, month, day, hour, minute, second, tz) + + def __sub__(self, other): + if isinstance(other, timedelta): + return self + -other + elif isinstance(other, datetime): + days, time = self._sub(other) + return time + timedelta(days=days) + else: + raise TypeError + + def __lt__(self, other): + return self._cmp(other) < 0 + + def __le__(self, other): + return self._cmp(other) <= 0 + + def __eq__(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 __repr__(self): + return "datetime.datetime(days=%d, seconds=%d, tzinfo=%s)" \ + % (self._ord, self._time._s, repr(self._tz)) + + def __str__(self): + return self.isoformat(" ") + + 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 date(self): + return datetime(0, 0, self.toordinal(), tzinfo=self._tz) + + def time(self): + return timedelta(seconds=self._time.total_seconds()) + + def tzname(self): + return None if self._tz is None else self._tz.tzname(self) + + def replace( + self, year=None, month=None, day=None, hour=None, minute=None, second=None, tzinfo=True + ): + year_, month_, day_, hour_, minute_, second_, tz_ = self.tuple() + if year is None: + year = year_ + if month is None: + month = month_ + if day is None: + day = day_ + if hour is None: + hour = hour_ + if minute is None: + minute = minute_ + if second is None: + second = second_ + if tzinfo is True: + tzinfo = tz_ + return datetime(year, month, day, hour, minute, second, tzinfo) + + def astimezone(self, tz): + if self._tz == None: + raise NotImplementedError + ret = self - self.utcoffset() + tz.utcoffset(self) + return ret.replace(tzinfo=tz) + + def dateisoformat(self): + return self.isoformat()[:10] + + def timeisoformat(self): + return self.isoformat()[11:19] + + def isoformat(self, sep="T"): + dt = ("%04d-%02d-%02d" + sep + "%02d:%02d:%02d") % self.tuple()[:6] + if self._tz == None: + return dt + return dt + self._tz.isoformat(self, utc=False) + + def toordinal(self): + return self._ord + + def isoweekday(self): + return self._ord % 7 or 7 + + def tuple(self): + return self._tuple(self._ord, self._time, self._tz)[:-2] + + def _sub(self, other): + # Subtract two datetime instances. + if (self._tz == None) ^ (other._tz == None): + raise TypeError + + if self._tz == None or self.utcoffset() == other.utcoffset(): + dt1 = self + dt2 = other + else: + dt1 = self.astimezone(timezone.utc) + dt2 = other.astimezone(timezone.utc) + + days = dt1._ord - dt2._ord + time = dt1._time - dt2._time + return days, time + + def _cmp(self, other): + # Compare two datetime instances. + days, time = self._sub(other) + if days < 0: + return -1 + if days > 0: + return 1 + + secs = time.total_seconds() + if secs < 0: + return -1 + if secs > 0: + return 1 + + return 0 + + def _tuple(self, ordinal, time, tz): + # Split a datetime to its components. + year, month, day = _ord2ymd(ordinal) + sign, days, hour, minute, second = time.tuple() + return year, month, day, hour, minute, second, tz, days, sign + + +def fromisoformat(s): + l = len(s) + if l < 10 or s[4] != "-" or s[7] != "-": + raise ValueError + # parse date + year = int(s[0:4]) + month = int(s[5:7]) + day = int(s[8:10]) + hour = 0 + minute = 0 + second = 0 + microsecond = 0 + tz_sign = "" + tz_hour = 0 + tz_minute = 0 + tz_second = 0 + tz_microsecond = 0 + i = 10 + if l > i and s[i] != "+": + # parse time + if l - i < 3: + raise ValueError + i += 3 + 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 + second = int(s[i - 2 : i]) + if l > i and s[i] == ".": + i += 4 + if l - i < 0: + raise ValueError + microsecond = 1000 * int(s[i - 3 : i]) + if l > i and s[i] != "+": + i += 3 + if l - i < 0: + raise ValueError + microsecond += 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_second = int(s[i - 2 : i]) + if l > i and s[i] == ".": + i += 7 + if l - i < 0: + raise ValueError + tz_microsecond = int(s[i - 6 : i]) + if l != i: + raise ValueError + if tz_sign: + td = timedelta(tz_hour, tz_minute, tz_second) + if tz_sign == "-": + td = -td + tz = timezone(td) + else: + tz = None + return datetime(year, month, day, hour, minute, second, tz) + + +def fromordinal(n): + if not 1 <= n <= 3652059: + raise ValueError + return datetime(0, 0, n) + + +def combine(date, time, tzinfo=True): + if tzinfo is True: + dt = date + else: + dt = date.replace(tzinfo=tzinfo) + return dt + time diff --git a/python-stdlib/datetime/metadata.txt b/python-stdlib/datetime/metadata.txt new file mode 100644 index 000000000..2e2955a36 --- /dev/null +++ b/python-stdlib/datetime/metadata.txt @@ -0,0 +1,4 @@ +srctype = cpython +type = module +version = 2.0.0 +author = Lorenzo Cappelletti diff --git a/python-stdlib/datetime/setup.py b/python-stdlib/datetime/setup.py new file mode 100644 index 000000000..55a3dd8c9 --- /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="2.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 000000000..1a9adf422 --- /dev/null +++ b/python-stdlib/datetime/test_datetime.py @@ -0,0 +1,704 @@ +import unittest +from datetime import\ + timedelta as td,\ + timezone as tz,\ + datetime as dt,\ + fromisoformat as fif + + +### timedelta ################################################################ + +a = td(hours=7) +b = td(minutes=6) +c = td(seconds=10) +s = td(seconds=1) +t1 = td(2, 3, 4) +t2 = td(2, 3, 4) +t3 = td(2, 3, 5) + +class TestTimeDelta(unittest.TestCase): + + def test_constructor01(self): + self.assertEqual(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0)) + + def test_constructor02(self): + self.assertEqual(td(weeks=1), td(days=7)) + + def test_constructor03(self): + self.assertEqual(td(days=1), td(hours=24)) + + def test_constructor04(self): + self.assertEqual(td(hours=1), td(minutes=60)) + + def test_constructor05(self): + self.assertEqual(td(minutes=1), td(seconds=60)) + + def test_constructor06(self): + self.assertEqual(td(weeks=1.0 / 7), td(days=1)) + + def test_constructor07(self): + self.assertEqual(td(days=1.0 / 24), td(hours=1)) + + def test_constructor08(self): + self.assertEqual(td(hours=1.0 / 60), td(minutes=1)) + + def test_constructor09(self): + self.assertEqual(td(minutes=1.0 / 60), td(seconds=1)) + + def test_constant01(self): + self.assertTrue(td(0, 0, 0, 365*td.MINYEAR).total_seconds() >= -2**31) + + def test_constant02(self): + self.assertFalse(td(0, 0, 0, 365*(td.MINYEAR - 1)).total_seconds() >= -2**31) + + def test_constant03(self): + self.assertTrue(td(23, 59, 59, 365*td.MAXYEAR).total_seconds() <= 2**31 - 1) + + def test_constant04(self): + self.assertFalse(td(23, 59, 59, 365*(td.MAXYEAR + 1)).total_seconds() <= 2**31 - 1) + + def test_computation01(self): + self.assertEqual(a + b + c, td(7, 6, 10)) + + def test_computation02(self): + self.assertEqual(a - b, td(6, 60 - 6)) + + def test_computation03(self): + self.assertEqual(-a, td(-7)) + + def test_computation04(self): + self.assertEqual(+a, td(7)) + + def test_computation05(self): + self.assertEqual(-b, td(-1, 54)) + + def test_computation06(self): + self.assertEqual(-c, td(-1, 59, 50)) + + def test_computation07(self): + self.assertEqual(abs(a), a) + + def test_computation08(self): + self.assertEqual(abs(-a), a) + + def test_computation09(self): + self.assertEqual(td(6, 60), a) + + def test_computation10(self): + self.assertEqual(td(0, 0, 60 * 6), b) + + def test_computation11(self): + self.assertEqual(a * 10, td(70)) + + def test_computation12(self): + self.assertEqual(a * 10, 10 * a) + + def test_computation13(self): + self.assertEqual(a * 10, 10 * a) + + def test_computation14(self): + self.assertEqual(b * 10, td(0, 60)) + + def test_computation15(self): + self.assertEqual(10 * b, td(0, 60)) + + def test_computation16(self): + self.assertEqual(c * 10, td(0, 0, 100)) + + def test_computation17(self): + self.assertEqual(10 * c, td(0, 0, 100)) + + def test_computation18(self): + self.assertEqual(a * -1, -a) + + def test_computation19(self): + self.assertEqual(b * -2, -b - b) + + def test_computation20(self): + self.assertEqual(c * -2, -c + -c) + + def test_computation21(self): + self.assertEqual(b * (60 * 24), (b * 60) * 24) + + def test_computation22(self): + self.assertEqual(b * (60 * 24), (60 * b) * 24) + + def test_computation23(self): + self.assertEqual(c * 6, td(0, 1)) + + def test_computation24(self): + self.assertEqual(6 * c, td(0, 1)) + + def test_computation25(self): + self.assertEqual(a // 7, td(1)) + + def test_computation26(self): + self.assertEqual(b // 6, td(0, 1)) + + def test_computation27(self): + self.assertEqual(c // 10, td(0, 0, 1)) + + def test_computation28(self): + self.assertEqual(a // 10, td(0, 7 * 6)) + + def test_computation29(self): + self.assertEqual(a // 3600, td(0, 0, 7)) + + def test_computation30(self): + self.assertEqual(a / 0.5, td(14)) + + def test_computation31(self): + self.assertEqual(b / 0.5, td(0, 12)) + + def test_computation32(self): + self.assertEqual(a / 7, td(1)) + + def test_computation33(self): + self.assertEqual(b / 6, td(0, 1)) + + def test_computation34(self): + self.assertEqual(c / 10, td(0, 0, 1)) + + def test_computation35(self): + self.assertEqual(a / 10, td(0, 7 * 6)) + + def test_computation36(self): + self.assertEqual(a / 3600, td(0, 0, 7)) + + def test_computation37(self): + self.assertEqual((3 * s) * 0.5, 2 * s) + + def test_computation38(self): + self.assertEqual((5 * s) * 0.5, 2 * s) + + def test_computation39(self): + self.assertEqual(0.5 * (3 * s), 2 * s) + + def test_computation40(self): + self.assertEqual(0.5 * (5 * s), 2 * s) + + def test_computation41(self): + self.assertEqual((-3 * s) * 0.5, -2 * s) + + def test_computation42(self): + self.assertEqual((-5 * s) * 0.5, -2 * s) + + def test_computation43(self): + self.assertEqual((3 * s) / 2, 2 * s) + + def test_computation44(self): + self.assertEqual((5 * s) / 2, 2 * s) + + def test_computation45(self): + self.assertEqual((-3 * s) / 2.0, -2 * s) + + def test_computation46(self): + self.assertEqual((-5 * s) / 2.0, -2 * s) + + def test_computation47(self): + self.assertEqual((3 * s) / -2, -2 * s) + + def test_computation48(self): + self.assertEqual((5 * s) / -2, -2 * s) + + def test_computation49(self): + self.assertEqual((3 * s) / -2.0, -2 * s) + + def test_computation50(self): + self.assertEqual((5 * s) / -2.0, -2 * s) + + def test_computation51(self): + for i in range(-10, 10): + self.assertEqual((i * s / 3) // s, round(i / 3)) + + def test_computation52(self): + for i in range(-10, 10): + self.assertEqual((i * s / -3) // s, round(i / -3)) + + def test_total_seconds(self): + d = td(days=365) + self.assertEqual(d.total_seconds(), 31536000.0) + + def test_carries(self): + t1 = td( + days=100, + weeks=-7, + hours=-24 * (100 - 49), + minutes=-3, + seconds=3 * 60 + 1, + ) + t2 = td(seconds=1) + self.assertEqual(t1, t2) + + def test_compare01(self): + self.assertEqual(t1, t2) + + def test_compare02(self): + self.assertTrue(t1 <= t2) + + def test_compare03(self): + self.assertTrue(t1 >= t2) + + def test_compare04(self): + self.assertTrue(not t1 != t2) + + def test_compare05(self): + self.assertTrue(not t1 < t2) + + def test_compare06(self): + self.assertTrue(not t1 > t2) + + def test_compare07(self): + self.assertTrue(t1 < t3) + + def test_compare08(self): + self.assertTrue(t3 > t1) + + def test_compare09(self): + self.assertTrue(t1 <= t3) + + def test_compare10(self): + self.assertTrue(t3 >= t1) + + def test_compare11(self): + self.assertTrue(t1 != t3) + + def test_compare12(self): + self.assertTrue(t3 != t1) + + def test_compare13(self): + self.assertTrue(not t1 == t3) + + def test_compare14(self): + self.assertTrue(not t3 == t1) + + def test_compare15(self): + self.assertTrue(not t1 > t3) + + def test_compare16(self): + self.assertTrue(not t3 < t1) + + def test_compare17(self): + self.assertTrue(not t1 >= t3) + + def test_compare18(self): + self.assertTrue(not t3 <= t1) + + def test_str01(self): + self.assertEqual(str(td(days=1)), "1d 00:00:00") + + def test_str02(self): + self.assertEqual(str(td(days=-1)), "-1d 00:00:00") + + def test_str03(self): + self.assertEqual(str(td(days=2)), "2d 00:00:00") + + def test_str04(self): + self.assertEqual(str(td(days=-2)), "-2d 00:00:00") + + def test_str05(self): + self.assertEqual(str(td(12, 58, 59)), "12:58:59") + + def test_str06(self): + self.assertEqual(str(td(2, 3, 4)), "02:03:04") + + def test_repr01(self): + self.assertEqual(repr(td(1)), "datetime.timedelta(seconds=%d)" % (1*3600)) + + def test_repr02(self): + self.assertEqual(repr(td(10, 2)), "datetime.timedelta(seconds=%d)" % (10*3600 + 2*60)) + + def test_repr03(self): + self.assertEqual(repr(td(-10, 2, 40)), "datetime.timedelta(seconds=%d)" % (-10*3600 + 2*60 + 40)) + + def test_bool01(self): + self.assertTrue(td(1)) + + def test_bool02(self): + self.assertTrue(td(0, 1)) + + def test_bool03(self): + self.assertTrue(td(0, 0, 1)) + + def test_bool04(self): + self.assertTrue(not td(0)) + + def test_division01(self): + t = td(hours=1, minutes=24, seconds=19) + second = td(seconds=1) + self.assertEqual(t / second, 5059.0) + self.assertEqual(t // second, 5059) + + def test_division02(self): + t = td(minutes=2, seconds=30) + minute = td(minutes=1) + self.assertEqual(t / minute, 2.5) + self.assertEqual(t // minute, 2) + + def test_remainder01(self): + t = td(minutes=2, seconds=30) + r = t % td(minutes=1) + self.assertEqual(r, td(seconds=30)) + + def test_remainder02(self): + t = td(minutes=-2, seconds=30) + r = t % td(minutes=1) + self.assertEqual(r, td(seconds=30)) + + def test_divmod01(self): + t = td(minutes=2, seconds=30) + q, r = divmod(t, td(minutes=1)) + self.assertEqual(q, 2) + self.assertEqual(r, td(seconds=30)) + + def test_divmod02(self): + t = td(minutes=-2, seconds=30) + q, r = divmod(t, td(minutes=1)) + self.assertEqual(q, -2) + self.assertEqual(r, td(seconds=30)) + + +### timezone ################################################################# + +class Cet(tz): + # Central European Time (see https://en.wikipedia.org/wiki/Summer_time_in_Europe) + + def __init__(self): + super().__init__(td(hours=1), "CET") + + def dst(self, dt): + return td(hours=1) if self.isdst(dt) else td(0) + + def tzname(self, dt): + return 'CEST' if self.isdst(dt) else 'CET' + + def isdst(self, dt): + if dt is None: + return False + year, month, day, hour, minute, second, tz = dt.tuple() + if not 2000 <= year < 2100: + raise ValueError + if 3 < month < 10: + return True + if month == 3: + beg = 31 - (5*year//4 + 4) % 7 # last Sunday of March + if day < beg: return False + if day > beg: return True + return hour >= 3 + if month == 10: + end = 31 - (5*year//4 + 1) % 7 # last Sunday of October + if day < end: return True + if day > end: return False + return hour < 3 + return False + +tz1 = tz(td(hours=-1)) +tz2 = Cet() + +class TestTimeZone(unittest.TestCase): + + def test_constructor01(self): + self.assertEqual(str(tz1), 'UTC-01:00') + + def test_constructor02(self): + self.assertEqual(str(tz2), 'CET') + + def test_utcoffset01(self): + self.assertEqual(str(tz2.utcoffset(None)), '01:00:00') + + def test_utcoffset02(self): + self.assertEqual(str(tz2.utcoffset(dt(2010, 3, 27, 12))), '01:00:00') + + def test_utcoffset03(self): + self.assertEqual(str(tz2.utcoffset(dt(2010, 3, 28, 12))), '02:00:00') + + def test_utcoffset04(self): + self.assertEqual(str(tz2.utcoffset(dt(2010, 10, 30, 12))), '02:00:00') + + def test_utcoffset05(self): + self.assertEqual(str(tz2.utcoffset(dt(2010, 10, 31, 12))), '01:00:00') + + def test_isoformat01(self): + self.assertEqual(tz2.isoformat(dt(2011, 1, 1)), 'UTC+01:00') + + def test_isoformat02(self): + self.assertEqual(tz2.isoformat(dt(2011, 8, 1)), 'UTC+02:00') + + def test_tzname01(self): + self.assertEqual(tz2.tzname(dt(2011, 1, 1)), 'CET') + + def test_tzname02(self): + self.assertEqual(tz2.tzname(dt(2011, 8, 1)), 'CEST') + + +### datetime ################################################################# + +d1 = dt(2002, 1, 31) +d2 = dt(1956, 1, 31) +d3 = dt(2002, 3, 1, 12, 59, 59, tz2) +d4 = dt(2002, 3, 2, 17, 6) +d5 = dt(2002, 1, 31) + +hour = td(hours=1) +day = td(days=1) +week = td(weeks=1) + +class TestTimeZone(unittest.TestCase): + + def test_constructor01(self): + d = dt(2002, 3, 1, 12, 0) + year, month, day, hour, minute, second, tz = d.tuple() + self.assertEqual(year, 2002) + self.assertEqual(month, 3) + self.assertEqual(day, 1) + self.assertEqual(hour, 12) + self.assertEqual(minute, 0) + self.assertEqual(second, 0) + self.assertEqual(tz, None) + + def test_constructor02(self): + year, month, day, hour, minute, second, tz = d3.tuple() + self.assertEqual(year, 2002) + self.assertEqual(month, 3) + self.assertEqual(day, 1) + self.assertEqual(hour, 12) + self.assertEqual(minute, 59) + self.assertEqual(second, 59) + self.assertEqual(tz, tz2) + + def test_constructor03(self): + dt(dt.MINYEAR, 1, 1) + + def test_constructor04(self): + dt(dt.MAXYEAR, 12, 31) + + def test_constructor05(self): + self.assertRaises(ValueError, dt, dt.MINYEAR - 1, 1, 1) + + def test_constructor06(self): + self.assertRaises(ValueError, dt, dt.MAXYEAR + 1, 1, 1) + + def test_constructor07(self): + self.assertRaises(ValueError, dt, 2000, 0, 1) + + def test_constructor08(self): + dt(2000, 2, 29) + + def test_constructor09(self): + dt(2004, 2, 29) + + def test_constructor10(self): + dt(2400, 2, 29) + + def test_constructor11(self): + self.assertRaises(ValueError, dt, 2000, 2, 30) + + def test_constructor12(self): + self.assertRaises(ValueError, dt, 2001, 2, 29) + + def test_constructor13(self): + self.assertRaises(ValueError, dt, 2100, 2, 29) + + def test_constructor14(self): + self.assertRaises(ValueError, dt, 1900, 2, 29) + + def test_constructor15(self): + self.assertRaises(ValueError, dt, 2000, 1, 0) + + def test_constructor16(self): + self.assertRaises(ValueError, dt, 2000, 1, 32) + + def test_constructor17(self): + self.assertRaises(ValueError, dt, 2000, 1, 31, -1) + + def test_constructor18(self): + self.assertRaises(ValueError, dt, 2000, 1, 31, 24) + + def test_constructor19(self): + self.assertRaises(ValueError, dt, 2000, 1, 31, 23, -1) + + def test_constructor20(self): + self.assertRaises(ValueError, dt, 2000, 1, 31, 23, 60) + + def test_constructor21(self): + self.assertRaises(ValueError, dt, 2000, 1, 31, 23, 59, -1) + + def test_constructor22(self): + self.assertRaises(ValueError, dt, 2000, 1, 31, 23, 59, 60) + + def test_computation01(self): + d = d1 - d2 + self.assertEqual(d.total_seconds(), + (46 * 365 + len(range(1956, 2002, 4)))*24*60*60) + + def test_computation02(self): + self.assertEqual(d4 + hour, dt(2002, 3, 2, 18, 6)) + + def test_computation02(self): + self.assertEqual(hour + d4, dt(2002, 3, 2, 18, 6)) + + def test_computation03(self): + self.assertEqual(d4 + 10*hour, dt(2002, 3, 3, 3, 6)) + + def test_computation04(self): + self.assertEqual(d4 - hour, dt(2002, 3, 2, 16, 6)) + + def test_computation05(self): + self.assertEqual(-hour + d4, dt(2002, 3, 2, 16, 6)) + + def test_computation06(self): + self.assertEqual(d4 - hour, d4 + -hour) + + def test_computation07(self): + self.assertEqual(d4 - 20*hour, dt(2002, 3, 1, 21, 6)) + + def test_computation08(self): + self.assertEqual(d4 + day, dt(2002, 3, 3, 17, 6)) + + def test_computation09(self): + self.assertEqual(d4 - day, dt(2002, 3, 1, 17, 6)) + + def test_computation10(self): + self.assertEqual(d4 + week, dt(2002, 3, 9, 17, 6)) + + def test_computation11(self): + self.assertEqual(d4 - week, dt(2002, 2, 23, 17, 6)) + + def test_computation12(self): + self.assertEqual(d4 + 52*week, dt(2003, 3, 1, 17, 6)) + + def test_computation13(self): + self.assertEqual(d4 - 52*week, dt(2001, 3, 3, 17, 6)) + + def test_computation14(self): + self.assertEqual((d4 + week) - d4, week) + + def test_computation15(self): + self.assertEqual((d4 + day) - d4, day) + + def test_computation16(self): + self.assertEqual((d4 + hour) - d4, hour) + + def test_computation17(self): + self.assertEqual(d4 - (d4 + week), -week) + + def test_computation18(self): + self.assertEqual(d4 - (d4 + day), -day) + + def test_computation19(self): + self.assertEqual(d4 - (d4 + hour), -hour) + + def test_computation20(self): + self.assertEqual(d4 - (d4 - week), week) + + def test_computation21(self): + self.assertEqual(d4 - (d4 - day), day) + + def test_computation22(self): + self.assertEqual(d4 - (d4 - hour), hour) + + def test_computation23(self): + self.assertEqual(d4 + (week + day + hour), dt(2002, 3, 10, 18, 6)) + + def test_computation24(self): + self.assertEqual(d4 + (week + day + hour), (((d4 + week) + day) + hour)) + + def test_computation25(self): + self.assertEqual(d4 - (week + day + hour), dt(2002, 2, 22, 16, 6)) + + def test_computation26(self): + self.assertEqual(d4 - (week + day + hour), (((d4 - week) - day) - hour)) + + def test_compare01(self): + self.assertEqual(d1, d5) + + def test_compare02(self): + self.assertTrue(d1 <= d5) + + def test_compare03(self): + self.assertTrue(d1 >= d5) + + def test_compare04(self): + self.assertFalse(d1 != d5) + + def test_compare05(self): + self.assertFalse(d1 < d5) + + def test_compare06(self): + self.assertFalse(d1 > d5) + + def test_compare07(self): + self.assertTrue(d2 < d5) + + def test_compare08(self): + self.assertTrue(d5 > d2) + + def test_compare09(self): + self.assertTrue(d2 <= d5) + + def test_compare10(self): + self.assertTrue(d5 >= d2) + + def test_compare11(self): + self.assertTrue(d2 != d5) + + def test_compare12(self): + self.assertTrue(d5 != d2) + + def test_compare13(self): + self.assertFalse(d2 == d5) + + def test_compare14(self): + self.assertFalse(d5 == d2) + + def test_compare15(self): + self.assertFalse(d2 > d5) + + def test_compare16(self): + self.assertFalse(d5 < d2) + + def test_compare17(self): + self.assertFalse(d2 >= d5) + + def test_compare18(self): + self.assertFalse(d5 <= d2) + + def test_astimezone01(self): + self.assertEqual(d3.astimezone(tz.utc), dt(2002, 3, 1, 11, 59, 59, tz.utc)) + + def test_isoformat01(self): + self.assertEqual(d3.isoformat(), "2002-03-01T12:59:59+01:00") + + def test_isoformat02(self): + self.assertEqual(d3.isoformat("T"), "2002-03-01T12:59:59+01:00") + + def test_isoformat03(self): + self.assertEqual(d3.isoformat(" "), "2002-03-01 12:59:59+01:00") + + def test_isoformat04(self): + self.assertEqual(str(d3), "2002-03-01 12:59:59+01:00") + + def test_isoformat05(self): + self.assertEqual(d3.dateisoformat(), "2002-03-01") + + def test_isoformat06(self): + self.assertEqual(d3.timeisoformat(), "12:59:59") + + def test_fromisoformat01(self): + self.assertEqual(fif("1975-08-10"), dt(1975, 8, 10)) + + def test_fromisoformat02(self): + self.assertEqual(fif("1975-08-10 23"), dt(1975, 8, 10, 23)) + + def test_fromisoformat03(self): + self.assertEqual(fif("1975-08-10 23:30"), dt(1975, 8, 10, 23, 30)) + + def test_fromisoformat04(self): + self.assertEqual(fif("1975-08-10 23:30:12"), dt(1975, 8, 10, 23, 30, 12)) + + def test_fromisoformat05(self): + self.assertEqual(str(fif("1975-08-10 23:30:12+01:00")), "1975-08-10 23:30:12+01:00") + + +if __name__ == '__main__': + unittest.main() diff --git a/unix-ffi/datetime/datetime.py b/unix-ffi/datetime/datetime.py deleted file mode 100644 index f9eeb90f7..000000000 --- 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 950962aef..000000000 --- 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 2f502e520..000000000 --- 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 180ad194f..000000000 --- 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() From 639d1b8db5a871554d5ad29df532324479155c64 Mon Sep 17 00:00:00 2001 From: Lorenzo Cappelletti Date: Sat, 2 Oct 2021 11:14:50 +0200 Subject: [PATCH 02/14] python-stdlib/datetime: add @property timezone Signed-off-by: Lorenzo Cappelletti --- python-stdlib/datetime/datetime.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/python-stdlib/datetime/datetime.py b/python-stdlib/datetime/datetime.py index 96751efe6..9693bfed7 100644 --- a/python-stdlib/datetime/datetime.py +++ b/python-stdlib/datetime/datetime.py @@ -255,6 +255,10 @@ def __repr__(self): def __str__(self): return self.isoformat(" ") + @property + def tzinfo(self): + return self._tz + def utcoffset(self): return None if self._tz is None else self._tz.utcoffset(self) From d26e198d79be7a08ee823097d779c95da466d140 Mon Sep 17 00:00:00 2001 From: Lorenzo Cappelletti Date: Tue, 26 Oct 2021 22:43:34 +0200 Subject: [PATCH 03/14] python-stdlib/datetime: add EPOCH. Signed-off-by: Lorenzo Cappelletti --- python-stdlib/datetime/datetime.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/python-stdlib/datetime/datetime.py b/python-stdlib/datetime/datetime.py index 9693bfed7..5b9343e19 100644 --- a/python-stdlib/datetime/datetime.py +++ b/python-stdlib/datetime/datetime.py @@ -360,6 +360,9 @@ def _tuple(self, ordinal, time, tz): return year, month, day, hour, minute, second, tz, days, sign +datetime.EPOCH = datetime(2000, 1, 1, tzinfo=timezone.utc) + + def fromisoformat(s): l = len(s) if l < 10 or s[4] != "-" or s[7] != "-": From fb20fa75ce8e48337d64399fd0ec373e05fb54c3 Mon Sep 17 00:00:00 2001 From: Lorenzo Cappelletti Date: Tue, 26 Oct 2021 21:46:39 +0200 Subject: [PATCH 04/14] python-stdlib/datetime: optimize variable names. Signed-off-by: Lorenzo Cappelletti --- python-stdlib/datetime/datetime.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/python-stdlib/datetime/datetime.py b/python-stdlib/datetime/datetime.py index 5b9343e19..b428cec56 100644 --- a/python-stdlib/datetime/datetime.py +++ b/python-stdlib/datetime/datetime.py @@ -373,13 +373,13 @@ def fromisoformat(s): day = int(s[8:10]) hour = 0 minute = 0 - second = 0 - microsecond = 0 + sec = 0 + usec = 0 tz_sign = "" tz_hour = 0 tz_minute = 0 - tz_second = 0 - tz_microsecond = 0 + tz_sec = 0 + tz_usec = 0 i = 10 if l > i and s[i] != "+": # parse time @@ -396,17 +396,17 @@ def fromisoformat(s): i += 3 if l - i < 0: raise ValueError - second = int(s[i - 2 : i]) + sec = int(s[i - 2 : i]) if l > i and s[i] == ".": i += 4 if l - i < 0: raise ValueError - microsecond = 1000 * int(s[i - 3 : i]) + usec = 1000 * int(s[i - 3 : i]) if l > i and s[i] != "+": i += 3 if l - i < 0: raise ValueError - microsecond += int(s[i - 3 : i]) + usec += int(s[i - 3 : i]) if l > i: if s[i] not in "+-": raise ValueError @@ -420,22 +420,22 @@ def fromisoformat(s): i += 3 if l - i < 0: raise ValueError - tz_second = int(s[i - 2 : i]) + tz_sec = int(s[i - 2 : i]) if l > i and s[i] == ".": i += 7 if l - i < 0: raise ValueError - tz_microsecond = int(s[i - 6 : i]) + tz_usec = int(s[i - 6 : i]) if l != i: raise ValueError if tz_sign: - td = timedelta(tz_hour, tz_minute, tz_second) + td = timedelta(tz_hour, tz_minute, tz_sec) if tz_sign == "-": td = -td tz = timezone(td) else: tz = None - return datetime(year, month, day, hour, minute, second, tz) + return datetime(year, month, day, hour, minute, sec, tz) def fromordinal(n): From 112e17f1f741e390c4811ad464e5a6ebcd1f4b73 Mon Sep 17 00:00:00 2001 From: Lorenzo Cappelletti Date: Tue, 26 Oct 2021 22:02:45 +0200 Subject: [PATCH 05/14] python-stdlib/datetime: get rid of embarassing comment. Signed-off-by: Lorenzo Cappelletti --- python-stdlib/datetime/datetime.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python-stdlib/datetime/datetime.py b/python-stdlib/datetime/datetime.py index b428cec56..3551245d5 100644 --- a/python-stdlib/datetime/datetime.py +++ b/python-stdlib/datetime/datetime.py @@ -2,7 +2,6 @@ __version__ = "2.0.0" -# The following functions were (stolen and) adapted from Python's datetime. def _is_leap(year): return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) From 15f4fdd94db12f8c26e7ca4e012c320be9937067 Mon Sep 17 00:00:00 2001 From: Lorenzo Cappelletti Date: Tue, 26 Oct 2021 22:03:22 +0200 Subject: [PATCH 06/14] python-stdlib/datetime: optimize inline constant tuples. Signed-off-by: Lorenzo Cappelletti --- python-stdlib/datetime/datetime.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/python-stdlib/datetime/datetime.py b/python-stdlib/datetime/datetime.py index 3551245d5..208d8be6d 100644 --- a/python-stdlib/datetime/datetime.py +++ b/python-stdlib/datetime/datetime.py @@ -12,18 +12,20 @@ def _days_before_year(year): return y * 365 + y // 4 - y // 100 + y // 400 +_DIM = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) + def _days_in_month(year, month): # year, month -> number of days in that month in that year. if month == 2 and _is_leap(year): return 29 - return (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)[month] + return _DIM[month] + +_DBM = (0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334) def _days_before_month(year, month): # year, month -> number of days in year preceding first day of month. - return (0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334)[month] + ( - month > 2 and _is_leap(year) - ) + return _DBM[month] + (month > 2 and _is_leap(year)) def _ymd2ord(year, month, day): From d8118ccd72004dc5595893b8e8dc681c994136fb Mon Sep 17 00:00:00 2001 From: Lorenzo Cappelletti Date: Mon, 1 Nov 2021 21:00:19 +0100 Subject: [PATCH 07/14] python-stdlib/datetime: sort method order. By matching the order of class methods used in Python's datetime, code changes are easier to implement. Signed-off-by: Lorenzo Cappelletti --- python-stdlib/datetime/datetime.py | 184 ++++++++++++++--------------- 1 file changed, 89 insertions(+), 95 deletions(-) diff --git a/python-stdlib/datetime/datetime.py b/python-stdlib/datetime/datetime.py index 208d8be6d..238a3d4b8 100644 --- a/python-stdlib/datetime/datetime.py +++ b/python-stdlib/datetime/datetime.py @@ -60,6 +60,12 @@ class timedelta: def __init__(self, hours=0, minutes=0, seconds=0, days=0, weeks=0): self._s = round((((weeks * 7 + days) * 24 + hours) * 60 + minutes) * 60 + seconds) + def __repr__(self): + return "datetime.timedelta(seconds=%d)" % self._s + + def __str__(self): + return self.isoformat() + def total_seconds(self): return self._s @@ -71,6 +77,15 @@ def __add__(self, other): def __sub__(self, other): return timedelta(seconds=self._s - other._s) + def __neg__(self): + return timedelta(seconds=-self._s) + + def __pos__(self): + return self + + def __abs__(self): + return timedelta(seconds=abs(self._s)) + def __mul__(self, other): return timedelta(seconds=round(other * self._s)) @@ -95,12 +110,6 @@ def __divmod__(self, other): q, r = divmod(self._s, other._s) return q, timedelta(seconds=r) - def __neg__(self): - return timedelta(seconds=-self._s) - - def __pos__(self): - return self - def __eq__(self, other): return self._s == other._s @@ -119,21 +128,6 @@ def __gt__(self, other): def __bool__(self): return self._s != 0 - def __neg__(self): - return timedelta(seconds=-self._s) - - def __pos__(self): - return self - - def __abs__(self): - return timedelta(seconds=abs(self._s)) - - def __repr__(self): - return "datetime.timedelta(seconds=%d)" % self._s - - def __str__(self): - return self.isoformat() - def isoformat(self): t = self.tuple() if 0 <= self._s < 86400: @@ -215,65 +209,18 @@ def __init__(self, year, month, day, hour=0, minute=0, second=0, tzinfo=None): self._time = timedelta(hour, minute, second) self._tz = tzinfo - def __add__(self, other): - time = self._time + other - sign, days, hour, minute, second = time.tuple() - if sign == "-": - days += 1 - time += timedelta(days=days) - days = -days - year, month, day, hour, minute, second, tz = self._tuple(self._ord + days, time, self._tz)[:7] - return datetime(year, month, day, hour, minute, second, tz) - - def __sub__(self, other): - if isinstance(other, timedelta): - return self + -other - elif isinstance(other, datetime): - days, time = self._sub(other) - return time + timedelta(days=days) - else: - raise TypeError - - def __lt__(self, other): - return self._cmp(other) < 0 - - def __le__(self, other): - return self._cmp(other) <= 0 - - def __eq__(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 __repr__(self): - return "datetime.datetime(days=%d, seconds=%d, tzinfo=%s)" \ - % (self._ord, self._time._s, repr(self._tz)) - - def __str__(self): - return self.isoformat(" ") - @property def tzinfo(self): return self._tz - 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 date(self): return datetime(0, 0, self.toordinal(), tzinfo=self._tz) def time(self): return timedelta(seconds=self._time.total_seconds()) - def tzname(self): - return None if self._tz is None else self._tz.tzname(self) + def toordinal(self): + return self._ord def replace( self, year=None, month=None, day=None, hour=None, minute=None, second=None, tzinfo=True @@ -301,42 +248,42 @@ def astimezone(self, tz): ret = self - self.utcoffset() + tz.utcoffset(self) return ret.replace(tzinfo=tz) - def dateisoformat(self): - return self.isoformat()[:10] - - def timeisoformat(self): - return self.isoformat()[11:19] - def isoformat(self, sep="T"): dt = ("%04d-%02d-%02d" + sep + "%02d:%02d:%02d") % self.tuple()[:6] if self._tz == None: return dt return dt + self._tz.isoformat(self, utc=False) - def toordinal(self): - return self._ord + def __repr__(self): + return "datetime.datetime(days=%d, seconds=%d, tzinfo=%s)" \ + % (self._ord, self._time._s, repr(self._tz)) - def isoweekday(self): - return self._ord % 7 or 7 + def __str__(self): + return self.isoformat(" ") - def tuple(self): - return self._tuple(self._ord, self._time, self._tz)[:-2] + def utcoffset(self): + return None if self._tz is None else self._tz.utcoffset(self) - def _sub(self, other): - # Subtract two datetime instances. - if (self._tz == None) ^ (other._tz == None): - raise TypeError + def tzname(self): + return None if self._tz is None else self._tz.tzname(self) - if self._tz == None or self.utcoffset() == other.utcoffset(): - dt1 = self - dt2 = other - else: - dt1 = self.astimezone(timezone.utc) - dt2 = other.astimezone(timezone.utc) + def dst(self): + return None if self._tz is None else self._tz.dst(self) - days = dt1._ord - dt2._ord - time = dt1._time - dt2._time - return days, time + def __eq__(self, other): + 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. @@ -354,6 +301,53 @@ def _cmp(self, other): return 0 + def __add__(self, other): + time = self._time + other + sign, days, hour, minute, second = time.tuple() + if sign == "-": + days += 1 + time += timedelta(days=days) + days = -days + year, month, day, hour, minute, second, tz = self._tuple(self._ord + days, time, self._tz)[:7] + return datetime(year, month, day, hour, minute, second, tz) + + def __sub__(self, other): + if isinstance(other, timedelta): + return self + -other + elif isinstance(other, datetime): + days, time = self._sub(other) + return time + timedelta(days=days) + else: + raise TypeError + + def _sub(self, other): + # Subtract two datetime instances. + if (self._tz == None) ^ (other._tz == None): + raise TypeError + + if self._tz == None or self.utcoffset() == other.utcoffset(): + dt1 = self + dt2 = other + else: + dt1 = self.astimezone(timezone.utc) + dt2 = other.astimezone(timezone.utc) + + days = dt1._ord - dt2._ord + time = dt1._time - dt2._time + return days, time + + def isoweekday(self): + return self._ord % 7 or 7 + + def dateisoformat(self): + return self.isoformat()[:10] + + def timeisoformat(self): + return self.isoformat()[11:19] + + def tuple(self): + return self._tuple(self._ord, self._time, self._tz)[:-2] + def _tuple(self, ordinal, time, tz): # Split a datetime to its components. year, month, day = _ord2ymd(ordinal) From 3a9085381042a97ad659ef3bcd1e1ba1a7ac83c4 Mon Sep 17 00:00:00 2001 From: Lorenzo Cappelletti Date: Mon, 1 Nov 2021 23:15:58 +0100 Subject: [PATCH 08/14] python-stdlib/datetime: nanosecond resolution. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `timedelta` resolution is now 1 nanosecond, instead of 1 second. The module is now suitable for logging at microsecond scale and below. As a bonus, `timedelta` can now handle deltas which span ±292 years (instead of the previous ±68 years). The cost is 4 more bytes per instance. Signed-off-by: Lorenzo Cappelletti --- python-stdlib/datetime/datetime.py | 119 +++++++++++++----------- python-stdlib/datetime/test_datetime.py | 86 ++++++++++------- 2 files changed, 120 insertions(+), 85 deletions(-) diff --git a/python-stdlib/datetime/datetime.py b/python-stdlib/datetime/datetime.py index 238a3d4b8..bae2b2072 100644 --- a/python-stdlib/datetime/datetime.py +++ b/python-stdlib/datetime/datetime.py @@ -54,103 +54,113 @@ def _ord2ymd(n): class timedelta: - MINYEAR = -68 # timedelta( 0, 0, 0, -365*68).total_seconds() >= -2**31 - MAXYEAR = 68 # timedelta(23, 59, 59, 365*68).total_seconds() <= 2**31 - 1 + MINYEAR = -292 # timedelta( 0, 0, 0, -365*584).total_seconds() > -2**63 / 10**9 + MAXYEAR = 292 # timedelta(23, 59, 59, 365*584).total_seconds() < (2**63 - 1) / 10**9 - def __init__(self, hours=0, minutes=0, seconds=0, days=0, weeks=0): - self._s = round((((weeks * 7 + days) * 24 + hours) * 60 + minutes) * 60 + seconds) + def __init__(self, hours=0, minutes=0, seconds=0, days=0, weeks=0, milliseconds=0, microseconds=0, nanoseconds=0): + self._ns = round(((((((weeks * 7 + days) * 24 + hours) * 60 + minutes) * 60 + seconds) * 1000 + milliseconds) * 1000 + microseconds) * 1000 + nanoseconds) def __repr__(self): - return "datetime.timedelta(seconds=%d)" % self._s + return "datetime.timedelta(seconds={})".format(self.total_seconds()) def __str__(self): return self.isoformat() def total_seconds(self): - return self._s + return self._ns / 1_000_000_000 + + @property + def nanoseconds(self): + return self._ns def __add__(self, other): if isinstance(other, datetime): return other + self - return timedelta(seconds=self._s + other._s) + return timedelta(nanoseconds=self._ns + other._ns) def __sub__(self, other): - return timedelta(seconds=self._s - other._s) + return timedelta(nanoseconds=self._ns - other._ns) def __neg__(self): - return timedelta(seconds=-self._s) + return timedelta(nanoseconds=-self._ns) def __pos__(self): return self def __abs__(self): - return timedelta(seconds=abs(self._s)) + return -self if self._ns < 0 else self def __mul__(self, other): - return timedelta(seconds=round(other * self._s)) + return timedelta(nanoseconds=round(other * self._ns)) __rmul__ = __mul__ def __truediv__(self, other): if isinstance(other, timedelta): - return self._s / other._s + return self._ns / other._ns else: - return timedelta(seconds=round(self._s / other)) + return timedelta(nanoseconds=round(self._ns / other)) def __floordiv__(self, other): if isinstance(other, timedelta): - return self._s // other._s + return self._ns // other._ns else: - return timedelta(seconds=int(self._s // other)) + return timedelta(nanoseconds=int(self._ns // other)) def __mod__(self, other): - return timedelta(seconds=self._s % other._s) + return timedelta(nanoseconds=self._ns % other._ns) def __divmod__(self, other): - q, r = divmod(self._s, other._s) - return q, timedelta(seconds=r) + q, r = divmod(self._ns, other._ns) + return q, timedelta(nanoseconds=r) def __eq__(self, other): - return self._s == other._s + return self._ns == other._ns def __le__(self, other): - return self._s <= other._s + return self._ns <= other._ns def __lt__(self, other): - return self._s < other._s + return self._ns < other._ns def __ge__(self, other): - return self._s >= other._s + return self._ns >= other._ns def __gt__(self, other): - return self._s > other._s + return self._ns > other._ns def __bool__(self): - return self._s != 0 + return self._ns != 0 def isoformat(self): t = self.tuple() - if 0 <= self._s < 86400: - return "%02d:%02d:%02d" % t[2:] + if 0 <= self._ns < 86400000000000: + s = str() else: - return "%s%dd %02d:%02d:%02d" % t + s = "%s%dd " % t[:2] + s += "%02d:%02d:%02d" % t[2:5] + us = round(t[5] / 1000) + if us: + s += f".{us:06d}" + return s def tuple(self, sign_pos=""): - s = self._s - if s < 0: - s *= -1 + ns = self._ns + if ns < 0: + ns *= -1 g = "-" else: g = sign_pos + s, ns = divmod(ns, 1_000_000_000) m, s = divmod(s, 60) h, m = divmod(m, 60) d, h = divmod(h, 24) - return g, d, h, m, s + return g, d, h, m, s, ns -timedelta.min = timedelta(seconds=-2147483648) # -2**31 -timedelta.max = timedelta(seconds=2147483647) # 2**31 - 1 -timedelta.resolution = timedelta(seconds=1) +timedelta.min = timedelta(seconds=-9223372036854775808) # -2**63 +timedelta.max = timedelta(seconds=9223372036854775807) # 2**63 - 1 +timedelta.resolution = timedelta(nanoseconds=1) class timezone: @@ -181,7 +191,7 @@ def isoformat(self, dt, *, utc=True): td = self.utcoffset(dt) if utc and not td: return "UTC" - sign, day, hour, minute, second = td.tuple("+") + sign, day, hour, minute, second, nanosec = td.tuple("+") return "%s%s%02d:%02d" % ("UTC" if utc else "", sign, hour, minute) @@ -192,7 +202,7 @@ class datetime: MINYEAR = 1 MAXYEAR = 9999 - def __init__(self, year, month, day, hour=0, minute=0, second=0, tzinfo=None): + def __init__(self, year, month, day, hour=0, minute=0, second=0, nanosecond=0, tzinfo=None): if year == 0 and month == 0 and day > 0: self._ord = day elif ( @@ -202,11 +212,12 @@ def __init__(self, year, month, day, hour=0, minute=0, second=0, tzinfo=None): and 0 <= hour < 24 and 0 <= minute < 60 and 0 <= second < 60 + and 0 <= nanosecond < 1_000_000_000 ): self._ord = _ymd2ord(year, month, day) else: raise ValueError - self._time = timedelta(hour, minute, second) + self._time = timedelta(hour, minute, second, nanosecond) self._tz = tzinfo @property @@ -217,15 +228,15 @@ def date(self): return datetime(0, 0, self.toordinal(), tzinfo=self._tz) def time(self): - return timedelta(seconds=self._time.total_seconds()) + return timedelta(nanoseconds=self._time.nanoseconds) def toordinal(self): return self._ord def replace( - self, year=None, month=None, day=None, hour=None, minute=None, second=None, tzinfo=True + self, year=None, month=None, day=None, hour=None, minute=None, second=None, nanosecond=None, tzinfo=True ): - year_, month_, day_, hour_, minute_, second_, tz_ = self.tuple() + year_, month_, day_, hour_, minute_, second_, nanosec_, tz_ = self.tuple() if year is None: year = year_ if month is None: @@ -238,9 +249,11 @@ def replace( minute = minute_ if second is None: second = second_ + if nanosecond is None: + nanosecond = nanosec_ if tzinfo is True: tzinfo = tz_ - return datetime(year, month, day, hour, minute, second, tzinfo) + return datetime(year, month, day, hour, minute, second, nanosecond, tzinfo) def astimezone(self, tz): if self._tz == None: @@ -255,8 +268,8 @@ def isoformat(self, sep="T"): return dt + self._tz.isoformat(self, utc=False) def __repr__(self): - return "datetime.datetime(days=%d, seconds=%d, tzinfo=%s)" \ - % (self._ord, self._time._s, repr(self._tz)) + return "datetime.datetime(day=%d, nanosecond=%d, tzinfo=%s)" \ + % (self._ord, self._time._ns, repr(self._tz)) def __str__(self): return self.isoformat(" ") @@ -293,23 +306,23 @@ def _cmp(self, other): if days > 0: return 1 - secs = time.total_seconds() - if secs < 0: + ns = time.nanoseconds + if ns < 0: return -1 - if secs > 0: + if ns > 0: return 1 return 0 def __add__(self, other): time = self._time + other - sign, days, hour, minute, second = time.tuple() + sign, days, hour, minute, second, nanosec = time.tuple() if sign == "-": days += 1 time += timedelta(days=days) days = -days - year, month, day, hour, minute, second, tz = self._tuple(self._ord + days, time, self._tz)[:7] - return datetime(year, month, day, hour, minute, second, tz) + year, month, day, hour, minute, second, nanosec, tz = self._tuple(self._ord + days, time, self._tz)[:8] + return datetime(year, month, day, hour, minute, second, nanosec, tz) def __sub__(self, other): if isinstance(other, timedelta): @@ -351,8 +364,8 @@ def tuple(self): def _tuple(self, ordinal, time, tz): # Split a datetime to its components. year, month, day = _ord2ymd(ordinal) - sign, days, hour, minute, second = time.tuple() - return year, month, day, hour, minute, second, tz, days, sign + sign, days, hour, minute, second, nanosec = time.tuple() + return year, month, day, hour, minute, second, nanosec, tz, days, sign datetime.EPOCH = datetime(2000, 1, 1, tzinfo=timezone.utc) @@ -424,13 +437,13 @@ def fromisoformat(s): if l != i: raise ValueError if tz_sign: - td = timedelta(tz_hour, tz_minute, tz_sec) + td = timedelta(tz_hour, tz_minute, tz_sec, microseconds=tz_usec) if tz_sign == "-": td = -td tz = timezone(td) else: tz = None - return datetime(year, month, day, hour, minute, sec, tz) + return datetime(year, month, day, hour, minute, sec, usec*1000, tz) def fromordinal(n): diff --git a/python-stdlib/datetime/test_datetime.py b/python-stdlib/datetime/test_datetime.py index 1a9adf422..db4fb9683 100644 --- a/python-stdlib/datetime/test_datetime.py +++ b/python-stdlib/datetime/test_datetime.py @@ -11,7 +11,7 @@ a = td(hours=7) b = td(minutes=6) c = td(seconds=10) -s = td(seconds=1) +ns = td(nanoseconds=1) t1 = td(2, 3, 4) t2 = td(2, 3, 4) t3 = td(2, 3, 5) @@ -34,28 +34,46 @@ def test_constructor05(self): self.assertEqual(td(minutes=1), td(seconds=60)) def test_constructor06(self): - self.assertEqual(td(weeks=1.0 / 7), td(days=1)) + self.assertEqual(td(seconds=1), td(milliseconds=1000)) def test_constructor07(self): - self.assertEqual(td(days=1.0 / 24), td(hours=1)) + self.assertEqual(td(milliseconds=1), td(microseconds=1000)) def test_constructor08(self): - self.assertEqual(td(hours=1.0 / 60), td(minutes=1)) + self.assertEqual(td(microseconds=1), td(nanoseconds=1000)) def test_constructor09(self): + self.assertEqual(td(weeks=1.0 / 7), td(days=1)) + + def test_constructor10(self): + self.assertEqual(td(days=1.0 / 24), td(hours=1)) + + def test_constructor11(self): + self.assertEqual(td(hours=1.0 / 60), td(minutes=1)) + + def test_constructor12(self): self.assertEqual(td(minutes=1.0 / 60), td(seconds=1)) + def test_constructor13(self): + self.assertEqual(td(seconds=0.001), td(milliseconds=1)) + + def test_constructor14(self): + self.assertEqual(td(milliseconds=0.001), td(microseconds=1)) + + def test_constructor15(self): + self.assertEqual(td(microseconds=0.001), td(nanoseconds=1)) + def test_constant01(self): - self.assertTrue(td(0, 0, 0, 365*td.MINYEAR).total_seconds() >= -2**31) + self.assertTrue(td(0, 0, 0, 365*td.MINYEAR).total_seconds() >= -2**63 / 10**9) def test_constant02(self): - self.assertFalse(td(0, 0, 0, 365*(td.MINYEAR - 1)).total_seconds() >= -2**31) + self.assertFalse(td(0, 0, 0, 365*(td.MINYEAR - 1)).total_seconds() >= -2**63 / 10**9) def test_constant03(self): - self.assertTrue(td(23, 59, 59, 365*td.MAXYEAR).total_seconds() <= 2**31 - 1) + self.assertTrue(td(23, 59, 59, 365*td.MAXYEAR).total_seconds() <= (2**63 - 1) / 10**9) def test_constant04(self): - self.assertFalse(td(23, 59, 59, 365*(td.MAXYEAR + 1)).total_seconds() <= 2**31 - 1) + self.assertFalse(td(23, 59, 59, 365*(td.MAXYEAR + 1)).total_seconds() <= (2**63 - 1) /10**9) def test_computation01(self): self.assertEqual(a + b + c, td(7, 6, 10)) @@ -166,54 +184,56 @@ def test_computation36(self): self.assertEqual(a / 3600, td(0, 0, 7)) def test_computation37(self): - self.assertEqual((3 * s) * 0.5, 2 * s) + self.assertEqual((3 * ns) * 0.5, 2 * ns) def test_computation38(self): - self.assertEqual((5 * s) * 0.5, 2 * s) + self.assertEqual((5 * ns) * 0.5, 2 * ns) def test_computation39(self): - self.assertEqual(0.5 * (3 * s), 2 * s) + self.assertEqual(0.5 * (3 * ns), 2 * ns) def test_computation40(self): - self.assertEqual(0.5 * (5 * s), 2 * s) + self.assertEqual(0.5 * (5 * ns), 2 * ns) def test_computation41(self): - self.assertEqual((-3 * s) * 0.5, -2 * s) + self.assertEqual((-3 * ns) * 0.5, -2 * ns) def test_computation42(self): - self.assertEqual((-5 * s) * 0.5, -2 * s) + self.assertEqual((-5 * ns) * 0.5, -2 * ns) def test_computation43(self): - self.assertEqual((3 * s) / 2, 2 * s) + self.assertEqual((3 * ns) / 2, 2 * ns) def test_computation44(self): - self.assertEqual((5 * s) / 2, 2 * s) + self.assertEqual((5 * ns) / 2, 2 * ns) def test_computation45(self): - self.assertEqual((-3 * s) / 2.0, -2 * s) + self.assertEqual((-3 * ns) / 2.0, -2 * ns) def test_computation46(self): - self.assertEqual((-5 * s) / 2.0, -2 * s) + self.assertEqual((-5 * ns) / 2.0, -2 * ns) def test_computation47(self): - self.assertEqual((3 * s) / -2, -2 * s) + self.assertEqual((3 * ns) / -2, -2 * ns) def test_computation48(self): - self.assertEqual((5 * s) / -2, -2 * s) + self.assertEqual((5 * ns) / -2, -2 * ns) def test_computation49(self): - self.assertEqual((3 * s) / -2.0, -2 * s) + self.assertEqual((3 * ns) / -2.0, -2 * ns) def test_computation50(self): - self.assertEqual((5 * s) / -2.0, -2 * s) + self.assertEqual((5 * ns) / -2.0, -2 * ns) def test_computation51(self): for i in range(-10, 10): - self.assertEqual((i * s / 3) // s, round(i / 3)) + with self.subTest(i=i): + self.assertEqual((i * ns / 3) // ns, round(i / 3)) def test_computation52(self): for i in range(-10, 10): - self.assertEqual((i * s / -3) // s, round(i / -3)) + with self.subTest(i=i): + self.assertEqual((i * ns / -3) // ns, round(i / -3)) def test_total_seconds(self): d = td(days=365) @@ -303,13 +323,13 @@ def test_str06(self): self.assertEqual(str(td(2, 3, 4)), "02:03:04") def test_repr01(self): - self.assertEqual(repr(td(1)), "datetime.timedelta(seconds=%d)" % (1*3600)) + self.assertEqual(repr(td(1)), "datetime.timedelta(seconds={})".format(1*3600.0)) def test_repr02(self): - self.assertEqual(repr(td(10, 2)), "datetime.timedelta(seconds=%d)" % (10*3600 + 2*60)) + self.assertEqual(repr(td(10, 2)), "datetime.timedelta(seconds={})".format(10*3600 + 2*60.0)) def test_repr03(self): - self.assertEqual(repr(td(-10, 2, 40)), "datetime.timedelta(seconds=%d)" % (-10*3600 + 2*60 + 40)) + self.assertEqual(repr(td(-10, 2, 40)), "datetime.timedelta(seconds={})".format(-10*3600 + 2*60 + 40.0)) def test_bool01(self): self.assertTrue(td(1)) @@ -375,7 +395,7 @@ def tzname(self, dt): def isdst(self, dt): if dt is None: return False - year, month, day, hour, minute, second, tz = dt.tuple() + year, month, day, hour, minute, second, nanosecond, tz = dt.tuple() if not 2000 <= year < 2100: raise ValueError if 3 < month < 10: @@ -435,7 +455,7 @@ def test_tzname02(self): d1 = dt(2002, 1, 31) d2 = dt(1956, 1, 31) -d3 = dt(2002, 3, 1, 12, 59, 59, tz2) +d3 = dt(2002, 3, 1, 12, 59, 59, 0, tz2) d4 = dt(2002, 3, 2, 17, 6) d5 = dt(2002, 1, 31) @@ -447,23 +467,25 @@ class TestTimeZone(unittest.TestCase): def test_constructor01(self): d = dt(2002, 3, 1, 12, 0) - year, month, day, hour, minute, second, tz = d.tuple() + year, month, day, hour, minute, second, nanosecond, tz = d.tuple() self.assertEqual(year, 2002) self.assertEqual(month, 3) self.assertEqual(day, 1) self.assertEqual(hour, 12) self.assertEqual(minute, 0) self.assertEqual(second, 0) + self.assertEqual(nanosecond, 0) self.assertEqual(tz, None) def test_constructor02(self): - year, month, day, hour, minute, second, tz = d3.tuple() + year, month, day, hour, minute, second, nanosecond, tz = d3.tuple() self.assertEqual(year, 2002) self.assertEqual(month, 3) self.assertEqual(day, 1) self.assertEqual(hour, 12) self.assertEqual(minute, 59) self.assertEqual(second, 59) + self.assertEqual(nanosecond, 0) self.assertEqual(tz, tz2) def test_constructor03(self): @@ -664,7 +686,7 @@ def test_compare18(self): self.assertFalse(d5 <= d2) def test_astimezone01(self): - self.assertEqual(d3.astimezone(tz.utc), dt(2002, 3, 1, 11, 59, 59, tz.utc)) + self.assertEqual(d3.astimezone(tz.utc), dt(2002, 3, 1, 11, 59, 59, 0, tz.utc)) def test_isoformat01(self): self.assertEqual(d3.isoformat(), "2002-03-01T12:59:59+01:00") From 267c1532f2852dd9a847cef08feef73bcc97b315 Mon Sep 17 00:00:00 2001 From: Lorenzo Cappelletti Date: Mon, 1 Nov 2021 23:46:00 +0100 Subject: [PATCH 09/14] python-stdlib/datetime: add more tests. Signed-off-by: Lorenzo Cappelletti --- python-stdlib/datetime/test_datetime.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/python-stdlib/datetime/test_datetime.py b/python-stdlib/datetime/test_datetime.py index db4fb9683..8431f774a 100644 --- a/python-stdlib/datetime/test_datetime.py +++ b/python-stdlib/datetime/test_datetime.py @@ -1,3 +1,4 @@ +# See https://github.com/python/cpython/blob/3.9/Lib/test/datetimetester.py import unittest from datetime import\ timedelta as td,\ @@ -685,6 +686,21 @@ def test_compare17(self): def test_compare18(self): self.assertFalse(d5 <= d2) + def test_resolution01(self): + self.assertIsInstance(td.min, td) + + def test_resolution02(self): + self.assertIsInstance(td.max, td) + + def test_resolution03(self): + self.assertIsInstance(td.resolution, td) + + def test_resolution04(self): + self.assertTrue(td.max > td.min) + + def test_resolution04(self): + self.assertEqual(td.resolution, td(nanoseconds=1)) + def test_astimezone01(self): self.assertEqual(d3.astimezone(tz.utc), dt(2002, 3, 1, 11, 59, 59, 0, tz.utc)) From 8691500d903743d2c164c75b56aa925844bb7b60 Mon Sep 17 00:00:00 2001 From: Lorenzo Cappelletti Date: Mon, 1 Nov 2021 23:48:10 +0100 Subject: [PATCH 10/14] python-stdlib/datetime: run /tools/codeformat.py. Signed-off-by: Lorenzo Cappelletti --- python-stdlib/datetime/datetime.py | 57 ++++++++++-- python-stdlib/datetime/test_datetime.py | 116 +++++++++++++----------- 2 files changed, 111 insertions(+), 62 deletions(-) diff --git a/python-stdlib/datetime/datetime.py b/python-stdlib/datetime/datetime.py index bae2b2072..fa3fbea4b 100644 --- a/python-stdlib/datetime/datetime.py +++ b/python-stdlib/datetime/datetime.py @@ -2,6 +2,7 @@ __version__ = "2.0.0" + def _is_leap(year): return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) @@ -14,6 +15,7 @@ def _days_before_year(year): _DIM = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) + def _days_in_month(year, month): # year, month -> number of days in that month in that year. if month == 2 and _is_leap(year): @@ -23,6 +25,7 @@ def _days_in_month(year, month): _DBM = (0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334) + def _days_before_month(year, month): # year, month -> number of days in year preceding first day of month. return _DBM[month] + (month > 2 and _is_leap(year)) @@ -54,11 +57,32 @@ def _ord2ymd(n): class timedelta: - MINYEAR = -292 # timedelta( 0, 0, 0, -365*584).total_seconds() > -2**63 / 10**9 - MAXYEAR = 292 # timedelta(23, 59, 59, 365*584).total_seconds() < (2**63 - 1) / 10**9 - - def __init__(self, hours=0, minutes=0, seconds=0, days=0, weeks=0, milliseconds=0, microseconds=0, nanoseconds=0): - self._ns = round(((((((weeks * 7 + days) * 24 + hours) * 60 + minutes) * 60 + seconds) * 1000 + milliseconds) * 1000 + microseconds) * 1000 + nanoseconds) + MINYEAR = -292 # timedelta( 0, 0, 0, -365*584).total_seconds() > -2**63 / 10**9 + MAXYEAR = 292 # timedelta(23, 59, 59, 365*584).total_seconds() < (2**63 - 1) / 10**9 + + def __init__( + self, + hours=0, + minutes=0, + seconds=0, + days=0, + weeks=0, + milliseconds=0, + microseconds=0, + nanoseconds=0, + ): + self._ns = round( + ( + ( + ((((weeks * 7 + days) * 24 + hours) * 60 + minutes) * 60 + seconds) * 1000 + + milliseconds + ) + * 1000 + + microseconds + ) + * 1000 + + nanoseconds + ) def __repr__(self): return "datetime.timedelta(seconds={})".format(self.total_seconds()) @@ -234,7 +258,15 @@ def toordinal(self): return self._ord def replace( - self, year=None, month=None, day=None, hour=None, minute=None, second=None, nanosecond=None, tzinfo=True + self, + year=None, + month=None, + day=None, + hour=None, + minute=None, + second=None, + nanosecond=None, + tzinfo=True, ): year_, month_, day_, hour_, minute_, second_, nanosec_, tz_ = self.tuple() if year is None: @@ -268,8 +300,11 @@ def isoformat(self, sep="T"): return dt + self._tz.isoformat(self, utc=False) def __repr__(self): - return "datetime.datetime(day=%d, nanosecond=%d, tzinfo=%s)" \ - % (self._ord, self._time._ns, repr(self._tz)) + return "datetime.datetime(day=%d, nanosecond=%d, tzinfo=%s)" % ( + self._ord, + self._time._ns, + repr(self._tz), + ) def __str__(self): return self.isoformat(" ") @@ -321,7 +356,9 @@ def __add__(self, other): days += 1 time += timedelta(days=days) days = -days - year, month, day, hour, minute, second, nanosec, tz = self._tuple(self._ord + days, time, self._tz)[:8] + year, month, day, hour, minute, second, nanosec, tz = self._tuple( + self._ord + days, time, self._tz + )[:8] return datetime(year, month, day, hour, minute, second, nanosec, tz) def __sub__(self, other): @@ -443,7 +480,7 @@ def fromisoformat(s): tz = timezone(td) else: tz = None - return datetime(year, month, day, hour, minute, sec, usec*1000, tz) + return datetime(year, month, day, hour, minute, sec, usec * 1000, tz) def fromordinal(n): diff --git a/python-stdlib/datetime/test_datetime.py b/python-stdlib/datetime/test_datetime.py index 8431f774a..d505deb51 100644 --- a/python-stdlib/datetime/test_datetime.py +++ b/python-stdlib/datetime/test_datetime.py @@ -1,10 +1,6 @@ # See https://github.com/python/cpython/blob/3.9/Lib/test/datetimetester.py import unittest -from datetime import\ - timedelta as td,\ - timezone as tz,\ - datetime as dt,\ - fromisoformat as fif +from datetime import timedelta as td, timezone as tz, datetime as dt, fromisoformat as fif ### timedelta ################################################################ @@ -17,8 +13,8 @@ t2 = td(2, 3, 4) t3 = td(2, 3, 5) -class TestTimeDelta(unittest.TestCase): +class TestTimeDelta(unittest.TestCase): def test_constructor01(self): self.assertEqual(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0)) @@ -65,16 +61,22 @@ def test_constructor15(self): self.assertEqual(td(microseconds=0.001), td(nanoseconds=1)) def test_constant01(self): - self.assertTrue(td(0, 0, 0, 365*td.MINYEAR).total_seconds() >= -2**63 / 10**9) + self.assertTrue(td(0, 0, 0, 365 * td.MINYEAR).total_seconds() >= -(2 ** 63) / 10 ** 9) def test_constant02(self): - self.assertFalse(td(0, 0, 0, 365*(td.MINYEAR - 1)).total_seconds() >= -2**63 / 10**9) + self.assertFalse( + td(0, 0, 0, 365 * (td.MINYEAR - 1)).total_seconds() >= -(2 ** 63) / 10 ** 9 + ) def test_constant03(self): - self.assertTrue(td(23, 59, 59, 365*td.MAXYEAR).total_seconds() <= (2**63 - 1) / 10**9) + self.assertTrue( + td(23, 59, 59, 365 * td.MAXYEAR).total_seconds() <= (2 ** 63 - 1) / 10 ** 9 + ) def test_constant04(self): - self.assertFalse(td(23, 59, 59, 365*(td.MAXYEAR + 1)).total_seconds() <= (2**63 - 1) /10**9) + self.assertFalse( + td(23, 59, 59, 365 * (td.MAXYEAR + 1)).total_seconds() <= (2 ** 63 - 1) / 10 ** 9 + ) def test_computation01(self): self.assertEqual(a + b + c, td(7, 6, 10)) @@ -270,40 +272,40 @@ def test_compare06(self): self.assertTrue(not t1 > t2) def test_compare07(self): - self.assertTrue(t1 < t3) + self.assertTrue(t1 < t3) def test_compare08(self): - self.assertTrue(t3 > t1) + self.assertTrue(t3 > t1) def test_compare09(self): - self.assertTrue(t1 <= t3) + self.assertTrue(t1 <= t3) def test_compare10(self): - self.assertTrue(t3 >= t1) + self.assertTrue(t3 >= t1) def test_compare11(self): - self.assertTrue(t1 != t3) + self.assertTrue(t1 != t3) def test_compare12(self): - self.assertTrue(t3 != t1) + self.assertTrue(t3 != t1) def test_compare13(self): - self.assertTrue(not t1 == t3) + self.assertTrue(not t1 == t3) def test_compare14(self): - self.assertTrue(not t3 == t1) + self.assertTrue(not t3 == t1) def test_compare15(self): - self.assertTrue(not t1 > t3) + self.assertTrue(not t1 > t3) def test_compare16(self): - self.assertTrue(not t3 < t1) + self.assertTrue(not t3 < t1) def test_compare17(self): - self.assertTrue(not t1 >= t3) + self.assertTrue(not t1 >= t3) def test_compare18(self): - self.assertTrue(not t3 <= t1) + self.assertTrue(not t3 <= t1) def test_str01(self): self.assertEqual(str(td(days=1)), "1d 00:00:00") @@ -324,13 +326,18 @@ def test_str06(self): self.assertEqual(str(td(2, 3, 4)), "02:03:04") def test_repr01(self): - self.assertEqual(repr(td(1)), "datetime.timedelta(seconds={})".format(1*3600.0)) + self.assertEqual(repr(td(1)), "datetime.timedelta(seconds={})".format(1 * 3600.0)) def test_repr02(self): - self.assertEqual(repr(td(10, 2)), "datetime.timedelta(seconds={})".format(10*3600 + 2*60.0)) + self.assertEqual( + repr(td(10, 2)), "datetime.timedelta(seconds={})".format(10 * 3600 + 2 * 60.0) + ) def test_repr03(self): - self.assertEqual(repr(td(-10, 2, 40)), "datetime.timedelta(seconds={})".format(-10*3600 + 2*60 + 40.0)) + self.assertEqual( + repr(td(-10, 2, 40)), + "datetime.timedelta(seconds={})".format(-10 * 3600 + 2 * 60 + 40.0), + ) def test_bool01(self): self.assertTrue(td(1)) @@ -381,6 +388,7 @@ def test_divmod02(self): ### timezone ################################################################# + class Cet(tz): # Central European Time (see https://en.wikipedia.org/wiki/Summer_time_in_Europe) @@ -391,7 +399,7 @@ def dst(self, dt): return td(hours=1) if self.isdst(dt) else td(0) def tzname(self, dt): - return 'CEST' if self.isdst(dt) else 'CET' + return "CEST" if self.isdst(dt) else "CET" def isdst(self, dt): if dt is None: @@ -402,54 +410,59 @@ def isdst(self, dt): if 3 < month < 10: return True if month == 3: - beg = 31 - (5*year//4 + 4) % 7 # last Sunday of March - if day < beg: return False - if day > beg: return True + beg = 31 - (5 * year // 4 + 4) % 7 # last Sunday of March + if day < beg: + return False + if day > beg: + return True return hour >= 3 if month == 10: - end = 31 - (5*year//4 + 1) % 7 # last Sunday of October - if day < end: return True - if day > end: return False + end = 31 - (5 * year // 4 + 1) % 7 # last Sunday of October + if day < end: + return True + if day > end: + return False return hour < 3 return False + tz1 = tz(td(hours=-1)) tz2 = Cet() -class TestTimeZone(unittest.TestCase): +class TestTimeZone(unittest.TestCase): def test_constructor01(self): - self.assertEqual(str(tz1), 'UTC-01:00') + self.assertEqual(str(tz1), "UTC-01:00") def test_constructor02(self): - self.assertEqual(str(tz2), 'CET') + self.assertEqual(str(tz2), "CET") def test_utcoffset01(self): - self.assertEqual(str(tz2.utcoffset(None)), '01:00:00') + self.assertEqual(str(tz2.utcoffset(None)), "01:00:00") def test_utcoffset02(self): - self.assertEqual(str(tz2.utcoffset(dt(2010, 3, 27, 12))), '01:00:00') + self.assertEqual(str(tz2.utcoffset(dt(2010, 3, 27, 12))), "01:00:00") def test_utcoffset03(self): - self.assertEqual(str(tz2.utcoffset(dt(2010, 3, 28, 12))), '02:00:00') + self.assertEqual(str(tz2.utcoffset(dt(2010, 3, 28, 12))), "02:00:00") def test_utcoffset04(self): - self.assertEqual(str(tz2.utcoffset(dt(2010, 10, 30, 12))), '02:00:00') + self.assertEqual(str(tz2.utcoffset(dt(2010, 10, 30, 12))), "02:00:00") def test_utcoffset05(self): - self.assertEqual(str(tz2.utcoffset(dt(2010, 10, 31, 12))), '01:00:00') + self.assertEqual(str(tz2.utcoffset(dt(2010, 10, 31, 12))), "01:00:00") def test_isoformat01(self): - self.assertEqual(tz2.isoformat(dt(2011, 1, 1)), 'UTC+01:00') + self.assertEqual(tz2.isoformat(dt(2011, 1, 1)), "UTC+01:00") def test_isoformat02(self): - self.assertEqual(tz2.isoformat(dt(2011, 8, 1)), 'UTC+02:00') + self.assertEqual(tz2.isoformat(dt(2011, 8, 1)), "UTC+02:00") def test_tzname01(self): - self.assertEqual(tz2.tzname(dt(2011, 1, 1)), 'CET') + self.assertEqual(tz2.tzname(dt(2011, 1, 1)), "CET") def test_tzname02(self): - self.assertEqual(tz2.tzname(dt(2011, 8, 1)), 'CEST') + self.assertEqual(tz2.tzname(dt(2011, 8, 1)), "CEST") ### datetime ################################################################# @@ -464,8 +477,8 @@ def test_tzname02(self): day = td(days=1) week = td(weeks=1) -class TestTimeZone(unittest.TestCase): +class TestTimeZone(unittest.TestCase): def test_constructor01(self): d = dt(2002, 3, 1, 12, 0) year, month, day, hour, minute, second, nanosecond, tz = d.tuple() @@ -551,8 +564,7 @@ def test_constructor22(self): def test_computation01(self): d = d1 - d2 - self.assertEqual(d.total_seconds(), - (46 * 365 + len(range(1956, 2002, 4)))*24*60*60) + self.assertEqual(d.total_seconds(), (46 * 365 + len(range(1956, 2002, 4))) * 24 * 60 * 60) def test_computation02(self): self.assertEqual(d4 + hour, dt(2002, 3, 2, 18, 6)) @@ -561,7 +573,7 @@ def test_computation02(self): self.assertEqual(hour + d4, dt(2002, 3, 2, 18, 6)) def test_computation03(self): - self.assertEqual(d4 + 10*hour, dt(2002, 3, 3, 3, 6)) + self.assertEqual(d4 + 10 * hour, dt(2002, 3, 3, 3, 6)) def test_computation04(self): self.assertEqual(d4 - hour, dt(2002, 3, 2, 16, 6)) @@ -573,7 +585,7 @@ def test_computation06(self): self.assertEqual(d4 - hour, d4 + -hour) def test_computation07(self): - self.assertEqual(d4 - 20*hour, dt(2002, 3, 1, 21, 6)) + self.assertEqual(d4 - 20 * hour, dt(2002, 3, 1, 21, 6)) def test_computation08(self): self.assertEqual(d4 + day, dt(2002, 3, 3, 17, 6)) @@ -588,10 +600,10 @@ def test_computation11(self): self.assertEqual(d4 - week, dt(2002, 2, 23, 17, 6)) def test_computation12(self): - self.assertEqual(d4 + 52*week, dt(2003, 3, 1, 17, 6)) + self.assertEqual(d4 + 52 * week, dt(2003, 3, 1, 17, 6)) def test_computation13(self): - self.assertEqual(d4 - 52*week, dt(2001, 3, 3, 17, 6)) + self.assertEqual(d4 - 52 * week, dt(2001, 3, 3, 17, 6)) def test_computation14(self): self.assertEqual((d4 + week) - d4, week) @@ -738,5 +750,5 @@ def test_fromisoformat05(self): self.assertEqual(str(fif("1975-08-10 23:30:12+01:00")), "1975-08-10 23:30:12+01:00") -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() From 37b5bf5b49111420322b8b6074713a217bc8a674 Mon Sep 17 00:00:00 2001 From: Lorenzo Cappelletti Date: Sat, 6 Nov 2021 07:16:21 +0100 Subject: [PATCH 11/14] python-stdlib/datetime: Fix tests. Signed-off-by: Lorenzo Cappelletti --- python-stdlib/datetime/test_datetime.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python-stdlib/datetime/test_datetime.py b/python-stdlib/datetime/test_datetime.py index d505deb51..18e8d566d 100644 --- a/python-stdlib/datetime/test_datetime.py +++ b/python-stdlib/datetime/test_datetime.py @@ -230,12 +230,12 @@ def test_computation50(self): def test_computation51(self): for i in range(-10, 10): - with self.subTest(i=i): + # with self.subTest(i=i): not supported by Micropython self.assertEqual((i * ns / 3) // ns, round(i / 3)) def test_computation52(self): for i in range(-10, 10): - with self.subTest(i=i): + # with self.subTest(i=i): not supported by Micropython self.assertEqual((i * ns / -3) // ns, round(i / -3)) def test_total_seconds(self): @@ -478,7 +478,7 @@ def test_tzname02(self): week = td(weeks=1) -class TestTimeZone(unittest.TestCase): +class TestDateTime(unittest.TestCase): def test_constructor01(self): d = dt(2002, 3, 1, 12, 0) year, month, day, hour, minute, second, nanosecond, tz = d.tuple() From d45c2453e51f5339c8ccd24ceee98ba98dd21953 Mon Sep 17 00:00:00 2001 From: Lorenzo Cappelletti Date: Sat, 6 Nov 2021 07:19:58 +0100 Subject: [PATCH 12/14] python-stdlib/datetime: Fix __add__(-day). When adding a negative integral number of days, time's reminder was lost and the result was one day less than expected. Signed-off-by: Lorenzo Cappelletti --- python-stdlib/datetime/datetime.py | 12 +++++++----- python-stdlib/datetime/test_datetime.py | 3 +++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/python-stdlib/datetime/datetime.py b/python-stdlib/datetime/datetime.py index fa3fbea4b..17a130f50 100644 --- a/python-stdlib/datetime/datetime.py +++ b/python-stdlib/datetime/datetime.py @@ -353,12 +353,14 @@ def __add__(self, other): time = self._time + other sign, days, hour, minute, second, nanosec = time.tuple() if sign == "-": - days += 1 - time += timedelta(days=days) + if hour or minute or second or nanosec: + # -10d -20:30:40 -> -11d 03:29:20 + days += 1 + time += timedelta(days=days) days = -days year, month, day, hour, minute, second, nanosec, tz = self._tuple( self._ord + days, time, self._tz - )[:8] + ) return datetime(year, month, day, hour, minute, second, nanosec, tz) def __sub__(self, other): @@ -396,13 +398,13 @@ def timeisoformat(self): return self.isoformat()[11:19] def tuple(self): - return self._tuple(self._ord, self._time, self._tz)[:-2] + return self._tuple(self._ord, self._time, self._tz) def _tuple(self, ordinal, time, tz): # Split a datetime to its components. year, month, day = _ord2ymd(ordinal) sign, days, hour, minute, second, nanosec = time.tuple() - return year, month, day, hour, minute, second, nanosec, tz, days, sign + return year, month, day, hour, minute, second, nanosec, tz datetime.EPOCH = datetime(2000, 1, 1, tzinfo=timezone.utc) diff --git a/python-stdlib/datetime/test_datetime.py b/python-stdlib/datetime/test_datetime.py index 18e8d566d..afa794de2 100644 --- a/python-stdlib/datetime/test_datetime.py +++ b/python-stdlib/datetime/test_datetime.py @@ -644,6 +644,9 @@ def test_computation25(self): def test_computation26(self): self.assertEqual(d4 - (week + day + hour), (((d4 - week) - day) - hour)) + def test_computation27(self): + self.assertEqual(d5 + -day, dt(2002, 1, 30)) + def test_compare01(self): self.assertEqual(d1, d5) From 702941805bcf554016f1e47634bce85232f225cb Mon Sep 17 00:00:00 2001 From: Lorenzo Cappelletti Date: Sat, 6 Nov 2021 10:29:48 +0100 Subject: [PATCH 13/14] python-stdlib/datetime: Fix datetime constructor. Signed-off-by: Lorenzo Cappelletti --- python-stdlib/datetime/datetime.py | 2 +- python-stdlib/datetime/test_datetime.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/python-stdlib/datetime/datetime.py b/python-stdlib/datetime/datetime.py index 17a130f50..09c9e5392 100644 --- a/python-stdlib/datetime/datetime.py +++ b/python-stdlib/datetime/datetime.py @@ -241,7 +241,7 @@ def __init__(self, year, month, day, hour=0, minute=0, second=0, nanosecond=0, t self._ord = _ymd2ord(year, month, day) else: raise ValueError - self._time = timedelta(hour, minute, second, nanosecond) + self._time = timedelta(hour, minute, second, nanoseconds=nanosecond) self._tz = tzinfo @property diff --git a/python-stdlib/datetime/test_datetime.py b/python-stdlib/datetime/test_datetime.py index afa794de2..7244973c9 100644 --- a/python-stdlib/datetime/test_datetime.py +++ b/python-stdlib/datetime/test_datetime.py @@ -469,7 +469,7 @@ def test_tzname02(self): d1 = dt(2002, 1, 31) d2 = dt(1956, 1, 31) -d3 = dt(2002, 3, 1, 12, 59, 59, 0, tz2) +d3 = dt(2002, 3, 1, 12, 59, 59, 100, tz2) d4 = dt(2002, 3, 2, 17, 6) d5 = dt(2002, 1, 31) @@ -499,7 +499,7 @@ def test_constructor02(self): self.assertEqual(hour, 12) self.assertEqual(minute, 59) self.assertEqual(second, 59) - self.assertEqual(nanosecond, 0) + self.assertEqual(nanosecond, 100) self.assertEqual(tz, tz2) def test_constructor03(self): @@ -717,7 +717,7 @@ def test_resolution04(self): self.assertEqual(td.resolution, td(nanoseconds=1)) def test_astimezone01(self): - self.assertEqual(d3.astimezone(tz.utc), dt(2002, 3, 1, 11, 59, 59, 0, tz.utc)) + self.assertEqual(d3.astimezone(tz.utc), dt(2002, 3, 1, 11, 59, 59, 100, tz.utc)) def test_isoformat01(self): self.assertEqual(d3.isoformat(), "2002-03-01T12:59:59+01:00") From 9d1a5d35e4f4be68022c929a5fe19382456b80ea Mon Sep 17 00:00:00 2001 From: Lorenzo Cappelletti Date: Wed, 6 Oct 2021 12:47:39 +0200 Subject: [PATCH 14/14] python-ecosys/suntime: add new module Signed-off-by: Lorenzo Cappelletti --- python-ecosys/suntime/example.py | 205 +++++++++++++++++++++++++ python-ecosys/suntime/metadata.txt | 4 + python-ecosys/suntime/setup.py | 24 +++ python-ecosys/suntime/suntime.py | 137 +++++++++++++++++ python-ecosys/suntime/test_suntime.py | 209 ++++++++++++++++++++++++++ 5 files changed, 579 insertions(+) create mode 100644 python-ecosys/suntime/example.py create mode 100644 python-ecosys/suntime/metadata.txt create mode 100644 python-ecosys/suntime/setup.py create mode 100644 python-ecosys/suntime/suntime.py create mode 100644 python-ecosys/suntime/test_suntime.py diff --git a/python-ecosys/suntime/example.py b/python-ecosys/suntime/example.py new file mode 100644 index 000000000..de6afd5c8 --- /dev/null +++ b/python-ecosys/suntime/example.py @@ -0,0 +1,205 @@ +# example.py + +import datetime, suntime, time + +class Cet(datetime.timezone): + def __init__(self): + super().__init__(datetime.timedelta(hours=1), "CET") + + def dst(self, dt): + return datetime.timedelta(hours=1) if self.isdst(dt) else datetime.timedelta(0) + + def tzname(self, dt): + return 'CEST' if self.isdst(dt) else 'CET' + + def isdst(self, dt): + if dt is None: + return False + year, month, day, hour, minute, second, tz = dt.tuple() + if not 2000 <= year < 2100: + raise ValueError + if 3 < month < 10: + return True + if month == 3: + beg = 31 - (5*year//4 + 4) % 7 # last Sunday of March + if day < beg: return False + if day > beg: return True + return hour >= 3 + if month == 10: + end = 31 - (5*year//4 + 1) % 7 # last Sunday of October + if day < end: return True + if day > end: return False + return hour < 3 + return False + +# initialization +CET = Cet() +Rome = suntime.Sundatetime(42.5966460, 12.4360233) +Rome.calc_sunrise_sunset(datetime.datetime(2000, 1, 1, tzinfo=CET)) + +# main loop (every minute or more) +now = datetime.datetime(*time.localtime()[:5], tzinfo=CET) +if (now.date() > Rome.sunset.date()): + Rome.calc_sunrise_sunset(now) +print (now, Rome.is_daytime(now)) + + +####################################################################### + +# place: latitude longitude +pl1 = ( 42.5966460, 12.4360233) # Rome +pl2 = ( 51.1627938,-122.9593616) # Vancouver +pl3 = (-33.9252192, 18.4240762) # CapeTown +pl4 = ( 55.1574890, 82.8547661) # Novosibirsk +pl5 = ( 78.6560170, 16.3447384) # Pyramiden +pl6 = pl5 +pl7 = (-77.7817838, 166.4561470) # McMurdo +pl8 = pl7 + +# date: YY MM DD sunrise sunset +dt1 = (2000, 1, 1) # 7:37 16:49 - https://www.timeanddate.com/sun/italy/rome?month=1&year=2000 +dt2 = (2014, 10, 3) # 7:15 18:46 - https://www.timeanddate.com/sun/canada/vancouver?month=10&year=2014 +dt3 = (2016, 12, 21) # 5:32 19:57 - https://www.timeanddate.com/sun/south-africa/cape-town?month=12&year=2016 +dt4 = (2021, 4, 24) # 6:04 20:50 - https://www.timeanddate.com/sun/russia/novosibirsk?month=4&year=2021 +dt5 = (2040, 8, 25) # up all day - https://www.timeanddate.com/sun/@2729216?month=8&year=2033 +dt6 = (2040, 8, 26) # 00:09 + # 1:45 23:41 - https://www.timeanddate.com/sun/@2729216?month=8&year=2040 +dt7 = (2033, 8, 10) # down all day - https://www.timeanddate.com/sun/antarctica/mcmurdo?month=8&year=2033 +dt8 = (2033, 10, 21) # 3:00 24:13 - https://www.timeanddate.com/sun/antarctica/mcmurdo?month=10&year=2033 + +# timezone offsets and DSTs (in hours) +tz1 = ( 1, 0) +tz2 = (-8, 1) +tz3 = ( 2, 0) +tz4 = ( 0, 0) # wrong; it generates negative hour because actual timezone is (7, 0) +tz5 = ( 1, 1) +tz6 = ( 1, 1) +tz7 = (13,-1) +tz8 = (13, 0) + + +####################################################################### + +# if `datetime` module is available +from suntime import Sundatetime +from datetime import datetime, timedelta, timezone + +class Tz(timezone): + def __init__(self, hours, dst=0): + super().__init__(timedelta(hours=hours)) + self._dst = dst + + def dst(self, dt): + return timedelta(hours=self._dst) if self.isdst(dt) else timedelta(0) + + def isdst(self, dt): + return self._dst != 0 + +now = datetime(*dt1, tzinfo=Tz(*tz1)) +sd1 = Sundatetime(*pl1) +sd1.calc_sunrise_sunset(now) +print('Rome:', now) +print('>', sd1.sunrise) # 2000-01-01 07:40:00+01:00 +print('>', sd1.sunset ) # 2000-01-01 16:47:00+01:00 + +now = datetime(*dt2, tzinfo=Tz(*tz2)) +sd2 = Sundatetime(*pl2) +sd2.calc_sunrise_sunset(now) +print('Vancouver:', now) +print('>', sd2.sunrise) # 2014-10-03 07:16:00-08:00 +print('>', sd2.sunset ) # 2014-10-03 18:46:00-08:00 + +now = datetime(*dt3, tzinfo=Tz(*tz3)) +sd3 = Sundatetime(*pl3) +sd3.calc_sunrise_sunset(now) +print('Cape Town:', now) +print('>', sd3.sunrise) # 2016-12-21 05:32:00+02:00 +print('>', sd3.sunset ) # 2016-12-21 19:57:00+02:00 + +now = datetime(*dt4, tzinfo=Tz(*tz4)) +sd4 = Sundatetime(*pl4) +sd4.calc_sunrise_sunset(now) +print('Novosibirsk:', now) +print('>', sd4.sunrise) # 2021-04-23 23:04:00+00:00 +print('>', sd4.sunset ) # 2021-04-24 13:49:00+00:00 + +now = datetime(*dt5, tzinfo=Tz(*tz5)) +sd5 = Sundatetime(*pl5) +sd5.calc_sunrise_sunset(now) +print('Pyramiden:', now) +print('>', sd5.sunrise) # 2040-08-24 12:57:00+02:00 +print('>', sd5.sunset ) # 2040-08-26 12:57:00+02:00 + +now = datetime(*dt6, tzinfo=Tz(*tz6)) +sd6 = Sundatetime(*pl6) +sd6.calc_sunrise_sunset(now) +print('Pyramiden:', now) +print('>', sd6.sunrise) # 2040-08-26 01:35:00+02:00 +print('>', sd6.sunset ) # 2040-08-27 00:18:00+02:00 + +now = datetime(*dt7, tzinfo=Tz(*tz7)) +sd7 = Sundatetime(*pl7) +sd7.calc_sunrise_sunset(now) +print('McMurdo:', now) +print('>', sd7.sunrise) # 2033-08-11 13:00:00+12:00 +print('>', sd7.sunset ) # 2033-08-09 13:00:00+12:00 + +now = datetime(*dt8, tzinfo=Tz(*tz8)) +sd8 = Sundatetime(*pl8) +sd8.calc_sunrise_sunset(now) +print('McMurdo:', now) +print('>', sd8.sunrise) # 2033-10-21 03:06:00+13:00 +print('>', sd8.sunset ) # 2033-10-22 00:12:00+13:00 + + +####################################################################### + +from suntime import Suntime + +st1 = Suntime(*pl1, timezone=tz1[0]*60) +st1.calc_sunrise_sunset(*dt1, dst=tz1[1]*60) +print('Rome:', dt1, tz1) +print('>', divmod(st1.sunrise, 60)) # (7, 40) +print('>', divmod(st1.sunset , 60)) # (16, 47) + +st2 = Suntime(*pl2, timezone=tz2[0]*60) +st2.calc_sunrise_sunset(*dt2, dst=tz2[1]*60) +print('Vancouver:', dt2, tz2) +print('>', divmod(st2.sunrise, 60)) # (7, 16) +print('>', divmod(st2.sunset , 60)) # (18, 46) + +st3 = Suntime(*pl3, timezone=tz3[0]*60) +st3.calc_sunrise_sunset(*dt3, dst=tz3[1]*60) +print('Cape Town:', dt3, tz3) +print('>', divmod(st3.sunrise, 60)) # (5, 32) +print('>', divmod(st3.sunset , 60)) # (19, 57) + +st4 = Suntime(*pl4, timezone=tz4[0]*60) +st4.calc_sunrise_sunset(*dt4, dst=tz4[1]*60) +print('Novosibirsk:', dt4, tz4) +print('>', divmod(st4.sunrise, 60)) # (-1, 4) +print('>', divmod(st4.sunset , 60)) # (13, 49) + +st5 = Suntime(*pl5, timezone=tz5[0]*60) +st5.calc_sunrise_sunset(*dt5, dst=tz5[1]*60) +print('Pyramiden:', dt5, tz5) +print('>', divmod(st5.sunrise, 60)) # (-12, 57) +print('>', divmod(st5.sunset , 60)) # (36, 57) + +st6 = Suntime(*pl6, timezone=tz6[0]*60) +st6.calc_sunrise_sunset(*dt6, dst=tz6[1]*60) +print('Pyramiden:', dt6, tz6) +print('>', divmod(st6.sunrise, 60)) # (1, 35) +print('>', divmod(st6.sunset , 60)) # (24, 18) + +st7 = Suntime(*pl7, timezone=tz7[0]*60) +st7.calc_sunrise_sunset(*dt7, dst=tz7[1]*60) +print('McMurdo:', dt7, tz7) +print('>', divmod(st7.sunrise, 60)) # (37, 0) +print('>', divmod(st7.sunset , 60)) # (-11, 0) + +st8 = Suntime(*pl8, timezone=tz8[0]*60) +st8.calc_sunrise_sunset(*dt8, dst=tz8[1]*60) +print('McMurdo:', dt8, tz8) +print('>', divmod(st8.sunrise, 60)) # (3, 6) +print('>', divmod(st8.sunset , 60)) # (24, 12) diff --git a/python-ecosys/suntime/metadata.txt b/python-ecosys/suntime/metadata.txt new file mode 100644 index 000000000..fbe4a671d --- /dev/null +++ b/python-ecosys/suntime/metadata.txt @@ -0,0 +1,4 @@ +srctype = cpython +type = module +version = 1.0.0 +author = Lorenzo Cappelletti diff --git a/python-ecosys/suntime/setup.py b/python-ecosys/suntime/setup.py new file mode 100644 index 000000000..bdf25a84c --- /dev/null +++ b/python-ecosys/suntime/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-suntime", + version="1.0.0", + description="suntime 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="GPL", + cmdclass={"sdist": sdist_upip.sdist}, + py_modules=["suntime"], +) diff --git a/python-ecosys/suntime/suntime.py b/python-ecosys/suntime/suntime.py new file mode 100644 index 000000000..c426e8919 --- /dev/null +++ b/python-ecosys/suntime/suntime.py @@ -0,0 +1,137 @@ +# suntime.py + +__version__ = "1.0.0" + +#--- Help Functions -------------------------------------------------# + +from math import acos, asin, ceil, cos, degrees as deg, fmod as mod,\ + sqrt, radians as rad, sin + +# https://en.wikipedia.org/wiki/Sunrise_equation +# https://en.wikipedia.org/wiki/Julian_day +# m = round((M - 14)/12) +# JDN = round(1461*(Y + 4800 + m)/4)\ +# + round((367*(M - 2 - 12*m))/12)\ +# - round((3*(round((Y + 4900 + m)/100)))/4)\ +# + D - 32075 +def equation (n, lat, lon, alt): + # n = ceil(Jd - 2451545.0 + 0.0008) + assert(0 <= n < 36525) # days in 21st century + Js = n - lon/360 + M = mod(357.5291 + 0.98560028*Js, 360) + C = 1.9148*sin(rad(M)) + 0.0200*sin(rad(2*M)) + 0.0003*sin(rad(3*M)) + λ = mod(M + C + 180 + 102.9372, 360) + Jt = 2451545.0 + Js + 0.0053*sin(rad(M)) - 0.0069*sin(rad(2*λ)) + sinδ = sin(rad(λ))*sin(rad(23.44)) + cosω0 = (sin(rad(-0.83 - 2.076*sqrt(alt)/60)) - sin(rad(lat))*sinδ)\ + / (cos(rad(lat))*cos(asin(sinδ))) + if cosω0 <= -1.0: + ω0 = 360 + elif cosω0 >= 1.0: + ω0 = -360 + else: + ω0 = deg(acos(cosω0)) + Jr = Jt - ω0/360 + Js = Jt + ω0/360 + return Jr, Js + + +#--- Suntime --------------------------------------------------------# + +# 500 = 499 (non-leap years before 2000) + 1 (Jan 1st 2000) +def day2000(year, month, day): + assert(2000 <= year < 2100) + assert(1 <= month <= 12) + assert(1 <= day <= 31) + MONTH_DAYS = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30) + return (year - 2000)*365\ + + sum(MONTH_DAYS[:month - 1])\ + + (year if month >= 3 else year - 1)//4\ + + day\ + - 500 + +def jdate2time (Jd, n, tz=0): + jtime = Jd - (2451545 + n) + minutes = round(jtime*1440) + 720 + tz + return minutes + +class Suntime: + def __init__(self, latitude, longitude, altitude=0, timezone=0): + self.latitude = latitude + self.longitude = longitude + self.altitude = altitude + self.timezone = timezone + self.sunrise = None + self.sunset = None + + def calc_sunrise_sunset(self, year, month, day, dst=0): + n = day2000(year, month, day) + Jr, Js = equation(n, self.latitude, self.longitude, self.altitude) + tz = self.timezone + dst + self.sunrise = jdate2time(Jr, n, tz) + self.sunset = jdate2time(Js, n, tz) + + def is_daytime (self, minutes): + if self.sunrise is None or self.sunset is None: + return None + if not 0 <= minutes < 1440: + return None + return self.sunrise <= minutes < self.sunset + + def is_nighttime (self, minutes): + daytime = self.is_daytime(minutes) + if daytime is None: + return None + return not daytime + + def is_sunrise (self, minutes): + return self.is_daytime(minutes) and minutes == self.sunrise + + def is_sunset (self, minutes): + return self.is_nighttime(minutes) and minutes == self.sunset + + +#--- Sundatetime ----------------------------------------------------# + +import datetime as dt + +def jdate2datetime(Jd, date): + days = date.toordinal() + n = days - dt.datetime(2000, 1, 1).toordinal() + dt_ = dt.datetime(0, 0, days, minute=jdate2time(Jd, n), tzinfo=dt.timezone.utc) + if date.tzinfo: + dt_ = dt_.astimezone(date.tzinfo) + return dt_ + +class Sundatetime: + def __init__(self, latitude, longitude, altitude=0): + self.latitude = latitude + self.longitude = longitude + self.altitude = altitude + self.sunrise = None + self.sunset = None + + def calc_sunrise_sunset(self, date): + n = date.toordinal() - dt.datetime(2000, 1, 1).toordinal() + Jr, Js = equation(n, self.latitude, self.longitude, self.altitude) + self.sunrise = jdate2datetime(Jr, date) + self.sunset = jdate2datetime(Js, date) + + def is_daytime (self, now): + if self.sunrise is None or self.sunset is None: + return None + if self.sunrise >= self.sunset: + return None + return self.sunrise <= now < self.sunset + + def is_nighttime (self, now): + daytime = self.is_daytime(now) + if daytime is None: + return None + return not daytime + + def is_sunrise (self, now): + return self.is_daytime(now) and self.sunrise == now + + def is_sunset (self, now): + return self.is_nighttime(now) and self.sunset == now diff --git a/python-ecosys/suntime/test_suntime.py b/python-ecosys/suntime/test_suntime.py new file mode 100644 index 000000000..303620330 --- /dev/null +++ b/python-ecosys/suntime/test_suntime.py @@ -0,0 +1,209 @@ +# test_suntime.py + +import unittest +from suntime import Suntime, Sundatetime + +pl1 = ( 42.5966460, 12.4360233) +pl2 = ( 51.1627938,-122.9593616) +pl3 = (-33.9252192, 18.4240762) +pl4 = ( 55.1574890, 82.8547661) +pl5 = ( 78.6560170, 16.3447384) +pl6 = pl5 +pl7 = (-77.7817838, 166.4561470) +pl8 = pl7 + +dt1 = (2000, 1, 1) +dt2 = (2014, 10, 3) +dt3 = (2016, 12, 21) +dt4 = (2021, 4, 24) +dt5 = (2040, 8, 25) +dt6 = (2040, 8, 26) +dt7 = (2033, 8, 10) +dt8 = (2033, 10, 21) + +tz1 = ( 1, 0) +tz2 = (-8, 1) +tz3 = ( 2, 0) +tz4 = ( 0, 0) +tz5 = ( 1, 1) +tz6 = ( 1, 1) +tz7 = (13,-1) +tz8 = (13, 0) + +from datetime import datetime, timedelta, timezone + +class Tz(timezone): + def __init__(self, hours, dst=0): + super().__init__(timedelta(hours=hours)) + self._dst = dst + + def dst(self, dt): + return timedelta(hours=self._dst) if self.isdst(dt) else timedelta(0) + + def isdst(self, dt): + return self._dst != 0 + +class TestSunTime(unittest.TestCase): + + def test_suntime1(self): + st1 = Suntime(*pl1, timezone=tz1[0]*60) + st1.calc_sunrise_sunset(*dt1, dst=tz1[1]*60) + self.assertEqual(divmod(st1.sunrise, 60), ( 7, 40)) + self.assertEqual(divmod(st1.sunset , 60), (16, 47)) + self.assertFalse(st1.is_daytime( 0*60 + 0)) + self.assertTrue (st1.is_daytime(12*60 + 0)) + self.assertTrue (st1.is_sunrise( 7*60 + 40)) + self.assertTrue (st1.is_sunset (16*60 + 47)) + self.assertIsNone(st1.is_daytime ( -1)) + self.assertIsNone(st1.is_daytime (1440)) + self.assertIsNone(st1.is_nighttime( -1)) + self.assertIsNone(st1.is_nighttime(1440)) + + def test_suntime2(self): + st2 = Suntime(*pl2, timezone=tz2[0]*60) + st2.calc_sunrise_sunset(*dt2, dst=tz2[1]*60) + self.assertEqual(divmod(st2.sunrise, 60), ( 7, 16)) + self.assertEqual(divmod(st2.sunset , 60), (18, 46)) + self.assertFalse(st2.is_daytime( 0*60 + 0)) + self.assertTrue (st2.is_daytime(12*60 + 0)) + self.assertTrue (st2.is_sunrise( 7*60 + 16)) + self.assertTrue (st2.is_sunset (18*60 + 46)) + + def test_suntime3(self): + st3 = Suntime(*pl3, timezone=tz3[0]*60) + st3.calc_sunrise_sunset(*dt3, dst=tz3[1]*60) + self.assertEqual(divmod(st3.sunrise, 60), ( 5, 32)) + self.assertEqual(divmod(st3.sunset , 60), (19, 57)) + self.assertFalse(st3.is_daytime( 0*60 + 0)) + self.assertTrue (st3.is_daytime(12*60 + 0)) + self.assertTrue (st3.is_sunrise( 5*60 + 32)) + self.assertTrue (st3.is_sunset (19*60 + 57)) + + def test_suntime4(self): + st4 = Suntime(*pl4, timezone=tz4[0]*60) + st4.calc_sunrise_sunset(*dt4, dst=tz4[1]*60) + self.assertEqual(divmod(st4.sunrise, 60), (-1, 4)) + self.assertEqual(divmod(st4.sunset , 60), (13, 49)) + + def test_suntime5(self): + st5 = Suntime(*pl5, timezone=tz5[0]*60) + st5.calc_sunrise_sunset(*dt5, dst=tz5[1]*60) + self.assertEqual(divmod(st5.sunrise, 60), (-12, 57)) + self.assertEqual(divmod(st5.sunset , 60), ( 36, 57)) + self.assertTrue (st5.is_daytime ( 0*60 + 0)) + self.assertFalse(st5.is_nighttime( 12*60 + 0)) + self.assertFalse(st5.is_sunrise (-12*60 + 57)) + self.assertFalse(st5.is_sunset ( 36*60 + 57)) + + def test_suntime6(self): + st6 = Suntime(*pl6, timezone=tz6[0]*60) + st6.calc_sunrise_sunset(*dt6, dst=tz6[1]*60) + self.assertEqual(divmod(st6.sunrise, 60), ( 1, 35)) + self.assertEqual(divmod(st6.sunset , 60), (24, 18)) + self.assertTrue (st6.is_sunrise( 1*60 + 35)) + self.assertFalse(st6.is_sunset (24*60 + 18)) + + def test_suntime7(self): + st7 = Suntime(*pl7, timezone=tz7[0]*60) + st7.calc_sunrise_sunset(*dt7, dst=tz7[1]*60) + self.assertEqual(divmod(st7.sunrise, 60), ( 37, 0)) + self.assertEqual(divmod(st7.sunset , 60), (-11, 0)) + self.assertFalse(st7.is_daytime ( 12*60 + 0)) + self.assertTrue (st7.is_nighttime( 12*60 + 0)) + self.assertFalse(st7.is_sunrise ( 37*60 + 0)) + self.assertFalse(st7.is_sunset (-11*60 + 0)) + + def test_suntime8(self): + st8 = Suntime(*pl8, timezone=tz8[0]*60) + st8.calc_sunrise_sunset(*dt8, dst=tz8[1]*60) + self.assertEqual(divmod(st8.sunrise, 60), ( 3, 6)) + self.assertEqual(divmod(st8.sunset , 60), (24, 12)) + self.assertTrue (st8.is_daytime (12*60 + 6)) + self.assertFalse(st8.is_nighttime(23*60 + 59)) + self.assertTrue (st8.is_sunrise ( 3*60 + 6)) + self.assertFalse(st8.is_sunset (24*60 + 12)) + + def test_sundatetime1(self): + tz = Tz(*tz1) + sd1 = Sundatetime(*pl1) + sd1.calc_sunrise_sunset(datetime(*dt1, tzinfo=tz)) + self.assertEqual(sd1.sunrise.tuple(), (2000, 1, 1, 7, 40, 0, tz) ) + self.assertEqual(sd1.sunset .tuple(), (2000, 1, 1, 16, 47, 0, tz) ) + self.assertFalse(sd1.is_daytime(datetime(2000, 1, 1, 0, 0, 0, tz))) + self.assertTrue (sd1.is_daytime(datetime(2000, 1, 1, 12, 0, 0, tz))) + self.assertTrue (sd1.is_sunrise(datetime(2000, 1, 1, 7, 40, 0, tz))) + self.assertTrue (sd1.is_sunset (datetime(2000, 1, 1, 16, 47, 0, tz))) + + def test_sundatetime2(self): + tz = Tz(*tz2) + sd2 = Sundatetime(*pl2) + sd2.calc_sunrise_sunset(datetime(*dt2, tzinfo=tz)) + self.assertEqual(sd2.sunrise.tuple(), (2014, 10, 3, 7, 16, 0, tz)) + self.assertEqual(sd2.sunset .tuple(), (2014, 10, 3, 18, 46, 0, tz)) + self.assertFalse(sd2.is_daytime(datetime(2014, 10, 3, 0, 0, 0, tz))) + self.assertTrue (sd2.is_daytime(datetime(2014, 10, 3, 12, 0, 0, tz))) + self.assertTrue (sd2.is_sunrise(datetime(2014, 10, 3, 7, 16, 0, tz))) + self.assertTrue (sd2.is_sunset (datetime(2014, 10, 3, 18, 46, 0, tz))) + + def test_sundatetime3(self): + tz = Tz(*tz3) + sd3 = Sundatetime(*pl3) + sd3.calc_sunrise_sunset(datetime(*dt3, tzinfo=tz)) + self.assertEqual(sd3.sunrise.tuple(), (2016, 12, 21, 5, 32, 0, tz)) + self.assertEqual(sd3.sunset .tuple(), (2016, 12, 21, 19, 57, 0, tz)) + self.assertFalse(sd3.is_daytime(datetime(2016, 12, 21, 0, 0, 0, tz))) + self.assertTrue (sd3.is_daytime(datetime(2016, 12, 21, 12, 0, 0, tz))) + self.assertTrue (sd3.is_sunrise(datetime(2016, 12, 21, 5, 32, 0, tz))) + self.assertTrue (sd3.is_sunset (datetime(2016, 12, 21, 19, 57, 0, tz))) + + def test_sundatetime4(self): + tz = Tz(*tz4) + sd4 = Sundatetime(*pl4) + sd4.calc_sunrise_sunset(datetime(*dt4, tzinfo=tz)) + self.assertEqual(sd4.sunrise.tuple(), (2021, 4, 23, 23, 4, 0, tz)) + self.assertEqual(sd4.sunset .tuple(), (2021, 4, 24, 13, 49, 0, tz)) + + def test_sundatetime5(self): + tz = Tz(*tz5) + sd5 = Sundatetime(*pl5) + sd5.calc_sunrise_sunset(datetime(*dt5, tzinfo=tz)) + self.assertEqual(sd5.sunrise.tuple(), (2040, 8, 24, 12, 57, 0, tz)) + self.assertEqual(sd5.sunset .tuple(), (2040, 8, 26, 12, 57, 0, tz)) + self.assertTrue (sd5.is_daytime (datetime(2040, 8, 25, 0, 0, 0, tz))) + self.assertFalse(sd5.is_nighttime(datetime(2040, 8, 25, 12, 0, 0, tz))) + self.assertTrue (sd5.is_sunrise (datetime(2040, 8, 24, 12, 57, 0, tz))) + self.assertTrue (sd5.is_sunset (datetime(2040, 8, 26, 12, 57, 0, tz))) + + def test_sundatetime6(self): + tz = Tz(*tz6) + sd6 = Sundatetime(*pl6) + sd6.calc_sunrise_sunset(datetime(*dt6, tzinfo=tz)) + self.assertEqual(sd6.sunrise.tuple(), (2040, 8, 26, 1, 35, 0, tz)) + self.assertEqual(sd6.sunset .tuple(), (2040, 8, 27, 0, 18, 0, tz)) + self.assertTrue (sd6.is_sunrise(datetime(2040, 8, 26, 1, 35, 0, tz))) + self.assertTrue (sd6.is_sunset (datetime(2040, 8, 27, 0, 18, 0, tz))) + + def test_sundatetime7(self): + tz = Tz(*tz7) + sd7 = Sundatetime(*pl7) + sd7.calc_sunrise_sunset(datetime(*dt7, tzinfo=tz)) + self.assertEqual(sd7.sunrise.tuple(), (2033, 8, 11, 13, 0, 0, tz)) + self.assertEqual(sd7.sunset .tuple(), (2033, 8, 9, 13, 0, 0, tz)) + self.assertFalse(sd7.is_daytime (datetime(2033, 8, 11, 12, 0, 0, tz))) + self.assertFalse(sd7.is_nighttime(datetime(2033, 8, 25, 0, 0, 0, tz))) + self.assertFalse(sd7.is_sunrise (datetime(2033, 8, 11, 13, 0, 0, tz))) + self.assertFalse(sd7.is_sunset (datetime(2033, 8, 9, 13, 0, 0, tz))) + + def test_sundatetime8(self): + tz = Tz(*tz8) + sd8 = Sundatetime(*pl8) + sd8.calc_sunrise_sunset(datetime(*dt8, tzinfo=tz)) + self.assertEqual(sd8.sunrise.tuple(), (2033, 10, 21, 3, 6, 0, tz)) + self.assertEqual(sd8.sunset .tuple(), (2033, 10, 22, 0, 12, 0, tz)) + self.assertTrue (sd8.is_daytime (datetime(2033, 10, 21, 12, 6, 0, tz))) + self.assertFalse(sd8.is_nighttime(datetime(2033, 10, 21, 23, 59, 0, tz))) + self.assertTrue (sd8.is_sunrise (datetime(2033, 10, 21, 3, 6, 0, tz))) + self.assertTrue (sd8.is_sunset (datetime(2033, 10, 22, 0, 12, 0, tz))) + +if __name__ == '__main__': + unittest.main()