mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-01-06 11:09:57 -08:00
c1b8be0227
* Bump arrow from 1.2.3 to 1.3.0 Bumps [arrow](https://github.com/arrow-py/arrow) from 1.2.3 to 1.3.0. - [Release notes](https://github.com/arrow-py/arrow/releases) - [Changelog](https://github.com/arrow-py/arrow/blob/master/CHANGELOG.rst) - [Commits](https://github.com/arrow-py/arrow/compare/1.2.3...1.3.0) --- updated-dependencies: - dependency-name: arrow dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * Update arrow==1.3.0 --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> [skip ci]
149 lines
5.1 KiB
Python
149 lines
5.1 KiB
Python
"""Provides the :class:`Arrow <arrow.formatter.DateTimeFormatter>` class, an improved formatter for datetimes."""
|
|
|
|
import re
|
|
import sys
|
|
from datetime import datetime, timedelta
|
|
from typing import Optional, Pattern, cast
|
|
|
|
from dateutil import tz as dateutil_tz
|
|
|
|
from arrow import locales
|
|
from arrow.constants import DEFAULT_LOCALE
|
|
|
|
if sys.version_info < (3, 8): # pragma: no cover
|
|
from typing_extensions import Final
|
|
else:
|
|
from typing import Final # pragma: no cover
|
|
|
|
|
|
FORMAT_ATOM: Final[str] = "YYYY-MM-DD HH:mm:ssZZ"
|
|
FORMAT_COOKIE: Final[str] = "dddd, DD-MMM-YYYY HH:mm:ss ZZZ"
|
|
FORMAT_RFC822: Final[str] = "ddd, DD MMM YY HH:mm:ss Z"
|
|
FORMAT_RFC850: Final[str] = "dddd, DD-MMM-YY HH:mm:ss ZZZ"
|
|
FORMAT_RFC1036: Final[str] = "ddd, DD MMM YY HH:mm:ss Z"
|
|
FORMAT_RFC1123: Final[str] = "ddd, DD MMM YYYY HH:mm:ss Z"
|
|
FORMAT_RFC2822: Final[str] = "ddd, DD MMM YYYY HH:mm:ss Z"
|
|
FORMAT_RFC3339: Final[str] = "YYYY-MM-DD HH:mm:ssZZ"
|
|
FORMAT_RSS: Final[str] = "ddd, DD MMM YYYY HH:mm:ss Z"
|
|
FORMAT_W3C: Final[str] = "YYYY-MM-DD HH:mm:ssZZ"
|
|
|
|
|
|
class DateTimeFormatter:
|
|
# This pattern matches characters enclosed in square brackets are matched as
|
|
# an atomic group. For more info on atomic groups and how to they are
|
|
# emulated in Python's re library, see https://stackoverflow.com/a/13577411/2701578
|
|
|
|
_FORMAT_RE: Final[Pattern[str]] = re.compile(
|
|
r"(\[(?:(?=(?P<literal>[^]]))(?P=literal))*\]|YYY?Y?|MM?M?M?|Do|DD?D?D?|d?dd?d?|HH?|hh?|mm?|ss?|SS?S?S?S?S?|ZZ?Z?|a|A|X|x|W)"
|
|
)
|
|
|
|
locale: locales.Locale
|
|
|
|
def __init__(self, locale: str = DEFAULT_LOCALE) -> None:
|
|
self.locale = locales.get_locale(locale)
|
|
|
|
def format(cls, dt: datetime, fmt: str) -> str:
|
|
# FIXME: _format_token() is nullable
|
|
return cls._FORMAT_RE.sub(
|
|
lambda m: cast(str, cls._format_token(dt, m.group(0))), fmt
|
|
)
|
|
|
|
def _format_token(self, dt: datetime, token: Optional[str]) -> Optional[str]:
|
|
if token and token.startswith("[") and token.endswith("]"):
|
|
return token[1:-1]
|
|
|
|
if token == "YYYY":
|
|
return self.locale.year_full(dt.year)
|
|
if token == "YY":
|
|
return self.locale.year_abbreviation(dt.year)
|
|
|
|
if token == "MMMM":
|
|
return self.locale.month_name(dt.month)
|
|
if token == "MMM":
|
|
return self.locale.month_abbreviation(dt.month)
|
|
if token == "MM":
|
|
return f"{dt.month:02d}"
|
|
if token == "M":
|
|
return f"{dt.month}"
|
|
|
|
if token == "DDDD":
|
|
return f"{dt.timetuple().tm_yday:03d}"
|
|
if token == "DDD":
|
|
return f"{dt.timetuple().tm_yday}"
|
|
if token == "DD":
|
|
return f"{dt.day:02d}"
|
|
if token == "D":
|
|
return f"{dt.day}"
|
|
|
|
if token == "Do":
|
|
return self.locale.ordinal_number(dt.day)
|
|
|
|
if token == "dddd":
|
|
return self.locale.day_name(dt.isoweekday())
|
|
if token == "ddd":
|
|
return self.locale.day_abbreviation(dt.isoweekday())
|
|
if token == "d":
|
|
return f"{dt.isoweekday()}"
|
|
|
|
if token == "HH":
|
|
return f"{dt.hour:02d}"
|
|
if token == "H":
|
|
return f"{dt.hour}"
|
|
if token == "hh":
|
|
return f"{dt.hour if 0 < dt.hour < 13 else abs(dt.hour - 12):02d}"
|
|
if token == "h":
|
|
return f"{dt.hour if 0 < dt.hour < 13 else abs(dt.hour - 12)}"
|
|
|
|
if token == "mm":
|
|
return f"{dt.minute:02d}"
|
|
if token == "m":
|
|
return f"{dt.minute}"
|
|
|
|
if token == "ss":
|
|
return f"{dt.second:02d}"
|
|
if token == "s":
|
|
return f"{dt.second}"
|
|
|
|
if token == "SSSSSS":
|
|
return f"{dt.microsecond:06d}"
|
|
if token == "SSSSS":
|
|
return f"{dt.microsecond // 10:05d}"
|
|
if token == "SSSS":
|
|
return f"{dt.microsecond // 100:04d}"
|
|
if token == "SSS":
|
|
return f"{dt.microsecond // 1000:03d}"
|
|
if token == "SS":
|
|
return f"{dt.microsecond // 10000:02d}"
|
|
if token == "S":
|
|
return f"{dt.microsecond // 100000}"
|
|
|
|
if token == "X":
|
|
return f"{dt.timestamp()}"
|
|
|
|
if token == "x":
|
|
return f"{dt.timestamp() * 1_000_000:.0f}"
|
|
|
|
if token == "ZZZ":
|
|
return dt.tzname()
|
|
|
|
if token in ["ZZ", "Z"]:
|
|
separator = ":" if token == "ZZ" else ""
|
|
tz = dateutil_tz.tzutc() if dt.tzinfo is None else dt.tzinfo
|
|
# `dt` must be aware object. Otherwise, this line will raise AttributeError
|
|
# https://github.com/arrow-py/arrow/pull/883#discussion_r529866834
|
|
# datetime awareness: https://docs.python.org/3/library/datetime.html#aware-and-naive-objects
|
|
total_minutes = int(cast(timedelta, tz.utcoffset(dt)).total_seconds() / 60)
|
|
|
|
sign = "+" if total_minutes >= 0 else "-"
|
|
total_minutes = abs(total_minutes)
|
|
hour, minute = divmod(total_minutes, 60)
|
|
|
|
return f"{sign}{hour:02d}{separator}{minute:02d}"
|
|
|
|
if token in ("a", "A"):
|
|
return self.locale.meridian(dt.hour, token)
|
|
|
|
if token == "W":
|
|
year, week, day = dt.isocalendar()
|
|
return f"{year}-W{week:02d}-{day}"
|