Source code for datetime_formatter.__datetime_format

import attr  # type: ignore
import datetime  # type: ignore
import dateutil.tz  # type: ignore
from holidays.holiday_base import HolidayBase
import re
from string import Formatter

from typing import (
    Dict,
    Optional,
    Protocol,
    Union,
)

from .__datetime import _DateTime

from .__formats import (
    _PARSE_DT_REGEX,
    _PARSE_DT_SUB_REGEX,
    _PARSE_DT_TRANSLATION,
    _SUPPORTED_TRANSLATION_SIZES,
    _SUPPORTED_TRANSLATION_DIRECTIONS,
    _SUPPORTED_DATETIME_OUTPUT_FORMATS,
    _dtformat,
    _dttranslate,
)

__all__ = [
    "dtfmt",
    "dtformat",
    "DateTimeFormatter",
    "DateTimeFormatTimeZoneError",
    "DateTimeFormatTranslationError",
    "DateTimeFormatFieldError",
]


class SupportsToDateTime(Protocol):
    def to_datetime(self) -> datetime.datetime:
        ...  # pragma: no cover


[docs]def dtformat( dt: Union[ str, int, datetime.datetime, datetime.date, datetime.time, SupportsToDateTime, ], fmtstr: str, output_tz: Optional[Union[str, datetime.tzinfo]] = None, holidays: Optional[Union[Dict[str, str], HolidayBase]] = None, ) -> str: if fmtstr is None: return None if not fmtstr.startswith("%") and not fmtstr.endswith("%"): fmtstr = f"%{fmtstr}%" # mypy complains about kw use in the below, which is weird, but best # to skip dtf = DateTimeFormatter(dt, holidays=holidays) # type:ignore if isinstance(output_tz, str): orig_tz = output_tz output_tz = dateutil.tz.gettz(output_tz) if output_tz is None: raise DateTimeFormatTimeZoneError( f"invalid tz specification {orig_tz}, could not convert to " "tzinfo" ) if output_tz is not None: if dtf.dt.dt.tzinfo is None: raise DateTimeFormatTimeZoneError( f"tried to translate to an output timezone ({output_tz}), " "but provided datetime is naive" ) dtf.dt.dt = dtf.dt.dt.astimezone(output_tz) return dtf.format(fmtstr)
dtfmt = dtformat class DateTimeFormatTimeZoneError(Exception): pass class DateTimeFormatTranslationError(Exception): pass class DateTimeFormatFieldError(Exception): pass
[docs]@attr.s(auto_attribs=True) class DateTimeFormatter(Formatter): dt: _DateTime = attr.ib(converter=_DateTime) holidays: Optional[HolidayBase] = None def __call__(self, *args, **kwargs): return self.format(*args, **kwargs)
[docs] def format(self, s, *args, **kwargs): # magic to make sure %%-wrapped are recognized s = re.sub(_PARSE_DT_SUB_REGEX, r"{\1}", s) parse_fields = self.parse(s) for _, fld, _, _ in parse_fields: if fld is None: continue if len(fld) == 0: continue matches = re.match(_PARSE_DT_REGEX, fld) if matches is None: continue fmt = matches.group("format") translation = matches.group("translation") use_dt = _translate_dt(self.dt, translation, self.holidays) if use_dt is None: raise DateTimeFormatTranslationError( f"error in datetime_format specification {fld}, " f"found but could not identify translation {translation}" ) stfmt = _SUPPORTED_DATETIME_OUTPUT_FORMATS.get(fmt, None) if stfmt is None: raise DateTimeFormatFieldError( f"Found format specification {fmt} in date format {fld}, " f"but this is an unsupported specification." ) if isinstance(stfmt, str): kwargs.update({fld: use_dt.strftime(stfmt)}) else: kwargs.update({fld: stfmt(use_dt)}) return super().format(s, *args, **kwargs)
def _get_dir(d): return _SUPPORTED_TRANSLATION_DIRECTIONS[d] def _get_size(s): return _SUPPORTED_TRANSLATION_SIZES[s] def _translate_dt(dt, translation, holidays): if translation is None: return dt.dt trans_matches = re.match(_PARSE_DT_TRANSLATION, translation) if trans_matches is None: return None dir_name = trans_matches.group("dir") num = int(trans_matches.group("num")) size_name = trans_matches.group("size") try: dir = _get_dir(dir_name) size = _get_size(size_name) except KeyError as ke: raise DateTimeFormatTranslationError( f"Error decoding translation: {translation}, error was:\n" f"KeyError: {str(ke)}" ) return dt.translate(size, num * dir, holidays=holidays)