Adafruit_CircuitPython_date.../tests/test_date.py
2021-03-17 12:16:21 -04:00

473 lines
18 KiB
Python

# SPDX-FileCopyrightText: 2001-2021 Python Software Foundation.All rights reserved.
# SPDX-FileCopyrightText: 2000 BeOpen.com. All rights reserved.
# SPDX-FileCopyrightText: 1995-2001 Corporation for National Research Initiatives.
# All rights reserved.
# SPDX-FileCopyrightText: 1995-2001 Corporation for National Research Initiatives.
# All rights reserved.
# SPDX-FileCopyrightText: 1991-1995 Stichting Mathematisch Centrum. All rights reserved.
# SPDX-FileCopyrightText: 2021 Brent Rubell for Adafruit Industries
# SPDX-License-Identifier: Python-2.0
# Implements a subset of https://github.com/python/cpython/blob/master/Lib/test/datetimetester.py
# NOTE: This test is based off CPython and therefore linting is disabled within this file.
# pylint:disable=invalid-name, no-member, wrong-import-position, undefined-variable, no-self-use, cell-var-from-loop, misplaced-comparison-constant, too-many-public-methods, fixme, import-outside-toplevel, unused-argument, too-few-public-methods
import sys
import unittest
# CPython standard implementation
from datetime import date as cpython_date
from datetime import MINYEAR, MAXYEAR
# CircuitPython subset implementation
sys.path.append("..")
from adafruit_datetime import date as cpy_date
# An arbitrary collection of objects of non-datetime types, for testing
# mixed-type comparisons.
OTHERSTUFF = (10, 34.5, "abc", {}, [], ())
class TestDate(unittest.TestCase):
def test_basic_attributes(self):
dt = cpy_date(2002, 3, 1)
dt_2 = cpython_date(2002, 3, 1)
self.assertEqual(dt.year, dt_2.year)
self.assertEqual(dt.month, dt_2.month)
self.assertEqual(dt.day, dt_2.day)
def test_bad_constructor_arguments(self):
# bad years
cpy_date(MINYEAR, 1, 1) # no exception
cpy_date(MAXYEAR, 1, 1) # no exception
self.assertRaises(ValueError, cpy_date, MINYEAR - 1, 1, 1)
self.assertRaises(ValueError, cpy_date, MAXYEAR + 1, 1, 1)
# bad months
cpy_date(2000, 1, 1) # no exception
cpy_date(2000, 12, 1) # no exception
self.assertRaises(ValueError, cpy_date, 2000, 0, 1)
self.assertRaises(ValueError, cpy_date, 2000, 13, 1)
# bad days
cpy_date(2000, 2, 29) # no exception
cpy_date(2004, 2, 29) # no exception
cpy_date(2400, 2, 29) # no exception
self.assertRaises(ValueError, cpy_date, 2000, 2, 30)
self.assertRaises(ValueError, cpy_date, 2001, 2, 29)
self.assertRaises(ValueError, cpy_date, 2100, 2, 29)
self.assertRaises(ValueError, cpy_date, 1900, 2, 29)
self.assertRaises(ValueError, cpy_date, 2000, 1, 0)
self.assertRaises(ValueError, cpy_date, 2000, 1, 32)
def test_hash_equality(self):
d = cpy_date(2000, 12, 31)
e = cpy_date(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 = cpy_date(2001, 1, 1)
e = cpy_date(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_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 = cpy_date.fromtimestamp(ts)
self.assertEqual(d.year, year)
self.assertEqual(d.month, month)
self.assertEqual(d.day, day)
def test_fromisoformat(self):
# Try an arbitrary fixed value.
iso_date_string = "1999-09-19"
d = cpy_date.fromisoformat(iso_date_string)
self.assertEqual(d.year, 1999)
self.assertEqual(d.month, 9)
self.assertEqual(d.day, 19)
def test_fromisoformat_bad_formats(self):
# Try an arbitrary fixed value.
self.assertRaises(ValueError, cpy_date.fromisoformat, "99-09-19")
self.assertRaises(ValueError, cpy_date.fromisoformat, "1999-13-19")
# TODO: Test this when timedelta is added in
@unittest.skip("Skip for CircuitPython - timedelta() not yet implemented.")
def test_today(self):
import time
# We claim that today() is like fromtimestamp(time.time()), so
# prove it.
for dummy in range(3):
today = cpy_date.today()
ts = time.time()
todayagain = cpy_date.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(
cpy_date(2002, 3, 4 + i).weekday(),
cpython_date(2002, 3, 4 + i).weekday(),
)
self.assertEqual(
cpy_date(2002, 3, 4 + i).isoweekday(),
cpython_date(2002, 3, 4 + i).isoweekday(),
)
# January 2, 1956 is a Monday
self.assertEqual(
cpy_date(1956, 1, 2 + i).weekday(),
cpython_date(1956, 1, 2 + i).weekday(),
)
self.assertEqual(
cpy_date(1956, 1, 2 + i).isoweekday(),
cpython_date(1956, 1, 2 + i).isoweekday(),
)
@unittest.skip(
"Skip for CircuitPython - isocalendar() not implemented for date objects."
)
def test_isocalendar(self):
# Check examples from
# http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
for i in range(7):
d = cpy_date(2003, 12, 22 + i)
self.assertEqual(d.isocalendar(), (2003, 52, i + 1))
d = cpy_date(2003, 12, 29) + timedelta(i)
self.assertEqual(d.isocalendar(), (2004, 1, i + 1))
d = cpy_date(2004, 1, 5 + i)
self.assertEqual(d.isocalendar(), (2004, 2, i + 1))
d = cpy_date(2009, 12, 21 + i)
self.assertEqual(d.isocalendar(), (2009, 52, i + 1))
d = cpy_date(2009, 12, 28) + timedelta(i)
self.assertEqual(d.isocalendar(), (2009, 53, i + 1))
d = cpy_date(2010, 1, 4 + i)
self.assertEqual(d.isocalendar(), (2010, 1, i + 1))
def test_isoformat(self):
# test isoformat against expected and cpython equiv.
t = cpy_date(2, 3, 2)
t2 = cpython_date(2, 3, 2)
self.assertEqual(t.isoformat(), "0002-03-02")
self.assertEqual(t.isoformat(), t2.isoformat())
@unittest.skip("Skip for CircuitPython - ctime() not implemented for date objects.")
def test_ctime(self):
t = cpy_date(2002, 3, 2)
self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
@unittest.skip(
"Skip for CircuitPython - strftime() not implemented for date objects."
)
def test_strftime(self):
t = cpy_date(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 = cpy_date(2007, 9, 10)
self.assertEqual(dt.__format__(""), str(dt))
# check that a derived class's __str__() gets called
class A(cpy_date):
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(cpy_date):
def strftime(self, format_spec):
return "B"
b = B(2007, 9, 10)
self.assertEqual(b.__format__(""), str(dt))
# date strftime not implemented in CircuitPython, skip
"""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(
"Skip for CircuitPython - min/max/resolution not implemented for date objects."
)
def test_resolution_info(self):
# XXX: Should min and max respect subclassing?
if issubclass(cpy_date, datetime):
expected_class = datetime
else:
expected_class = date
self.assertIsInstance(cpy_date.min, expected_class)
self.assertIsInstance(cpy_date.max, expected_class)
self.assertIsInstance(cpy_date.resolution, timedelta)
self.assertTrue(cpy_date.max > cpy_date.min)
# TODO: Needs timedelta
@unittest.skip("Skip for CircuitPython - timedelta not implemented.")
def test_extreme_timedelta(self):
big = cpy_date.max - cpy_date.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(cpy_date.min + big, cpy_date.max)
self.assertEqual(cpy_date.max - big, cpy_date.min)
def test_timetuple(self):
for i in range(7):
# January 2, 1956 is a Monday (0)
d = cpy_date(1956, 1, 2 + i)
t = d.timetuple()
d2 = cpython_date(1956, 1, 2 + i)
t2 = d2.timetuple()
self.assertEqual(t, t2)
# February 1, 1956 is a Wednesday (2)
d = cpy_date(1956, 2, 1 + i)
t = d.timetuple()
d2 = cpython_date(1956, 2, 1 + i)
t2 = d2.timetuple()
self.assertEqual(t, t2)
# March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
# of the year.
d = cpy_date(1956, 3, 1 + i)
t = d.timetuple()
d2 = cpython_date(1956, 3, 1 + i)
t2 = d2.timetuple()
self.assertEqual(t, t2)
self.assertEqual(t.tm_year, t2.tm_year)
self.assertEqual(t.tm_mon, t2.tm_mon)
self.assertEqual(t.tm_mday, t2.tm_mday)
self.assertEqual(t.tm_hour, t2.tm_hour)
self.assertEqual(t.tm_min, t2.tm_min)
self.assertEqual(t.tm_sec, t2.tm_sec)
self.assertEqual(t.tm_wday, t2.tm_wday)
self.assertEqual(t.tm_yday, t2.tm_yday)
self.assertEqual(t.tm_isdst, t2.tm_isdst)
def test_compare(self):
t1 = cpy_date(2, 3, 4)
t2 = cpy_date(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 = cpy_date(*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 = cpy_date(2000, 4, 5)
our2 = cpython_date(2000, 4, 5)
# Our class can be compared for equality to other classes
self.assertEqual(our == 1, our2 == 1)
self.assertEqual(1 == our, 1 == our2)
self.assertEqual(our != 1, our2 != 1)
self.assertEqual(1 != our, 1 != our2)
# 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)
@unittest.skip(
"Skip for CircuitPython - min/max date attributes not implemented yet."
)
def test_bool(self):
# All dates are considered true.
self.assertTrue(cpy_date.min)
self.assertTrue(cpy_date.max)
@unittest.skip("Skip for CircuitPython - date strftime not implemented yet.")
def test_strftime_y2k(self):
for y in (1, 49, 70, 99, 100, 999, 1000, 1970):
d = cpy_date(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)
@unittest.skip("Skip for CircuitPython - date replace not implemented.")
def test_replace(self):
cls = cpy_date
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(cpy_date):
theAnswer = 42
def __new__(cls, *args, **kws):
temp = kws.copy()
extra = temp.pop("extra")
result = cpy_date.__new__(cls, *args, **temp)
result.extra = extra
return result
def newmeth(self, start):
return start + self.year + self.month
args = 2003, 4, 14
dt1 = cpy_date(*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)