Merge pull request #1 from brentru/fix-cpy-issues

CircuitPython API Compatibility and Bugfixes
This commit is contained in:
Brent Rubell 2021-01-29 13:56:59 -05:00 committed by GitHub
commit ffa0ec0f7b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 38 additions and 157 deletions

View file

@ -19,6 +19,8 @@ Introduction
Basic date and time types. Implements a subset of the `CPython datetime module <https://docs.python.org/3/library/datetime.html>`_.
NOTE: This library has a large memory footprint and is intended for hardware such as the SAMD51, ESP32-S2, and nRF52.
Dependencies
=============
This driver depends on:

156
adafruit_datetime.py Normal file → Executable file
View file

@ -130,76 +130,6 @@ def _format_offset(off):
return s
# pylint: disable=invalid-name, too-many-locals, too-many-nested-blocks, too-many-branches, too-many-statements
def _wrap_strftime(time_obj, strftime_fmt, timetuple):
# Don't call utcoffset() or tzname() unless actually needed.
f_replace = None # the string to use for %f
z_replace = None # the string to use for %z
Z_replace = None # the string to use for %Z
# Scan strftime_fmt for %z and %Z escapes, replacing as needed.
newformat = []
push = newformat.append
i, n = 0, len(strftime_fmt)
while i < n:
ch = strftime_fmt[i]
i += 1
if ch == "%":
if i < n:
ch = strftime_fmt[i]
i += 1
if ch == "f":
if f_replace is None:
f_replace = "%06d" % getattr(time_obj, "microsecond", 0)
newformat.append(f_replace)
elif ch == "z":
if z_replace is None:
z_replace = ""
if hasattr(time_obj, "utcoffset"):
offset = time_obj.utcoffset()
if offset is not None:
sign = "+"
if offset.days < 0:
offset = -offset
sign = "-"
h, rest = divmod(offset, timedelta(hours=1))
m, rest = divmod(rest, timedelta(minutes=1))
s = rest.seconds
u = offset.microseconds
if u:
z_replace = "%c%02d%02d%02d.%06d" % (
sign,
h,
m,
s,
u,
)
elif s:
z_replace = "%c%02d%02d%02d" % (sign, h, m, s)
else:
z_replace = "%c%02d%02d" % (sign, h, m)
assert "%" not in z_replace
newformat.append(z_replace)
elif ch == "Z":
if Z_replace is None:
Z_replace = ""
if hasattr(time_obj, "tzname"):
s = time_obj.tzname()
if s is not None:
# strftime is going to have at this: escape %
Z_replace = s.replace("%", "%%")
newformat.append(Z_replace)
else:
push("%")
push(ch)
else:
push("%")
else:
push(ch)
newformat = "".join(newformat)
return _time.strftime(newformat, timetuple)
# Utility functions - timezone
def _check_tzname(name):
""""Just raise TypeError if the arg isn't None or a string."""
@ -370,7 +300,7 @@ def _ord2ymd(n):
class timedelta:
"""A timedelta object represents a duration, the difference between two dates or times."""
# pylint: disable=too-many-arguments
# pylint: disable=too-many-arguments, too-many-locals, too-many-statements
def __new__(
cls,
days=0,
@ -859,13 +789,15 @@ class timezone(tzinfo):
raise ValueError(
"offset must be a timedelta" " representing a whole number of minutes"
)
cls._offset = offset
cls._name = name
return cls._create(offset, name)
# pylint: disable=protected-access
# pylint: disable=protected-access, bad-super-call
@classmethod
def _create(cls, offset, name=None):
"""High-level creation for a timezone object."""
self = tzinfo.__new__(cls)
self = super(tzinfo, cls).__new__(cls)
self._offset = offset
self._name = name
return self
@ -998,15 +930,6 @@ class time:
# For a time t, str(t) is equivalent to t.isoformat()
__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 utcoffset(self):
"""Return the timezone offset in minutes east of UTC (negative west of
UTC)."""
@ -1123,8 +1046,6 @@ class time:
def __format__(self, fmt):
if not isinstance(fmt, str):
raise TypeError("must be str, not %s" % type(fmt).__name__)
if len(fmt) != 0:
return self.strftime(fmt)
return str(self)
def __repr__(self):
@ -1259,7 +1180,11 @@ class datetime(date):
t -= 1
us += 1000000
converter = _time.gmtime if utc else _time.localtime
if utc:
raise NotImplementedError(
"CircuitPython does not currently implement time.gmtime."
)
converter = _time.localtime
struct_time = converter(t)
ss = min(struct_time[5], 59) # clamp out leap seconds if the platform has them
result = cls(
@ -1272,39 +1197,7 @@ class datetime(date):
us,
tz,
)
if tz is None:
# As of version 2015f max fold in IANA database is
# 23 hours at 1969-09-30 13:00:00 in Kwajalein.
# Let's probe 24 hours in the past to detect a transition:
max_fold_seconds = 24 * 3600
struct_time = converter(t - max_fold_seconds)[:6]
probe1 = cls(
struct_time[0],
struct_time[1],
struct_time[2],
struct_time[3],
struct_time[4],
struct_time[5],
us,
tz,
)
trans = result - probe1 - timedelta(0, max_fold_seconds)
if trans.days < 0:
struct_time = converter(t + trans // timedelta(0, 1))[:6]
probe2 = cls(
struct_time[0],
struct_time[1],
struct_time[2],
struct_time[3],
struct_time[4],
struct_time[5],
us,
tz,
)
if probe2 == result:
result._fold = 1
else:
if tz is not None:
result = tz.fromutc(result)
return result
@ -1316,7 +1209,7 @@ class datetime(date):
@classmethod
def now(cls, timezone=None):
"""Return the current local date and time."""
return cls.fromtimestamp(_time.time(), timezone)
return cls.fromtimestamp(_time.time(), tz=timezone)
@classmethod
def utcfromtimestamp(cls, timestamp):
@ -1449,19 +1342,18 @@ class datetime(date):
"""Return the day of the week as an integer, where Monday is 0 and Sunday is 6."""
return (self.toordinal() + 6) % 7
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)
def ctime(self):
"Return string representing the datetime."
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 __repr__(self):
"""Convert to formal string, for repr()."""

View file

@ -10,7 +10,7 @@
# Example of working with a `datetime` object
# from https://docs.python.org/3/library/datetime.html#examples-of-usage-datetime
from adafruit_datetime import datetime, date, time, timezone
from adafruit_datetime import datetime, date, time
# Using datetime.combine()
d = date(2005, 7, 14)
@ -20,7 +20,6 @@ print(datetime.combine(d, t))
# Using datetime.now()
print("Current time (GMT +1):", datetime.now())
print("Current UTC time: ", datetime.now(timezone.utc))
# Using datetime.timetuple() to get tuple of all attributes
dt = datetime(2006, 11, 21, 16, 30)
@ -28,9 +27,4 @@ tt = dt.timetuple()
for it in tt:
print(it)
# Formatting a datetime
print(
"The {1} is {0:%d}, the {2} is {0:%B}, the {3} is {0:%I:%M%p}.".format(
dt, "day", "month", "time"
)
)
print("Today is: ", dt.ctime())

View file

@ -21,10 +21,3 @@ print("ISO8601-Formatted Time:", iso_time)
# Timezone name
print("Timezone Name:", t.tzname())
# Return a string representing the time, controlled by an explicit format string
strf_time = t.strftime("%H:%M:%S %Z")
print("Formatted time string:", strf_time)
# Specifies a format string in formatted string literals
print("The time is {:%H:%M}.".format(t))

View file

@ -506,6 +506,7 @@ class TestDateTime(TestDate):
got = self.theclass.fromtimestamp(ts)
self.verify_field_equality(expected, got)
@unittest.skip("gmtime not implemented in CircuitPython")
def test_utcfromtimestamp(self):
import time
@ -514,8 +515,6 @@ class TestDateTime(TestDate):
got = self.theclass.utcfromtimestamp(ts)
self.verify_field_equality(expected, got)
# TODO
@unittest.skip("Wait until we bring in UTCOFFSET")
# 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")
@ -547,8 +546,6 @@ class TestDateTime(TestDate):
else:
self.assertEqual(self.theclass.fromtimestamp(s), t)
# TODO
@unittest.skip("Hold off on this test until we bring timezone in")
def test_timestamp_aware(self):
t = self.theclass(1970, 1, 1, tzinfo=timezone.utc)
self.assertEqual(t.timestamp(), 0.0)
@ -559,6 +556,7 @@ class TestDateTime(TestDate):
)
self.assertEqual(t.timestamp(), 18000 + 3600 + 2 * 60 + 3 + 4 * 1e-6)
@unittest.skip("Not implemented - gmtime")
@support.run_with_tz("MSK-03") # Something east of Greenwich
def test_microsecond_rounding(self):
for fts in [self.theclass.fromtimestamp, self.theclass.utcfromtimestamp]:
@ -599,8 +597,7 @@ class TestDateTime(TestDate):
self.assertEqual(t.second, 0)
self.assertEqual(t.microsecond, 7812)
# TODO
@unittest.skip("timezone not implemented")
@unittest.skip("gmtime not implemented in CircuitPython")
def test_timestamp_limits(self):
# minimum timestamp
min_dt = self.theclass.min.replace(tzinfo=timezone.utc)
@ -649,6 +646,7 @@ class TestDateTime(TestDate):
for insane in -1e200, 1e200:
self.assertRaises(OverflowError, self.theclass.fromtimestamp, insane)
@unittest.skip("Not implemented - gmtime")
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
@ -657,7 +655,7 @@ class TestDateTime(TestDate):
for insane in -1e200, 1e200:
self.assertRaises(OverflowError, self.theclass.utcfromtimestamp, insane)
@unittest.skip("Not implemented - utcnow")
@unittest.skip("gmtime not implemented in CircuitPython")
def test_utcnow(self):
import time
@ -672,7 +670,7 @@ class TestDateTime(TestDate):
# Else try again a few times.
self.assertLessEqual(abs(from_timestamp - from_now), tolerance)
@unittest.skip("Not implemented - strptime")
@unittest.skip("gmtime not implemented in CircuitPython")
def test_strptime(self):
string = "2004-12-01 13:02:47.197"
format = "%Y-%m-%d %H:%M:%S.%f"
@ -735,7 +733,7 @@ class TestDateTime(TestDate):
with self.assertRaises(ValueError):
strptime("-000", "%z")
@unittest.skip("Not implemented - strptime")
@unittest.skip("gmtime not implemented in CircuitPython")
def test_strptime_single_digit(self):
# bpo-34903: Check that single digit dates and times are allowed.
@ -798,7 +796,7 @@ class TestDateTime(TestDate):
self.assertEqual(tt.tm_yday, t.toordinal() - date(t.year, 1, 1).toordinal() + 1)
self.assertEqual(tt.tm_isdst, -1)
@unittest.skip("Not implemented - strftime")
@unittest.skip("gmtime not implemented in CircuitPython")
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)

View file

@ -220,6 +220,7 @@ class TestTime(HarmlessMixedComparison, unittest.TestCase):
t = self.theclass(second=1)
self.assertRaises(TypeError, t.isoformat, foo=3)
@unittest.skip("strftime not implemented for CircuitPython time objects")
def test_strftime(self):
t = self.theclass(1, 2, 3, 4)
self.assertEqual(t.strftime("%H %M %S %f"), "01 02 03 000004")
@ -231,6 +232,7 @@ class TestTime(HarmlessMixedComparison, unittest.TestCase):
except UnicodeEncodeError:
pass
@unittest.skip("strftime not implemented for CircuitPython time objects")
def test_format(self):
t = self.theclass(1, 2, 3, 4)
self.assertEqual(t.__format__(""), str(t))