From: Frank Brehm Date: Wed, 27 Mar 2024 11:06:31 +0000 (+0100) Subject: Adding and using function fromisoformat() in lib/pp_admintools/postfix_chain.py X-Git-Url: https://git.uhu-banane.net/?a=commitdiff_plain;h=bc51703e013e2bdbe350987fb32d22120c6a5d8c;p=pixelpark%2Fpp-admin-tools.git Adding and using function fromisoformat() in lib/pp_admintools/postfix_chain.py --- diff --git a/lib/pp_admintools/postfix_chain.py b/lib/pp_admintools/postfix_chain.py index 428e2b5..72c51ca 100644 --- a/lib/pp_admintools/postfix_chain.py +++ b/lib/pp_admintools/postfix_chain.py @@ -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\d{4})-?(?P[01]\d)-?(?P[0-3]\d)' +PATTERN_ISOTIME = ( + r'(?P[0-2]\d):?(?P[0-5]\d)(?::?(?P[0-5]\d)(\.(?P\d+))?)?') +PATTERN_ISOTIMEZONE = r'((?PZ)|(?P[+-][01]\d)(?::?(?P[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\d{4})-?(?P[01]\d)-?(?P[0-3]\d)' - pattern_isotime = ( - r'(?P[0-2]\d):?(?P[0-5]\d)(?::?(?P[0-5]\d)(\.(?P\d+))?)?') - pattern_isotimezone = r'((?PZ)|(?P[+-][01]\d)(?::?(?P[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