Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pendulum/tz/timezone.py: 33%

210 statements  

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

1from datetime import datetime 

2from datetime import timedelta 

3from datetime import tzinfo 

4from typing import Optional 

5from typing import TypeVar 

6from typing import overload 

7 

8import pendulum 

9 

10from pendulum.helpers import local_time 

11from pendulum.helpers import timestamp 

12from pendulum.utils._compat import _HAS_FOLD 

13 

14from .exceptions import AmbiguousTime 

15from .exceptions import NonExistingTime 

16from .zoneinfo import read 

17from .zoneinfo import read_file 

18from .zoneinfo.transition import Transition 

19 

20 

21POST_TRANSITION = "post" 

22PRE_TRANSITION = "pre" 

23TRANSITION_ERROR = "error" 

24 

25_datetime = datetime 

26_D = TypeVar("_D", bound=datetime) 

27 

28 

29class Timezone(tzinfo): 

30 """ 

31 Represents a named timezone. 

32 

33 The accepted names are those provided by the IANA time zone database. 

34 

35 >>> from pendulum.tz.timezone import Timezone 

36 >>> tz = Timezone('Europe/Paris') 

37 """ 

38 

39 def __init__(self, name, extended=True): # type: (str, bool) -> None 

40 tz = read(name, extend=extended) 

41 

42 self._name = name 

43 self._transitions = tz.transitions 

44 self._hint = {True: None, False: None} 

45 

46 @property 

47 def name(self): # type: () -> str 

48 return self._name 

49 

50 def convert(self, dt, dst_rule=None): # type: (_D, Optional[str]) -> _D 

51 """ 

52 Converts a datetime in the current timezone. 

53 

54 If the datetime is naive, it will be normalized. 

55 

56 >>> from datetime import datetime 

57 >>> from pendulum import timezone 

58 >>> paris = timezone('Europe/Paris') 

59 >>> dt = datetime(2013, 3, 31, 2, 30, fold=1) 

60 >>> in_paris = paris.convert(dt) 

61 >>> in_paris.isoformat() 

62 '2013-03-31T03:30:00+02:00' 

63 

64 If the datetime is aware, it will be properly converted. 

65 

66 >>> new_york = timezone('America/New_York') 

67 >>> in_new_york = new_york.convert(in_paris) 

68 >>> in_new_york.isoformat() 

69 '2013-03-30T21:30:00-04:00' 

70 """ 

71 if dt.tzinfo is None: 

72 return self._normalize(dt, dst_rule=dst_rule) 

73 

74 return self._convert(dt) 

75 

76 def datetime( 

77 self, year, month, day, hour=0, minute=0, second=0, microsecond=0 

78 ): # type: (int, int, int, int, int, int, int) -> _datetime 

79 """ 

80 Return a normalized datetime for the current timezone. 

81 """ 

82 if _HAS_FOLD: 

83 return self.convert( 

84 datetime(year, month, day, hour, minute, second, microsecond, fold=1) 

85 ) 

86 

87 return self.convert( 

88 datetime(year, month, day, hour, minute, second, microsecond), 

89 dst_rule=POST_TRANSITION, 

90 ) 

91 

92 def _normalize(self, dt, dst_rule=None): # type: (_D, Optional[str]) -> _D 

93 sec = timestamp(dt) 

94 fold = 0 

95 transition = self._lookup_transition(sec) 

96 

97 if not _HAS_FOLD and dst_rule is None: 

98 dst_rule = POST_TRANSITION 

99 

100 if dst_rule is None: 

101 dst_rule = PRE_TRANSITION 

102 if dt.fold == 1: 

103 dst_rule = POST_TRANSITION 

104 

105 if sec < transition.local: 

106 if transition.is_ambiguous(sec): 

107 # Ambiguous time 

108 if dst_rule == TRANSITION_ERROR: 

109 raise AmbiguousTime(dt) 

110 

111 # We set the fold attribute for later 

112 if dst_rule == POST_TRANSITION: 

113 fold = 1 

114 elif transition.previous is not None: 

115 transition = transition.previous 

116 

117 if transition: 

118 if transition.is_ambiguous(sec): 

119 # Ambiguous time 

120 if dst_rule == TRANSITION_ERROR: 

121 raise AmbiguousTime(dt) 

122 

123 # We set the fold attribute for later 

124 if dst_rule == POST_TRANSITION: 

125 fold = 1 

126 elif transition.is_missing(sec): 

127 # Skipped time 

128 if dst_rule == TRANSITION_ERROR: 

129 raise NonExistingTime(dt) 

130 

131 # We adjust accordingly 

132 if dst_rule == POST_TRANSITION: 

133 sec += transition.fix 

134 fold = 1 

135 else: 

136 sec -= transition.fix 

137 

138 kwargs = {"tzinfo": self} 

139 if _HAS_FOLD or isinstance(dt, pendulum.DateTime): 

140 kwargs["fold"] = fold 

141 

142 return dt.__class__(*local_time(sec, 0, dt.microsecond), **kwargs) 

143 

144 def _convert(self, dt): # type: (_D) -> _D 

145 if dt.tzinfo is self: 

146 return self._normalize(dt, dst_rule=POST_TRANSITION) 

147 

148 if not isinstance(dt.tzinfo, Timezone): 

149 return dt.astimezone(self) 

150 

151 stamp = timestamp(dt) 

152 

153 if isinstance(dt.tzinfo, FixedTimezone): 

154 offset = dt.tzinfo.offset 

155 else: 

156 transition = dt.tzinfo._lookup_transition(stamp) 

157 offset = transition.ttype.offset 

158 

159 if stamp < transition.local and transition.previous is not None: 

160 if ( 

161 transition.previous.is_ambiguous(stamp) 

162 and getattr(dt, "fold", 1) == 0 

163 ): 

164 pass 

165 else: 

166 offset = transition.previous.ttype.offset 

167 

168 stamp -= offset 

169 

170 transition = self._lookup_transition(stamp, is_utc=True) 

171 if stamp < transition.at and transition.previous is not None: 

172 transition = transition.previous 

173 

174 offset = transition.ttype.offset 

175 stamp += offset 

176 fold = int(not transition.ttype.is_dst()) 

177 

178 kwargs = {"tzinfo": self} 

179 

180 if _HAS_FOLD or isinstance(dt, pendulum.DateTime): 

181 kwargs["fold"] = fold 

182 

183 return dt.__class__(*local_time(stamp, 0, dt.microsecond), **kwargs) 

184 

185 def _lookup_transition( 

186 self, stamp, is_utc=False 

187 ): # type: (int, bool) -> Transition 

188 lo, hi = 0, len(self._transitions) 

189 hint = self._hint[is_utc] 

190 if hint: 

191 if stamp == hint[0]: 

192 return self._transitions[hint[1]] 

193 elif stamp < hint[0]: 

194 hi = hint[1] 

195 else: 

196 lo = hint[1] 

197 

198 if not is_utc: 

199 while lo < hi: 

200 mid = (lo + hi) // 2 

201 if stamp < self._transitions[mid].to: 

202 hi = mid 

203 else: 

204 lo = mid + 1 

205 else: 

206 while lo < hi: 

207 mid = (lo + hi) // 2 

208 if stamp < self._transitions[mid].at: 

209 hi = mid 

210 else: 

211 lo = mid + 1 

212 

213 if lo >= len(self._transitions): 

214 # Beyond last transition 

215 lo = len(self._transitions) - 1 

216 

217 self._hint[is_utc] = (stamp, lo) 

218 

219 return self._transitions[lo] 

220 

221 @overload 

222 def utcoffset(self, dt): # type: (None) -> None 

223 pass 

224 

225 @overload 

226 def utcoffset(self, dt): # type: (_datetime) -> timedelta 

227 pass 

228 

229 def utcoffset(self, dt): 

230 if dt is None: 

231 return 

232 

233 transition = self._get_transition(dt) 

234 

235 return transition.utcoffset() 

236 

237 def dst( 

238 self, dt # type: Optional[_datetime] 

239 ): # type: (...) -> Optional[timedelta] 

240 if dt is None: 

241 return 

242 

243 transition = self._get_transition(dt) 

244 

245 if not transition.ttype.is_dst(): 

246 return timedelta() 

247 

248 return timedelta(seconds=transition.fix) 

249 

250 def tzname(self, dt): # type: (Optional[_datetime]) -> Optional[str] 

251 if dt is None: 

252 return 

253 

254 transition = self._get_transition(dt) 

255 

256 return transition.ttype.abbreviation 

257 

258 def _get_transition(self, dt): # type: (_datetime) -> Transition 

259 if dt.tzinfo is not None and dt.tzinfo is not self: 

260 dt = dt - dt.utcoffset() 

261 

262 stamp = timestamp(dt) 

263 

264 transition = self._lookup_transition(stamp, is_utc=True) 

265 else: 

266 stamp = timestamp(dt) 

267 

268 transition = self._lookup_transition(stamp) 

269 

270 if stamp < transition.local and transition.previous is not None: 

271 fold = getattr(dt, "fold", 1) 

272 if transition.is_ambiguous(stamp): 

273 if fold == 0: 

274 transition = transition.previous 

275 elif transition.previous.is_ambiguous(stamp) and fold == 0: 

276 pass 

277 else: 

278 transition = transition.previous 

279 

280 return transition 

281 

282 def fromutc(self, dt): # type: (_D) -> _D 

283 stamp = timestamp(dt) 

284 

285 transition = self._lookup_transition(stamp, is_utc=True) 

286 if stamp < transition.at and transition.previous is not None: 

287 transition = transition.previous 

288 

289 stamp += transition.ttype.offset 

290 

291 return dt.__class__(*local_time(stamp, 0, dt.microsecond), tzinfo=self) 

292 

293 def __repr__(self): # type: () -> str 

294 return "Timezone('{}')".format(self._name) 

295 

296 def __getinitargs__(self): # type: () -> tuple 

297 return (self._name,) 

298 

299 

300class FixedTimezone(Timezone): 

301 def __init__(self, offset, name=None): 

302 sign = "-" if offset < 0 else "+" 

303 

304 minutes = offset / 60 

305 hour, minute = divmod(abs(int(minutes)), 60) 

306 

307 if not name: 

308 name = "{0}{1:02d}:{2:02d}".format(sign, hour, minute) 

309 

310 self._name = name 

311 self._offset = offset 

312 self._utcoffset = timedelta(seconds=offset) 

313 

314 @property 

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

316 return self._offset 

317 

318 def _normalize(self, dt, dst_rule=None): # type: (_D, Optional[str]) -> _D 

319 if _HAS_FOLD: 

320 dt = dt.__class__( 

321 dt.year, 

322 dt.month, 

323 dt.day, 

324 dt.hour, 

325 dt.minute, 

326 dt.second, 

327 dt.microsecond, 

328 tzinfo=self, 

329 fold=0, 

330 ) 

331 else: 

332 dt = dt.__class__( 

333 dt.year, 

334 dt.month, 

335 dt.day, 

336 dt.hour, 

337 dt.minute, 

338 dt.second, 

339 dt.microsecond, 

340 tzinfo=self, 

341 ) 

342 

343 return dt 

344 

345 def _convert(self, dt): # type: (_D) -> _D 

346 if dt.tzinfo is not self: 

347 return dt.astimezone(self) 

348 

349 return dt 

350 

351 def utcoffset(self, dt): # type: (Optional[_datetime]) -> timedelta 

352 return self._utcoffset 

353 

354 def dst(self, dt): # type: (Optional[_datetime]) -> timedelta 

355 return timedelta() 

356 

357 def fromutc(self, dt): # type: (_D) -> _D 

358 # Use the stdlib datetime's add method to avoid infinite recursion 

359 return (datetime.__add__(dt, self._utcoffset)).replace(tzinfo=self) 

360 

361 def tzname(self, dt): # type: (Optional[_datetime]) -> Optional[str] 

362 return self._name 

363 

364 def __getinitargs__(self): # type: () -> tuple 

365 return self._offset, self._name 

366 

367 

368class TimezoneFile(Timezone): 

369 def __init__(self, path): 

370 tz = read_file(path) 

371 

372 self._name = "" 

373 self._transitions = tz.transitions 

374 self._hint = {True: None, False: None} 

375 

376 

377UTC = FixedTimezone(0, "UTC")