Skip to content

Commit ffa0ec0

Browse files
authored
Merge pull request #1 from brentru/fix-cpy-issues
CircuitPython API Compatibility and Bugfixes
2 parents 820240c + 9bd0af3 commit ffa0ec0

File tree

6 files changed

+38
-157
lines changed

6 files changed

+38
-157
lines changed

README.rst

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ Introduction
1919

2020
Basic date and time types. Implements a subset of the `CPython datetime module <https://docs.python.org/3/library/datetime.html>`_.
2121

22+
NOTE: This library has a large memory footprint and is intended for hardware such as the SAMD51, ESP32-S2, and nRF52.
23+
2224
Dependencies
2325
=============
2426
This driver depends on:

adafruit_datetime.py

100644100755
+24-132
Original file line numberDiff line numberDiff line change
@@ -130,76 +130,6 @@ def _format_offset(off):
130130
return s
131131

132132

133-
# pylint: disable=invalid-name, too-many-locals, too-many-nested-blocks, too-many-branches, too-many-statements
134-
def _wrap_strftime(time_obj, strftime_fmt, timetuple):
135-
# Don't call utcoffset() or tzname() unless actually needed.
136-
f_replace = None # the string to use for %f
137-
z_replace = None # the string to use for %z
138-
Z_replace = None # the string to use for %Z
139-
140-
# Scan strftime_fmt for %z and %Z escapes, replacing as needed.
141-
newformat = []
142-
push = newformat.append
143-
i, n = 0, len(strftime_fmt)
144-
while i < n:
145-
ch = strftime_fmt[i]
146-
i += 1
147-
if ch == "%":
148-
if i < n:
149-
ch = strftime_fmt[i]
150-
i += 1
151-
if ch == "f":
152-
if f_replace is None:
153-
f_replace = "%06d" % getattr(time_obj, "microsecond", 0)
154-
newformat.append(f_replace)
155-
elif ch == "z":
156-
if z_replace is None:
157-
z_replace = ""
158-
if hasattr(time_obj, "utcoffset"):
159-
offset = time_obj.utcoffset()
160-
if offset is not None:
161-
sign = "+"
162-
if offset.days < 0:
163-
offset = -offset
164-
sign = "-"
165-
h, rest = divmod(offset, timedelta(hours=1))
166-
m, rest = divmod(rest, timedelta(minutes=1))
167-
s = rest.seconds
168-
u = offset.microseconds
169-
if u:
170-
z_replace = "%c%02d%02d%02d.%06d" % (
171-
sign,
172-
h,
173-
m,
174-
s,
175-
u,
176-
)
177-
elif s:
178-
z_replace = "%c%02d%02d%02d" % (sign, h, m, s)
179-
else:
180-
z_replace = "%c%02d%02d" % (sign, h, m)
181-
assert "%" not in z_replace
182-
newformat.append(z_replace)
183-
elif ch == "Z":
184-
if Z_replace is None:
185-
Z_replace = ""
186-
if hasattr(time_obj, "tzname"):
187-
s = time_obj.tzname()
188-
if s is not None:
189-
# strftime is going to have at this: escape %
190-
Z_replace = s.replace("%", "%%")
191-
newformat.append(Z_replace)
192-
else:
193-
push("%")
194-
push(ch)
195-
else:
196-
push("%")
197-
else:
198-
push(ch)
199-
newformat = "".join(newformat)
200-
return _time.strftime(newformat, timetuple)
201-
202-
203133
# Utility functions - timezone
204134
def _check_tzname(name):
205135
""""Just raise TypeError if the arg isn't None or a string."""
@@ -370,7 +300,7 @@ def _ord2ymd(n):
370300
class timedelta:
371301
"""A timedelta object represents a duration, the difference between two dates or times."""
372302

373-
# pylint: disable=too-many-arguments
303+
# pylint: disable=too-many-arguments, too-many-locals, too-many-statements
374304
def __new__(
375305
cls,
376306
days=0,
@@ -859,13 +789,15 @@ def __new__(cls, offset, name=_Omitted):
859789
raise ValueError(
860790
"offset must be a timedelta" " representing a whole number of minutes"
861791
)
792+
cls._offset = offset
793+
cls._name = name
862794
return cls._create(offset, name)
863795

864-
# pylint: disable=protected-access
796+
# pylint: disable=protected-access, bad-super-call
865797
@classmethod
866798
def _create(cls, offset, name=None):
867799
"""High-level creation for a timezone object."""
868-
self = tzinfo.__new__(cls)
800+
self = super(tzinfo, cls).__new__(cls)
869801
self._offset = offset
870802
self._name = name
871803
return self
@@ -998,15 +930,6 @@ def isoformat(self, timespec="auto"):
998930
# For a time t, str(t) is equivalent to t.isoformat()
999931
__str__ = isoformat
1000932

1001-
def strftime(self, fmt):
1002-
"""Format using strftime(). The date part of the timestamp passed
1003-
to underlying strftime should not be used.
1004-
"""
1005-
# The year must be >= 1000 else Python's strftime implementation
1006-
# can raise a bogus exception.
1007-
timetuple = (1900, 1, 1, self._hour, self._minute, self._second, 0, 1, -1)
1008-
return _wrap_strftime(self, fmt, timetuple)
1009-
1010933
def utcoffset(self):
1011934
"""Return the timezone offset in minutes east of UTC (negative west of
1012935
UTC)."""
@@ -1123,8 +1046,6 @@ def _tzstr(self, sep=":"):
11231046
def __format__(self, fmt):
11241047
if not isinstance(fmt, str):
11251048
raise TypeError("must be str, not %s" % type(fmt).__name__)
1126-
if len(fmt) != 0:
1127-
return self.strftime(fmt)
11281049
return str(self)
11291050

11301051
def __repr__(self):
@@ -1259,7 +1180,11 @@ def _fromtimestamp(cls, t, utc, tz):
12591180
t -= 1
12601181
us += 1000000
12611182

1262-
converter = _time.gmtime if utc else _time.localtime
1183+
if utc:
1184+
raise NotImplementedError(
1185+
"CircuitPython does not currently implement time.gmtime."
1186+
)
1187+
converter = _time.localtime
12631188
struct_time = converter(t)
12641189
ss = min(struct_time[5], 59) # clamp out leap seconds if the platform has them
12651190
result = cls(
@@ -1272,39 +1197,7 @@ def _fromtimestamp(cls, t, utc, tz):
12721197
us,
12731198
tz,
12741199
)
1275-
if tz is None:
1276-
# As of version 2015f max fold in IANA database is
1277-
# 23 hours at 1969-09-30 13:00:00 in Kwajalein.
1278-
# Let's probe 24 hours in the past to detect a transition:
1279-
max_fold_seconds = 24 * 3600
1280-
1281-
struct_time = converter(t - max_fold_seconds)[:6]
1282-
probe1 = cls(
1283-
struct_time[0],
1284-
struct_time[1],
1285-
struct_time[2],
1286-
struct_time[3],
1287-
struct_time[4],
1288-
struct_time[5],
1289-
us,
1290-
tz,
1291-
)
1292-
trans = result - probe1 - timedelta(0, max_fold_seconds)
1293-
if trans.days < 0:
1294-
struct_time = converter(t + trans // timedelta(0, 1))[:6]
1295-
probe2 = cls(
1296-
struct_time[0],
1297-
struct_time[1],
1298-
struct_time[2],
1299-
struct_time[3],
1300-
struct_time[4],
1301-
struct_time[5],
1302-
us,
1303-
tz,
1304-
)
1305-
if probe2 == result:
1306-
result._fold = 1
1307-
else:
1200+
if tz is not None:
13081201
result = tz.fromutc(result)
13091202
return result
13101203

@@ -1316,7 +1209,7 @@ def fromtimestamp(cls, timestamp, tz=None):
13161209
@classmethod
13171210
def now(cls, timezone=None):
13181211
"""Return the current local date and time."""
1319-
return cls.fromtimestamp(_time.time(), timezone)
1212+
return cls.fromtimestamp(_time.time(), tz=timezone)
13201213

13211214
@classmethod
13221215
def utcfromtimestamp(cls, timestamp):
@@ -1449,19 +1342,18 @@ def weekday(self):
14491342
"""Return the day of the week as an integer, where Monday is 0 and Sunday is 6."""
14501343
return (self.toordinal() + 6) % 7
14511344

1452-
def strftime(self, fmt):
1453-
"""Format using strftime(). The date part of the timestamp passed
1454-
to underlying strftime should not be used.
1455-
"""
1456-
# The year must be >= 1000 else Python's strftime implementation
1457-
# can raise a bogus exception.
1458-
timetuple = (1900, 1, 1, self._hour, self._minute, self._second, 0, 1, -1)
1459-
return _wrap_strftime(self, fmt, timetuple)
1460-
1461-
def __format__(self, fmt):
1462-
if len(fmt) != 0:
1463-
return self.strftime(fmt)
1464-
return str(self)
1345+
def ctime(self):
1346+
"Return string representing the datetime."
1347+
weekday = self.toordinal() % 7 or 7
1348+
return "%s %s %2d %02d:%02d:%02d %04d" % (
1349+
_DAYNAMES[weekday],
1350+
_MONTHNAMES[self._month],
1351+
self._day,
1352+
self._hour,
1353+
self._minute,
1354+
self._second,
1355+
self._year,
1356+
)
14651357

14661358
def __repr__(self):
14671359
"""Convert to formal string, for repr()."""

examples/datetime_simpletest.py

+2-8
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
# Example of working with a `datetime` object
1212
# from https://docs.python.org/3/library/datetime.html#examples-of-usage-datetime
13-
from adafruit_datetime import datetime, date, time, timezone
13+
from adafruit_datetime import datetime, date, time
1414

1515
# Using datetime.combine()
1616
d = date(2005, 7, 14)
@@ -20,17 +20,11 @@
2020

2121
# Using datetime.now()
2222
print("Current time (GMT +1):", datetime.now())
23-
print("Current UTC time: ", datetime.now(timezone.utc))
2423

2524
# Using datetime.timetuple() to get tuple of all attributes
2625
dt = datetime(2006, 11, 21, 16, 30)
2726
tt = dt.timetuple()
2827
for it in tt:
2928
print(it)
3029

31-
# Formatting a datetime
32-
print(
33-
"The {1} is {0:%d}, the {2} is {0:%B}, the {3} is {0:%I:%M%p}.".format(
34-
dt, "day", "month", "time"
35-
)
36-
)
30+
print("Today is: ", dt.ctime())

examples/datetime_time.py

-7
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,3 @@
2121

2222
# Timezone name
2323
print("Timezone Name:", t.tzname())
24-
25-
# Return a string representing the time, controlled by an explicit format string
26-
strf_time = t.strftime("%H:%M:%S %Z")
27-
print("Formatted time string:", strf_time)
28-
29-
# Specifies a format string in formatted string literals
30-
print("The time is {:%H:%M}.".format(t))

tests/test_datetime.py

+8-10
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,7 @@ def test_fromtimestamp(self):
506506
got = self.theclass.fromtimestamp(ts)
507507
self.verify_field_equality(expected, got)
508508

509+
@unittest.skip("gmtime not implemented in CircuitPython")
509510
def test_utcfromtimestamp(self):
510511
import time
511512

@@ -514,8 +515,6 @@ def test_utcfromtimestamp(self):
514515
got = self.theclass.utcfromtimestamp(ts)
515516
self.verify_field_equality(expected, got)
516517

517-
# TODO
518-
@unittest.skip("Wait until we bring in UTCOFFSET")
519518
# Run with US-style DST rules: DST begins 2 a.m. on second Sunday in
520519
# March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0).
521520
@support.run_with_tz("EST+05EDT,M3.2.0,M11.1.0")
@@ -547,8 +546,6 @@ def test_timestamp_naive(self):
547546
else:
548547
self.assertEqual(self.theclass.fromtimestamp(s), t)
549548

550-
# TODO
551-
@unittest.skip("Hold off on this test until we bring timezone in")
552549
def test_timestamp_aware(self):
553550
t = self.theclass(1970, 1, 1, tzinfo=timezone.utc)
554551
self.assertEqual(t.timestamp(), 0.0)
@@ -559,6 +556,7 @@ def test_timestamp_aware(self):
559556
)
560557
self.assertEqual(t.timestamp(), 18000 + 3600 + 2 * 60 + 3 + 4 * 1e-6)
561558

559+
@unittest.skip("Not implemented - gmtime")
562560
@support.run_with_tz("MSK-03") # Something east of Greenwich
563561
def test_microsecond_rounding(self):
564562
for fts in [self.theclass.fromtimestamp, self.theclass.utcfromtimestamp]:
@@ -599,8 +597,7 @@ def test_microsecond_rounding(self):
599597
self.assertEqual(t.second, 0)
600598
self.assertEqual(t.microsecond, 7812)
601599

602-
# TODO
603-
@unittest.skip("timezone not implemented")
600+
@unittest.skip("gmtime not implemented in CircuitPython")
604601
def test_timestamp_limits(self):
605602
# minimum timestamp
606603
min_dt = self.theclass.min.replace(tzinfo=timezone.utc)
@@ -649,6 +646,7 @@ def test_insane_fromtimestamp(self):
649646
for insane in -1e200, 1e200:
650647
self.assertRaises(OverflowError, self.theclass.fromtimestamp, insane)
651648

649+
@unittest.skip("Not implemented - gmtime")
652650
def test_insane_utcfromtimestamp(self):
653651
# It's possible that some platform maps time_t to double,
654652
# and that this test will fail there. This test should
@@ -657,7 +655,7 @@ def test_insane_utcfromtimestamp(self):
657655
for insane in -1e200, 1e200:
658656
self.assertRaises(OverflowError, self.theclass.utcfromtimestamp, insane)
659657

660-
@unittest.skip("Not implemented - utcnow")
658+
@unittest.skip("gmtime not implemented in CircuitPython")
661659
def test_utcnow(self):
662660
import time
663661

@@ -672,7 +670,7 @@ def test_utcnow(self):
672670
# Else try again a few times.
673671
self.assertLessEqual(abs(from_timestamp - from_now), tolerance)
674672

675-
@unittest.skip("Not implemented - strptime")
673+
@unittest.skip("gmtime not implemented in CircuitPython")
676674
def test_strptime(self):
677675
string = "2004-12-01 13:02:47.197"
678676
format = "%Y-%m-%d %H:%M:%S.%f"
@@ -735,7 +733,7 @@ def test_strptime(self):
735733
with self.assertRaises(ValueError):
736734
strptime("-000", "%z")
737735

738-
@unittest.skip("Not implemented - strptime")
736+
@unittest.skip("gmtime not implemented in CircuitPython")
739737
def test_strptime_single_digit(self):
740738
# bpo-34903: Check that single digit dates and times are allowed.
741739

@@ -798,7 +796,7 @@ def test_more_timetuple(self):
798796
self.assertEqual(tt.tm_yday, t.toordinal() - date(t.year, 1, 1).toordinal() + 1)
799797
self.assertEqual(tt.tm_isdst, -1)
800798

801-
@unittest.skip("Not implemented - strftime")
799+
@unittest.skip("gmtime not implemented in CircuitPython")
802800
def test_more_strftime(self):
803801
# This tests fields beyond those tested by the TestDate.test_strftime.
804802
t = self.theclass(2004, 12, 31, 6, 22, 33, 47)

tests/test_time.py

+2
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ def test_1653736(self):
220220
t = self.theclass(second=1)
221221
self.assertRaises(TypeError, t.isoformat, foo=3)
222222

223+
@unittest.skip("strftime not implemented for CircuitPython time objects")
223224
def test_strftime(self):
224225
t = self.theclass(1, 2, 3, 4)
225226
self.assertEqual(t.strftime("%H %M %S %f"), "01 02 03 000004")
@@ -231,6 +232,7 @@ def test_strftime(self):
231232
except UnicodeEncodeError:
232233
pass
233234

235+
@unittest.skip("strftime not implemented for CircuitPython time objects")
234236
def test_format(self):
235237
t = self.theclass(1, 2, 3, 4)
236238
self.assertEqual(t.__format__(""), str(t))

0 commit comments

Comments
 (0)