Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/arrow/formatter.py: 23%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""Provides the :class:`Arrow <arrow.formatter.DateTimeFormatter>` class, an improved formatter for datetimes."""
3import re
4import sys
5from datetime import datetime, timedelta
6from typing import Optional, Pattern, cast
8from dateutil import tz as dateutil_tz
10from arrow import locales
11from arrow.constants import DEFAULT_LOCALE
13if sys.version_info < (3, 8): # pragma: no cover
14 from typing_extensions import Final
15else:
16 from typing import Final # pragma: no cover
19FORMAT_ATOM: Final[str] = "YYYY-MM-DD HH:mm:ssZZ"
20FORMAT_COOKIE: Final[str] = "dddd, DD-MMM-YYYY HH:mm:ss ZZZ"
21FORMAT_RFC822: Final[str] = "ddd, DD MMM YY HH:mm:ss Z"
22FORMAT_RFC850: Final[str] = "dddd, DD-MMM-YY HH:mm:ss ZZZ"
23FORMAT_RFC1036: Final[str] = "ddd, DD MMM YY HH:mm:ss Z"
24FORMAT_RFC1123: Final[str] = "ddd, DD MMM YYYY HH:mm:ss Z"
25FORMAT_RFC2822: Final[str] = "ddd, DD MMM YYYY HH:mm:ss Z"
26FORMAT_RFC3339: Final[str] = "YYYY-MM-DD HH:mm:ssZZ"
27FORMAT_RSS: Final[str] = "ddd, DD MMM YYYY HH:mm:ss Z"
28FORMAT_W3C: Final[str] = "YYYY-MM-DD HH:mm:ssZZ"
31class DateTimeFormatter:
32 # This pattern matches characters enclosed in square brackets are matched as
33 # an atomic group. For more info on atomic groups and how to they are
34 # emulated in Python's re library, see https://stackoverflow.com/a/13577411/2701578
36 _FORMAT_RE: Final[Pattern[str]] = re.compile(
37 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)"
38 )
40 locale: locales.Locale
42 def __init__(self, locale: str = DEFAULT_LOCALE) -> None:
43 self.locale = locales.get_locale(locale)
45 def format(cls, dt: datetime, fmt: str) -> str:
46 # FIXME: _format_token() is nullable
47 return cls._FORMAT_RE.sub(
48 lambda m: cast(str, cls._format_token(dt, m.group(0))), fmt
49 )
51 def _format_token(self, dt: datetime, token: Optional[str]) -> Optional[str]:
52 if token and token.startswith("[") and token.endswith("]"):
53 return token[1:-1]
55 if token == "YYYY":
56 return self.locale.year_full(dt.year)
57 if token == "YY":
58 return self.locale.year_abbreviation(dt.year)
60 if token == "MMMM":
61 return self.locale.month_name(dt.month)
62 if token == "MMM":
63 return self.locale.month_abbreviation(dt.month)
64 if token == "MM":
65 return f"{dt.month:02d}"
66 if token == "M":
67 return f"{dt.month}"
69 if token == "DDDD":
70 return f"{dt.timetuple().tm_yday:03d}"
71 if token == "DDD":
72 return f"{dt.timetuple().tm_yday}"
73 if token == "DD":
74 return f"{dt.day:02d}"
75 if token == "D":
76 return f"{dt.day}"
78 if token == "Do":
79 return self.locale.ordinal_number(dt.day)
81 if token == "dddd":
82 return self.locale.day_name(dt.isoweekday())
83 if token == "ddd":
84 return self.locale.day_abbreviation(dt.isoweekday())
85 if token == "d":
86 return f"{dt.isoweekday()}"
88 if token == "HH":
89 return f"{dt.hour:02d}"
90 if token == "H":
91 return f"{dt.hour}"
92 if token == "hh":
93 return f"{dt.hour if 0 < dt.hour < 13 else abs(dt.hour - 12):02d}"
94 if token == "h":
95 return f"{dt.hour if 0 < dt.hour < 13 else abs(dt.hour - 12)}"
97 if token == "mm":
98 return f"{dt.minute:02d}"
99 if token == "m":
100 return f"{dt.minute}"
102 if token == "ss":
103 return f"{dt.second:02d}"
104 if token == "s":
105 return f"{dt.second}"
107 if token == "SSSSSS":
108 return f"{dt.microsecond:06d}"
109 if token == "SSSSS":
110 return f"{dt.microsecond // 10:05d}"
111 if token == "SSSS":
112 return f"{dt.microsecond // 100:04d}"
113 if token == "SSS":
114 return f"{dt.microsecond // 1000:03d}"
115 if token == "SS":
116 return f"{dt.microsecond // 10000:02d}"
117 if token == "S":
118 return f"{dt.microsecond // 100000}"
120 if token == "X":
121 return f"{dt.timestamp()}"
123 if token == "x":
124 return f"{dt.timestamp() * 1_000_000:.0f}"
126 if token == "ZZZ":
127 return dt.tzname()
129 if token in ["ZZ", "Z"]:
130 separator = ":" if token == "ZZ" else ""
131 tz = dateutil_tz.tzutc() if dt.tzinfo is None else dt.tzinfo
132 # `dt` must be aware object. Otherwise, this line will raise AttributeError
133 # https://github.com/arrow-py/arrow/pull/883#discussion_r529866834
134 # datetime awareness: https://docs.python.org/3/library/datetime.html#aware-and-naive-objects
135 total_minutes = int(cast(timedelta, tz.utcoffset(dt)).total_seconds() / 60)
137 sign = "+" if total_minutes >= 0 else "-"
138 total_minutes = abs(total_minutes)
139 hour, minute = divmod(total_minutes, 60)
141 return f"{sign}{hour:02d}{separator}{minute:02d}"
143 if token in ("a", "A"):
144 return self.locale.meridian(dt.hour, token)
146 if token == "W":
147 year, week, day = dt.isocalendar()
148 return f"{year}-W{week:02d}-{day}"