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.2.7, created at 2023-06-07 06:35 +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 

6 

7from typing import Optional 

8 

9from pendulum.constants import MONTHS_OFFSETS 

10from pendulum.constants import SECS_PER_DAY 

11 

12from .exceptions import InvalidPosixSpec 

13 

14 

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) 

37 

38 

39def posix_spec(spec): # type: (str) -> PosixTimezone 

40 try: 

41 return _posix_spec(spec) 

42 except ValueError: 

43 raise InvalidPosixSpec(spec) 

44 

45 

46def _posix_spec(spec): # type: (str) -> PosixTimezone 

47 m = _spec.match(spec) 

48 if not m: 

49 raise ValueError("Invalid posix spec") 

50 

51 std_abbr = _parse_abbr(m.group("std_abbr")) 

52 std_offset = _parse_offset(m.group("std_offset")) 

53 

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 

62 

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

68 

69 return PosixTimezone(std_abbr, std_offset, dst_abbr, dst_offset, dst_start, dst_end) 

70 

71 

72def _parse_abbr(text): # type: (str) -> str 

73 return text.lstrip("<").rstrip(">") 

74 

75 

76def _parse_offset(text, sign=-1): # type: (str, int) -> int 

77 if text.startswith(("+", "-")): 

78 if text.startswith("-"): 

79 sign *= -1 

80 

81 text = text[1:] 

82 

83 minutes = 0 

84 seconds = 0 

85 

86 parts = text.split(":") 

87 hours = int(parts[0]) 

88 

89 if len(parts) > 1: 

90 minutes = int(parts[1]) 

91 

92 if len(parts) > 2: 

93 seconds = int(parts[2]) 

94 

95 return sign * ((((hours * 60) + minutes) * 60) + seconds) 

96 

97 

98def _parse_rule(rule): # type: (str) -> PosixTransition 

99 klass = NPosixTransition 

100 args = () 

101 

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]) 

108 

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]),) 

117 

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 

124 

125 args += (offset,) 

126 

127 return klass(*args) 

128 

129 

130class PosixTransition(object): 

131 def __init__(self, offset): # type: (int) -> None 

132 self._offset = offset 

133 

134 @property 

135 def offset(self): # type: () -> int 

136 return self._offset 

137 

138 def trans_offset(self, is_leap, jan1_weekday): # type: (bool, int) -> int 

139 raise NotImplementedError() 

140 

141 

142class JPosixTransition(PosixTransition): 

143 def __init__(self, day, offset): # type: (int, int) -> None 

144 self._day = day 

145 

146 super(JPosixTransition, self).__init__(offset) 

147 

148 @property 

149 def day(self): # type: () -> int 

150 """ 

151 day of non-leap year [1:365] 

152 """ 

153 return self._day 

154 

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 

159 

160 return (days * SECS_PER_DAY) + self._offset 

161 

162 

163class NPosixTransition(PosixTransition): 

164 def __init__(self, day, offset): # type: (int, int) -> None 

165 self._day = day 

166 

167 super(NPosixTransition, self).__init__(offset) 

168 

169 @property 

170 def day(self): # type: () -> int 

171 """ 

172 day of year [0:365] 

173 """ 

174 return self._day 

175 

176 def trans_offset(self, is_leap, jan1_weekday): # type: (bool, int) -> int 

177 days = self._day 

178 

179 return (days * SECS_PER_DAY) + self._offset 

180 

181 

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 

188 

189 super(MPosixTransition, self).__init__(offset) 

190 

191 @property 

192 def month(self): # type: () -> int 

193 """ 

194 month of year [1:12] 

195 """ 

196 return self._month 

197 

198 @property 

199 def week(self): # type: () -> int 

200 """ 

201 week of month [1:5] (5==last) 

202 """ 

203 return self._week 

204 

205 @property 

206 def weekday(self): # type: () -> int 

207 """ 

208 0==Sun, ..., 6=Sat 

209 """ 

210 return self._weekday 

211 

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 

221 

222 return (days * SECS_PER_DAY) + self._offset 

223 

224 

225class PosixTimezone: 

226 """ 

227 The entirety of a POSIX-string specified time-zone rule. 

228 

229 The standard abbreviation and offset are always given. 

230 """ 

231 

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 

247 

248 @property 

249 def std_abbr(self): # type: () -> str 

250 return self._std_abbr 

251 

252 @property 

253 def std_offset(self): # type: () -> int 

254 return self._std_offset 

255 

256 @property 

257 def dst_abbr(self): # type: () -> Optional[str] 

258 return self._dst_abbr 

259 

260 @property 

261 def dst_offset(self): # type: () -> Optional[int] 

262 return self._dst_offset 

263 

264 @property 

265 def dst_start(self): # type: () -> Optional[PosixTransition] 

266 return self._dst_start 

267 

268 @property 

269 def dst_end(self): # type: () -> Optional[PosixTransition] 

270 return self._dst_end