Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pendulum/tz/zoneinfo/posix_timezone.py: 33%
148 statements
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
1"""
2Parsing of a POSIX zone spec as described in the TZ part of section 8.3 in
3http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html.
4"""
5import re
7from typing import Optional
9from pendulum.constants import MONTHS_OFFSETS
10from pendulum.constants import SECS_PER_DAY
12from .exceptions import InvalidPosixSpec
15_spec = re.compile(
16 "^"
17 r"(?P<std_abbr><.*?>|[^-+,\d]{3,})"
18 r"(?P<std_offset>([+-])?(\d{1,2})(:\d{2}(:\d{2})?)?)"
19 r"(?P<dst_info>"
20 r" (?P<dst_abbr><.*?>|[^-+,\d]{3,})"
21 r" (?P<dst_offset>([+-])?(\d{1,2})(:\d{2}(:\d{2})?)?)?"
22 r")?"
23 r"(?:,(?P<rules>"
24 r" (?P<dst_start>"
25 r" (?:J\d+|\d+|M\d{1,2}.\d.[0-6])"
26 r" (?:/(?P<dst_start_offset>([+-])?(\d+)(:\d{2}(:\d{2})?)?))?"
27 " )"
28 " ,"
29 r" (?P<dst_end>"
30 r" (?:J\d+|\d+|M\d{1,2}.\d.[0-6])"
31 r" (?:/(?P<dst_end_offset>([+-])?(\d+)(:\d{2}(:\d{2})?)?))?"
32 " )"
33 "))?"
34 "$",
35 re.VERBOSE,
36)
39def posix_spec(spec): # type: (str) -> PosixTimezone
40 try:
41 return _posix_spec(spec)
42 except ValueError:
43 raise InvalidPosixSpec(spec)
46def _posix_spec(spec): # type: (str) -> PosixTimezone
47 m = _spec.match(spec)
48 if not m:
49 raise ValueError("Invalid posix spec")
51 std_abbr = _parse_abbr(m.group("std_abbr"))
52 std_offset = _parse_offset(m.group("std_offset"))
54 dst_abbr = None
55 dst_offset = None
56 if m.group("dst_info"):
57 dst_abbr = _parse_abbr(m.group("dst_abbr"))
58 if m.group("dst_offset"):
59 dst_offset = _parse_offset(m.group("dst_offset"))
60 else:
61 dst_offset = std_offset + 3600
63 dst_start = None
64 dst_end = None
65 if m.group("rules"):
66 dst_start = _parse_rule(m.group("dst_start"))
67 dst_end = _parse_rule(m.group("dst_end"))
69 return PosixTimezone(std_abbr, std_offset, dst_abbr, dst_offset, dst_start, dst_end)
72def _parse_abbr(text): # type: (str) -> str
73 return text.lstrip("<").rstrip(">")
76def _parse_offset(text, sign=-1): # type: (str, int) -> int
77 if text.startswith(("+", "-")):
78 if text.startswith("-"):
79 sign *= -1
81 text = text[1:]
83 minutes = 0
84 seconds = 0
86 parts = text.split(":")
87 hours = int(parts[0])
89 if len(parts) > 1:
90 minutes = int(parts[1])
92 if len(parts) > 2:
93 seconds = int(parts[2])
95 return sign * ((((hours * 60) + minutes) * 60) + seconds)
98def _parse_rule(rule): # type: (str) -> PosixTransition
99 klass = NPosixTransition
100 args = ()
102 if rule.startswith("M"):
103 rule = rule[1:]
104 parts = rule.split(".")
105 month = int(parts[0])
106 week = int(parts[1])
107 day = int(parts[2].split("/")[0])
109 args += (month, week, day)
110 klass = MPosixTransition
111 elif rule.startswith("J"):
112 rule = rule[1:]
113 args += (int(rule.split("/")[0]),)
114 klass = JPosixTransition
115 else:
116 args += (int(rule.split("/")[0]),)
118 # Checking offset
119 parts = rule.split("/")
120 if len(parts) > 1:
121 offset = _parse_offset(parts[-1], sign=1)
122 else:
123 offset = 7200
125 args += (offset,)
127 return klass(*args)
130class PosixTransition(object):
131 def __init__(self, offset): # type: (int) -> None
132 self._offset = offset
134 @property
135 def offset(self): # type: () -> int
136 return self._offset
138 def trans_offset(self, is_leap, jan1_weekday): # type: (bool, int) -> int
139 raise NotImplementedError()
142class JPosixTransition(PosixTransition):
143 def __init__(self, day, offset): # type: (int, int) -> None
144 self._day = day
146 super(JPosixTransition, self).__init__(offset)
148 @property
149 def day(self): # type: () -> int
150 """
151 day of non-leap year [1:365]
152 """
153 return self._day
155 def trans_offset(self, is_leap, jan1_weekday): # type: (bool, int) -> int
156 days = self._day
157 if not is_leap or days < MONTHS_OFFSETS[1][3]:
158 days -= 1
160 return (days * SECS_PER_DAY) + self._offset
163class NPosixTransition(PosixTransition):
164 def __init__(self, day, offset): # type: (int, int) -> None
165 self._day = day
167 super(NPosixTransition, self).__init__(offset)
169 @property
170 def day(self): # type: () -> int
171 """
172 day of year [0:365]
173 """
174 return self._day
176 def trans_offset(self, is_leap, jan1_weekday): # type: (bool, int) -> int
177 days = self._day
179 return (days * SECS_PER_DAY) + self._offset
182class MPosixTransition(PosixTransition):
183 def __init__(self, month, week, weekday, offset):
184 # type: (int, int, int, int) -> None
185 self._month = month
186 self._week = week
187 self._weekday = weekday
189 super(MPosixTransition, self).__init__(offset)
191 @property
192 def month(self): # type: () -> int
193 """
194 month of year [1:12]
195 """
196 return self._month
198 @property
199 def week(self): # type: () -> int
200 """
201 week of month [1:5] (5==last)
202 """
203 return self._week
205 @property
206 def weekday(self): # type: () -> int
207 """
208 0==Sun, ..., 6=Sat
209 """
210 return self._weekday
212 def trans_offset(self, is_leap, jan1_weekday): # type: (bool, int) -> int
213 last_week = self._week == 5
214 days = MONTHS_OFFSETS[is_leap][self._month + int(last_week)]
215 weekday = (jan1_weekday + days) % 7
216 if last_week:
217 days -= (weekday + 7 - 1 - self._weekday) % 7 + 1
218 else:
219 days += (self._weekday + 7 - weekday) % 7
220 days += (self._week - 1) * 7
222 return (days * SECS_PER_DAY) + self._offset
225class PosixTimezone:
226 """
227 The entirety of a POSIX-string specified time-zone rule.
229 The standard abbreviation and offset are always given.
230 """
232 def __init__(
233 self,
234 std_abbr, # type: str
235 std_offset, # type: int
236 dst_abbr, # type: Optional[str]
237 dst_offset, # type: Optional[int]
238 dst_start=None, # type: Optional[PosixTransition]
239 dst_end=None, # type: Optional[PosixTransition]
240 ):
241 self._std_abbr = std_abbr
242 self._std_offset = std_offset
243 self._dst_abbr = dst_abbr
244 self._dst_offset = dst_offset
245 self._dst_start = dst_start
246 self._dst_end = dst_end
248 @property
249 def std_abbr(self): # type: () -> str
250 return self._std_abbr
252 @property
253 def std_offset(self): # type: () -> int
254 return self._std_offset
256 @property
257 def dst_abbr(self): # type: () -> Optional[str]
258 return self._dst_abbr
260 @property
261 def dst_offset(self): # type: () -> Optional[int]
262 return self._dst_offset
264 @property
265 def dst_start(self): # type: () -> Optional[PosixTransition]
266 return self._dst_start
268 @property
269 def dst_end(self): # type: () -> Optional[PosixTransition]
270 return self._dst_end