Skip to content

Commit f611e90

Browse files
committed
Implemented Timezone Offset functionality and enabled unit tests
1 parent aceaa35 commit f611e90

File tree

3 files changed

+110
-37
lines changed

3 files changed

+110
-37
lines changed

adafruit_datetime.py

Lines changed: 97 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -665,12 +665,12 @@ def fromisoformat(cls, date_string):
665665
666666
"""
667667
match = _re.match(
668-
r"([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])", date_string
668+
r"([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$", date_string
669669
)
670670
if match:
671671
y, m, d = int(match.group(1)), int(match.group(2)), int(match.group(3))
672672
return cls(y, m, d)
673-
raise ValueError("Not a valid ISO Date")
673+
raise ValueError("Invalid isoformat string")
674674

675675
@classmethod
676676
def today(cls):
@@ -922,6 +922,90 @@ def tzinfo(self):
922922
"""
923923
return self._tzinfo
924924

925+
@staticmethod
926+
def _parse_iso_string(string_to_parse, segments):
927+
results = []
928+
929+
for regex in segments:
930+
match = _re.match(regex, string_to_parse)
931+
if match:
932+
for grp in range(regex.count("(")):
933+
results.append(int(match.group(grp + 1)))
934+
string_to_parse = string_to_parse[len(match.group(0)) :]
935+
elif string_to_parse: # Only raise an error if we're not done yet
936+
raise ValueError("Invalid isoformat string")
937+
if string_to_parse:
938+
raise ValueError("Invalid isoformat string")
939+
return results
940+
941+
# pylint: disable=too-many-locals
942+
@classmethod
943+
def fromisoformat(cls, time_string):
944+
"""Return a time object constructed from an ISO date format.
945+
Valid format is ``HH[:MM[:SS[.fff[fff]]]][+HH:MM[:SS[.ffffff]]]``
946+
947+
"""
948+
match = _re.match(r"(.*)[\-\+]", time_string)
949+
offset_string = None
950+
if match:
951+
offset_string = time_string[len(match.group(1)) :]
952+
time_string = match.group(1)
953+
954+
time_segments = (
955+
r"([0-9][0-9])",
956+
r":([0-9][0-9])",
957+
r":([0-9][0-9])",
958+
r"\.([0-9][0-9][0-9])",
959+
r"([0-9][0-9][0-9])",
960+
)
961+
offset_segments = (
962+
r"([\-\+][0-9][0-9]):([0-9][0-9])",
963+
r":([0-9][0-9])",
964+
r"\.([0-9][0-9][0-9][0-9][0-9][0-9])",
965+
)
966+
967+
results = cls._parse_iso_string(time_string, time_segments)
968+
if len(results) < 1:
969+
raise ValueError("Invalid isoformat string")
970+
if len(results) < len(time_segments):
971+
results += [None] * (len(time_segments) - len(results))
972+
if offset_string:
973+
results += cls._parse_iso_string(offset_string, offset_segments)
974+
975+
hh = results[0]
976+
mm = results[1] if len(results) >= 2 and results[1] is not None else 0
977+
ss = results[2] if len(results) >= 3 and results[2] is not None else 0
978+
us = 0
979+
if len(results) >= 4 and results[3] is not None:
980+
us += results[3] * 1000
981+
if len(results) >= 5 and results[4] is not None:
982+
us += results[4]
983+
tz = None
984+
if len(results) >= 7:
985+
offset_hh = results[5]
986+
multiplier = -1 if offset_hh < 0 else 1
987+
offset_mm = results[6] * multiplier
988+
offset_ss = (results[7] if len(results) >= 8 else 0) * multiplier
989+
offset_us = (results[8] if len(results) >= 9 else 0) * multiplier
990+
offset = timedelta(
991+
hours=offset_hh,
992+
minutes=offset_mm,
993+
seconds=offset_ss,
994+
microseconds=offset_us,
995+
)
996+
tz = timezone(offset, name="utcoffset")
997+
998+
result = cls(
999+
hh,
1000+
mm,
1001+
ss,
1002+
us,
1003+
tz,
1004+
)
1005+
return result
1006+
1007+
# pylint: enable=too-many-locals
1008+
9251009
# Instance methods
9261010
def isoformat(self, timespec="auto"):
9271011
"""Return a string representing the time in ISO 8601 format, one of:
@@ -1222,41 +1306,20 @@ def fromtimestamp(cls, timestamp, tz=None):
12221306
return cls._fromtimestamp(timestamp, tz is not None, tz)
12231307

12241308
@classmethod
1225-
def fromisoformat(cls, date_string, tz=None):
1309+
def fromisoformat(cls, date_string):
12261310
"""Return a datetime object constructed from an ISO date format.
1227-
Valid format is ``YYYY-MM-DD[*HH[:MM[:SS[.fff[fff]]]]]``
1311+
Valid format is ``YYYY-MM-DD[*HH[:MM[:SS[.fff[fff]]]][+HH:MM[:SS[.ffffff]]]]``
12281312
12291313
"""
1230-
match = _re.match(
1231-
(
1232-
r"([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])(T([0-9][0-9]))?(:([0-9][0-9]))?"
1233-
r"(:([0-9][0-9]))?(\.([0-9][0-9][0-9])([0-9][0-9][0-9])?)?"
1234-
),
1235-
date_string,
1236-
)
1237-
if match:
1238-
y, m, d = int(match.group(1)), int(match.group(2)), int(match.group(3))
1239-
hh = int(match.group(5)) if match.group(5) else 0
1240-
mm = int(match.group(5)) if match.group(7) else 0
1241-
ss = int(match.group(9)) if match.group(9) else 0
1242-
us = 0
1243-
print(match.group(10))
1244-
if match.group(11):
1245-
us += int(match.group(11)) * 1000
1246-
if match.group(12):
1247-
us += int(match.group(12))
1248-
result = cls(
1249-
y,
1250-
m,
1251-
d,
1252-
hh,
1253-
mm,
1254-
ss,
1255-
us,
1256-
tz,
1257-
)
1258-
return result
1259-
raise ValueError("Not a valid ISO Date")
1314+
if "T" in date_string:
1315+
date_string, time_string = date_string.split("T")
1316+
dateval = date.fromisoformat(date_string)
1317+
timeval = time.fromisoformat(time_string)
1318+
else:
1319+
dateval = date.fromisoformat(date_string)
1320+
timeval = time()
1321+
1322+
return cls.combine(dateval, timeval)
12601323

12611324
@classmethod
12621325
def now(cls, timezone=None):

tests/test_date.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,19 @@ def test_fromtimestamp(self):
8888
self.assertEqual(d.month, month)
8989
self.assertEqual(d.day, day)
9090

91+
def test_fromisoformat(self):
92+
# Try an arbitrary fixed value.
93+
iso_date_string = "1999-09-19"
94+
d = cpy_date.fromisoformat(iso_date_string)
95+
self.assertEqual(d.year, 1999)
96+
self.assertEqual(d.month, 9)
97+
self.assertEqual(d.day, 19)
98+
99+
def test_fromisoformat_bad_formats(self):
100+
# Try an arbitrary fixed value.
101+
self.assertRaises(ValueError, cpy_date.fromisoformat, "99-09-19")
102+
self.assertRaises(ValueError, cpy_date.fromisoformat, "1999-13-19")
103+
91104
# TODO: Test this when timedelta is added in
92105
@unittest.skip("Skip for CircuitPython - timedelta() not yet implemented.")
93106
def test_today(self):

tests/test_datetime.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1161,8 +1161,6 @@ def test_fromisoformat_timespecs(self):
11611161
dt_rt = self.theclass.fromisoformat(dtstr)
11621162
self.assertEqual(dt, dt_rt)
11631163

1164-
# TODO
1165-
@unittest.skip("fromisoformat not implemented")
11661164
def test_fromisoformat_fails_datetime(self):
11671165
# Test that fromisoformat() fails on invalid values
11681166
bad_strs = [
@@ -1219,7 +1217,6 @@ def test_fromisoformat_utc(self):
12191217

12201218
self.assertIs(dt.tzinfo, timezone.utc)
12211219

1222-
# TODO
12231220
@unittest.skip("fromisoformat not implemented")
12241221
def test_fromisoformat_subclass(self):
12251222
class DateTimeSubclass(self.theclass):

0 commit comments

Comments
 (0)