|
29 | 29 | # pylint: disable=too-many-lines
|
30 | 30 | import time as _time
|
31 | 31 | import math as _math
|
| 32 | +import re as _re |
32 | 33 | from micropython import const
|
33 | 34 |
|
34 | 35 | __version__ = "0.0.0-auto.0"
|
|
62 | 63 | )
|
63 | 64 | _DAYNAMES = (None, "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")
|
64 | 65 |
|
| 66 | +_INVALID_ISO_ERROR = "Invalid isoformat string: '{}'" |
| 67 | + |
65 | 68 | # Utility functions - universal
|
66 | 69 | def _cmp(obj_x, obj_y):
|
67 | 70 | return 0 if obj_x == obj_y else 1 if obj_x > obj_y else -1
|
@@ -657,6 +660,20 @@ def fromordinal(cls, ordinal):
|
657 | 660 | y, m, d = _ord2ymd(ordinal)
|
658 | 661 | return cls(y, m, d)
|
659 | 662 |
|
| 663 | + @classmethod |
| 664 | + def fromisoformat(cls, date_string): |
| 665 | + """Return a date object constructed from an ISO date format. |
| 666 | + Valid format is ``YYYY-MM-DD`` |
| 667 | +
|
| 668 | + """ |
| 669 | + match = _re.match( |
| 670 | + r"([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$", date_string |
| 671 | + ) |
| 672 | + if match: |
| 673 | + y, m, d = int(match.group(1)), int(match.group(2)), int(match.group(3)) |
| 674 | + return cls(y, m, d) |
| 675 | + raise ValueError(_INVALID_ISO_ERROR.format(date_string)) |
| 676 | + |
660 | 677 | @classmethod
|
661 | 678 | def today(cls):
|
662 | 679 | """Return the current local date."""
|
@@ -907,6 +924,96 @@ def tzinfo(self):
|
907 | 924 | """
|
908 | 925 | return self._tzinfo
|
909 | 926 |
|
| 927 | + @staticmethod |
| 928 | + def _parse_iso_string(string_to_parse, segments): |
| 929 | + results = [] |
| 930 | + |
| 931 | + remaining_string = string_to_parse |
| 932 | + for regex in segments: |
| 933 | + match = _re.match(regex, remaining_string) |
| 934 | + if match: |
| 935 | + for grp in range(regex.count("(")): |
| 936 | + results.append(int(match.group(grp + 1))) |
| 937 | + remaining_string = remaining_string[len(match.group(0)) :] |
| 938 | + elif remaining_string: # Only raise an error if we're not done yet |
| 939 | + raise ValueError() |
| 940 | + if remaining_string: |
| 941 | + raise ValueError() |
| 942 | + return results |
| 943 | + |
| 944 | + # pylint: disable=too-many-locals |
| 945 | + @classmethod |
| 946 | + def fromisoformat(cls, time_string): |
| 947 | + """Return a time object constructed from an ISO date format. |
| 948 | + Valid format is ``HH[:MM[:SS[.fff[fff]]]][+HH:MM[:SS[.ffffff]]]`` |
| 949 | +
|
| 950 | + """ |
| 951 | + # Store the original string in an error message |
| 952 | + original_string = time_string |
| 953 | + match = _re.match(r"(.*)[\-\+]", time_string) |
| 954 | + offset_string = None |
| 955 | + if match: |
| 956 | + offset_string = time_string[len(match.group(1)) :] |
| 957 | + time_string = match.group(1) |
| 958 | + |
| 959 | + time_segments = ( |
| 960 | + r"([0-9][0-9])", |
| 961 | + r":([0-9][0-9])", |
| 962 | + r":([0-9][0-9])", |
| 963 | + r"\.([0-9][0-9][0-9])", |
| 964 | + r"([0-9][0-9][0-9])", |
| 965 | + ) |
| 966 | + offset_segments = ( |
| 967 | + r"([\-\+][0-9][0-9]):([0-9][0-9])", |
| 968 | + r":([0-9][0-9])", |
| 969 | + r"\.([0-9][0-9][0-9][0-9][0-9][0-9])", |
| 970 | + ) |
| 971 | + |
| 972 | + try: |
| 973 | + results = cls._parse_iso_string(time_string, time_segments) |
| 974 | + if len(results) < 1: |
| 975 | + raise ValueError(_INVALID_ISO_ERROR.format(original_string)) |
| 976 | + if len(results) < len(time_segments): |
| 977 | + results += [None] * (len(time_segments) - len(results)) |
| 978 | + if offset_string: |
| 979 | + results += cls._parse_iso_string(offset_string, offset_segments) |
| 980 | + except ValueError as error: |
| 981 | + raise ValueError(_INVALID_ISO_ERROR.format(original_string)) from error |
| 982 | + |
| 983 | + hh = results[0] |
| 984 | + mm = results[1] if len(results) >= 2 and results[1] is not None else 0 |
| 985 | + ss = results[2] if len(results) >= 3 and results[2] is not None else 0 |
| 986 | + us = 0 |
| 987 | + if len(results) >= 4 and results[3] is not None: |
| 988 | + us += results[3] * 1000 |
| 989 | + if len(results) >= 5 and results[4] is not None: |
| 990 | + us += results[4] |
| 991 | + tz = None |
| 992 | + if len(results) >= 7: |
| 993 | + offset_hh = results[5] |
| 994 | + multiplier = -1 if offset_hh < 0 else 1 |
| 995 | + offset_mm = results[6] * multiplier |
| 996 | + offset_ss = (results[7] if len(results) >= 8 else 0) * multiplier |
| 997 | + offset_us = (results[8] if len(results) >= 9 else 0) * multiplier |
| 998 | + offset = timedelta( |
| 999 | + hours=offset_hh, |
| 1000 | + minutes=offset_mm, |
| 1001 | + seconds=offset_ss, |
| 1002 | + microseconds=offset_us, |
| 1003 | + ) |
| 1004 | + tz = timezone(offset, name="utcoffset") |
| 1005 | + |
| 1006 | + result = cls( |
| 1007 | + hh, |
| 1008 | + mm, |
| 1009 | + ss, |
| 1010 | + us, |
| 1011 | + tz, |
| 1012 | + ) |
| 1013 | + return result |
| 1014 | + |
| 1015 | + # pylint: enable=too-many-locals |
| 1016 | + |
910 | 1017 | # Instance methods
|
911 | 1018 | def isoformat(self, timespec="auto"):
|
912 | 1019 | """Return a string representing the time in ISO 8601 format, one of:
|
@@ -1163,6 +1270,11 @@ def tzinfo(self):
|
1163 | 1270 | """
|
1164 | 1271 | return self._tzinfo
|
1165 | 1272 |
|
| 1273 | + @property |
| 1274 | + def fold(self): |
| 1275 | + """Fold.""" |
| 1276 | + return self._fold |
| 1277 | + |
1166 | 1278 | # Class methods
|
1167 | 1279 |
|
1168 | 1280 | # pylint: disable=protected-access
|
@@ -1206,6 +1318,29 @@ def _fromtimestamp(cls, t, utc, tz):
|
1206 | 1318 | def fromtimestamp(cls, timestamp, tz=None):
|
1207 | 1319 | return cls._fromtimestamp(timestamp, tz is not None, tz)
|
1208 | 1320 |
|
| 1321 | + @classmethod |
| 1322 | + def fromisoformat(cls, date_string): |
| 1323 | + """Return a datetime object constructed from an ISO date format. |
| 1324 | + Valid format is ``YYYY-MM-DD[*HH[:MM[:SS[.fff[fff]]]][+HH:MM[:SS[.ffffff]]]]`` |
| 1325 | +
|
| 1326 | + """ |
| 1327 | + original_string = date_string |
| 1328 | + |
| 1329 | + time_string = None |
| 1330 | + try: |
| 1331 | + if len(date_string) > 10: |
| 1332 | + time_string = date_string[11:] |
| 1333 | + date_string = date_string[:10] |
| 1334 | + dateval = date.fromisoformat(date_string) |
| 1335 | + timeval = time.fromisoformat(time_string) |
| 1336 | + else: |
| 1337 | + dateval = date.fromisoformat(date_string) |
| 1338 | + timeval = time() |
| 1339 | + except ValueError as error: |
| 1340 | + raise ValueError(_INVALID_ISO_ERROR.format(original_string)) from error |
| 1341 | + |
| 1342 | + return cls.combine(dateval, timeval) |
| 1343 | + |
1209 | 1344 | @classmethod
|
1210 | 1345 | def now(cls, timezone=None):
|
1211 | 1346 | """Return the current local date and time."""
|
|
0 commit comments