]> Frank Brehm's Git Trees - pixelpark/pp-admin-tools.git/commitdiff
Adding and using function fromisoformat() in lib/pp_admintools/postfix_chain.py
authorFrank Brehm <frank.brehm@pixelpark.com>
Wed, 27 Mar 2024 11:06:31 +0000 (12:06 +0100)
committerFrank Brehm <frank.brehm@pixelpark.com>
Wed, 27 Mar 2024 11:06:31 +0000 (12:06 +0100)
lib/pp_admintools/postfix_chain.py

index 428e2b5485c6275643aed1655e2dd4ab64165021..72c51caabefea1cba35ce1b06b3e8dbef1dfbdf5 100644 (file)
@@ -25,17 +25,71 @@ from fb_tools.mailaddress import MailAddressList
 from fb_tools.obj import FbGenericBaseObject
 
 # Own modules
+from .errors import DateFormatError
+from .errors import WrongDateIsoformatError
+from .tzlocal import get_localzone
 from .xlate import XLATOR
 
 _ = XLATOR.gettext
 ngettext = XLATOR.ngettext
 
-__version__ = '0.6.0'
+__version__ = '0.6.1'
 
 LOG = logging.getLogger(__name__)
 
 UTC = utc = datetime.timezone(datetime.timedelta(), 'UTC')
 
+PATTERN_ISODATE = r'(?P<year>\d{4})-?(?P<month>[01]\d)-?(?P<day>[0-3]\d)'
+PATTERN_ISOTIME = (
+    r'(?P<hour>[0-2]\d):?(?P<min>[0-5]\d)(?::?(?P<sec>[0-5]\d)(\.(?P<nsec>\d+))?)?')
+PATTERN_ISOTIMEZONE = r'((?P<utc>Z)|(?P<tz_hours>[+-][01]\d)(?::?(?P<tz_mins>[0-5]\d))?)?'
+
+RE_ISODATETIME = re.compile(PATTERN_ISODATE + r'[T\s]' + PATTERN_ISOTIME + PATTERN_ISOTIMEZONE)
+
+
+# =============================================================================
+def fromisoformat(datestr):
+    """Try to convert a string with an ISO-formatted timestamp into a datetime object."""
+    if hasattr(datetime.datetime, 'fromisoformat'):
+        try:
+            ret = datetime.datetime.fromisoformat(datestr)
+            return ret
+        except ValueError as e:
+            raise DateFormatError(str(e))
+
+    match_obj = RE_ISODATETIME.search(datestr)
+    if not match_obj:
+        raise WrongDateIsoformatError(datestr)
+
+    params = {
+        'year': int(match_obj['year']),
+        'month': int(match_obj['month']),
+        'day': int(match_obj['day']),
+        'minute': int(match_obj['min']),
+    }
+    if match_obj['sec'] is not None:
+        params['second'] = int(match_obj['sec'])
+
+    if match_obj['nsec'] is not None:
+        params['microsecond'] = int(round(float('0.' + match_obj['nsec']) * 1000000))
+
+    if match_obj['utc']:
+        params['tzinfo'] = UTC
+    elif match_obj['tz_hours'] is not None:
+        prefix = match_obj['tz_hours'][0]
+        offset = 0
+        if match_obj['tz_mins'] is not None:
+            offset = int(match_obj['tz_mins']) * 60
+        offset += int(match_obj['tz_hours'][1:]) * 3600
+        if prefix == '-':
+            offset *= -1
+
+        params['tzinfo'] = datetime.timezone(datetime.timedelta(seconds=offset))
+    else:
+        params['tzinfo'] = get_localzone()
+
+    return datetime.datetime(**params)
+
 
 # =============================================================================
 class DataPair(object):
@@ -103,14 +157,6 @@ class DataPair(object):
 class PostfixLogchainInfo(FbGenericBaseObject):
     """A class for encapsulating the information from a chain of Postfix log entries."""
 
-    pattern_isodate = r'(?P<year>\d{4})-?(?P<month>[01]\d)-?(?P<day>[0-3]\d)'
-    pattern_isotime = (
-        r'(?P<hour>[0-2]\d):?(?P<min>[0-5]\d)(?::?(?P<sec>[0-5]\d)(\.(?P<nsec>\d+))?)?')
-    pattern_isotimezone = r'((?P<utc>Z)|(?P<tz_hours>[+-][01]\d)(?::?(?P<tz_mins>[0-5]\d))?)?'
-
-    re_isodatetime = re.compile(
-        pattern_isodate + r'[T\s]' + pattern_isotime + pattern_isotimezone)
-
     warn_on_parse_error = True
 
     attributes = (
@@ -159,41 +205,6 @@ class PostfixLogchainInfo(FbGenericBaseObject):
         """Append the given mail address to the list of TO addresses."""
         self.to_addresses.append(address)
 
-    # -------------------------------------------------------------------------
-    @classmethod
-    def date_fromisoformat(cls, datestr):
-        """Try to convert a string with an ISO-fromatted timestamp into a datetime object."""
-        m = cls.re_isodatetime.search(datestr)
-        if not m:
-            return None
-
-        params = {
-            'year': int(m['year']),
-            'month': int(m['month']),
-            'day': int(m['day']),
-            'minute': int(m['min']),
-        }
-        if m['sec'] is not None:
-            params['second'] = int(m['sec'])
-
-        if m['nsec'] is not None:
-            params['microsecond'] = int(round(float('0.' + m['nsec']) * 1000000))
-
-        if m['utc']:
-            params['tzinfo'] = UTC
-        elif m['tz_hours'] is not None:
-            prefix = m['tz_hours'][0]
-            offset = 0
-            if m['tz_mins'] is not None:
-                offset = int(m['tz_mins']) * 60
-            offset += int(m['tz_hours'][1:]) * 3600
-            if prefix == '-':
-                offset *= -1
-
-            params['tzinfo'] = datetime.timezone(datetime.timedelta(seconds=offset))
-
-        return datetime.datetime(**params)
-
     # -----------------------------------------------------------
     @property
     def auth(self):
@@ -370,10 +381,15 @@ class PostfixLogchainInfo(FbGenericBaseObject):
             self._end = None
             return
 
-        if hasattr(datetime.datetime, 'fromisoformat'):
-            ts_end = datetime.datetime.fromisoformat(val)
-        else:
-            ts_end = self.date_fromisoformat(val)
+        try:
+            ts_end = fromisoformat(val)
+        except DateFormatError as e:
+            msg = _('Could not interprete date {!r}:').format(val) + ' ' + str(e)
+            if self.warn_on_parse_error:
+                LOG.warn(msg)
+            else:
+                LOG.debug(msg)
+            ts_end = None
         if ts_end:
             self._end = ts_end
             return
@@ -589,10 +605,15 @@ class PostfixLogchainInfo(FbGenericBaseObject):
             self._start = None
             return
 
-        if hasattr(datetime.datetime, 'fromisoformat'):
-            ts_start = datetime.datetime.fromisoformat(val)
-        else:
-            ts_start = self.date_fromisoformat(val)
+        try:
+            ts_start = fromisoformat(val)
+        except DateFormatError as e:
+            msg = _('Could not interprete date {!r}:').format(val) + ' ' + str(e)
+            if self.warn_on_parse_error:
+                LOG.warn(msg)
+            else:
+                LOG.debug(msg)
+            ts_start = None
         if ts_start:
             self._start = ts_start
             return