diff --git a/README.rst b/README.rst index 3a9efaf..11b485d 100644 --- a/README.rst +++ b/README.rst @@ -19,6 +19,8 @@ Introduction Basic date and time types. Implements a subset of the `CPython datetime module `_. +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: diff --git a/adafruit_datetime.py b/adafruit_datetime.py old mode 100644 new mode 100755 index 08798da..c9d34fd --- a/adafruit_datetime.py +++ b/adafruit_datetime.py @@ -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 @@ def __new__(cls, offset, name=_Omitted): 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 @@ def isoformat(self, timespec="auto"): # 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 @@ def _tzstr(self, sep=":"): 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 @@ def _fromtimestamp(cls, t, utc, tz): 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 @@ def _fromtimestamp(cls, t, utc, tz): 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 @@ def fromtimestamp(cls, timestamp, tz=None): @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 @@ def weekday(self): """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().""" diff --git a/examples/datetime_simpletest.py b/examples/datetime_simpletest.py index 5d1fdc3..4c42b82 100644 --- a/examples/datetime_simpletest.py +++ b/examples/datetime_simpletest.py @@ -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 @@ # 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 @@ 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()) diff --git a/examples/datetime_time.py b/examples/datetime_time.py index 588eb2d..f36e3dc 100644 --- a/examples/datetime_time.py +++ b/examples/datetime_time.py @@ -21,10 +21,3 @@ # 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)) diff --git a/tests/test_datetime.py b/tests/test_datetime.py index 4e472a7..a3e90b0 100644 --- a/tests/test_datetime.py +++ b/tests/test_datetime.py @@ -506,6 +506,7 @@ def test_fromtimestamp(self): 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 @@ def test_utcfromtimestamp(self): 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 @@ def test_timestamp_naive(self): 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 @@ def test_timestamp_aware(self): ) 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 @@ def test_microsecond_rounding(self): 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 @@ def test_insane_fromtimestamp(self): 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 @@ def test_insane_utcfromtimestamp(self): 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 @@ def test_utcnow(self): # 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 @@ def test_strptime(self): 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 @@ def test_more_timetuple(self): 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) diff --git a/tests/test_time.py b/tests/test_time.py index 6ef4c1e..e5820e4 100644 --- a/tests/test_time.py +++ b/tests/test_time.py @@ -220,6 +220,7 @@ def test_1653736(self): 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 @@ def test_strftime(self): 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))