Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/arrow/formatter.py: 23%

102 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:17 +0000

1"""Provides the :class:`Arrow <arrow.formatter.DateTimeFormatter>` class, an improved formatter for datetimes.""" 

2 

3import re 

4import sys 

5from datetime import datetime, timedelta 

6from typing import Optional, Pattern, cast 

7 

8from dateutil import tz as dateutil_tz 

9 

10from arrow import locales 

11from arrow.constants import DEFAULT_LOCALE 

12 

13if sys.version_info < (3, 8): # pragma: no cover 

14 from typing_extensions import Final 

15else: 

16 from typing import Final # pragma: no cover 

17 

18 

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" 

29 

30 

31class DateTimeFormatter: 

32 

33 # This pattern matches characters enclosed in square brackets are matched as 

34 # an atomic group. For more info on atomic groups and how to they are 

35 # emulated in Python's re library, see https://stackoverflow.com/a/13577411/2701578 

36 

37 _FORMAT_RE: Final[Pattern[str]] = re.compile( 

38 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)" 

39 ) 

40 

41 locale: locales.Locale 

42 

43 def __init__(self, locale: str = DEFAULT_LOCALE) -> None: 

44 

45 self.locale = locales.get_locale(locale) 

46 

47 def format(cls, dt: datetime, fmt: str) -> str: 

48 

49 # FIXME: _format_token() is nullable 

50 return cls._FORMAT_RE.sub( 

51 lambda m: cast(str, cls._format_token(dt, m.group(0))), fmt 

52 ) 

53 

54 def _format_token(self, dt: datetime, token: Optional[str]) -> Optional[str]: 

55 

56 if token and token.startswith("[") and token.endswith("]"): 

57 return token[1:-1] 

58 

59 if token == "YYYY": 

60 return self.locale.year_full(dt.year) 

61 if token == "YY": 

62 return self.locale.year_abbreviation(dt.year) 

63 

64 if token == "MMMM": 

65 return self.locale.month_name(dt.month) 

66 if token == "MMM": 

67 return self.locale.month_abbreviation(dt.month) 

68 if token == "MM": 

69 return f"{dt.month:02d}" 

70 if token == "M": 

71 return f"{dt.month}" 

72 

73 if token == "DDDD": 

74 return f"{dt.timetuple().tm_yday:03d}" 

75 if token == "DDD": 

76 return f"{dt.timetuple().tm_yday}" 

77 if token == "DD": 

78 return f"{dt.day:02d}" 

79 if token == "D": 

80 return f"{dt.day}" 

81 

82 if token == "Do": 

83 return self.locale.ordinal_number(dt.day) 

84 

85 if token == "dddd": 

86 return self.locale.day_name(dt.isoweekday()) 

87 if token == "ddd": 

88 return self.locale.day_abbreviation(dt.isoweekday()) 

89 if token == "d": 

90 return f"{dt.isoweekday()}" 

91 

92 if token == "HH": 

93 return f"{dt.hour:02d}" 

94 if token == "H": 

95 return f"{dt.hour}" 

96 if token == "hh": 

97 return f"{dt.hour if 0 < dt.hour < 13 else abs(dt.hour - 12):02d}" 

98 if token == "h": 

99 return f"{dt.hour if 0 < dt.hour < 13 else abs(dt.hour - 12)}" 

100 

101 if token == "mm": 

102 return f"{dt.minute:02d}" 

103 if token == "m": 

104 return f"{dt.minute}" 

105 

106 if token == "ss": 

107 return f"{dt.second:02d}" 

108 if token == "s": 

109 return f"{dt.second}" 

110 

111 if token == "SSSSSS": 

112 return f"{dt.microsecond:06d}" 

113 if token == "SSSSS": 

114 return f"{dt.microsecond // 10:05d}" 

115 if token == "SSSS": 

116 return f"{dt.microsecond // 100:04d}" 

117 if token == "SSS": 

118 return f"{dt.microsecond // 1000:03d}" 

119 if token == "SS": 

120 return f"{dt.microsecond // 10000:02d}" 

121 if token == "S": 

122 return f"{dt.microsecond // 100000}" 

123 

124 if token == "X": 

125 return f"{dt.timestamp()}" 

126 

127 if token == "x": 

128 return f"{dt.timestamp() * 1_000_000:.0f}" 

129 

130 if token == "ZZZ": 

131 return dt.tzname() 

132 

133 if token in ["ZZ", "Z"]: 

134 separator = ":" if token == "ZZ" else "" 

135 tz = dateutil_tz.tzutc() if dt.tzinfo is None else dt.tzinfo 

136 # `dt` must be aware object. Otherwise, this line will raise AttributeError 

137 # https://github.com/arrow-py/arrow/pull/883#discussion_r529866834 

138 # datetime awareness: https://docs.python.org/3/library/datetime.html#aware-and-naive-objects 

139 total_minutes = int(cast(timedelta, tz.utcoffset(dt)).total_seconds() / 60) 

140 

141 sign = "+" if total_minutes >= 0 else "-" 

142 total_minutes = abs(total_minutes) 

143 hour, minute = divmod(total_minutes, 60) 

144 

145 return f"{sign}{hour:02d}{separator}{minute:02d}" 

146 

147 if token in ("a", "A"): 

148 return self.locale.meridian(dt.hour, token) 

149 

150 if token == "W": 

151 year, week, day = dt.isocalendar() 

152 return f"{year}-W{week:02d}-{day}"