Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/dateutil/tz/_common.py: 24%

161 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-08 06:51 +0000

1from six import PY2 

2 

3from functools import wraps 

4 

5from datetime import datetime, timedelta, tzinfo 

6 

7 

8ZERO = timedelta(0) 

9 

10__all__ = ['tzname_in_python2', 'enfold'] 

11 

12 

13def tzname_in_python2(namefunc): 

14 """Change unicode output into bytestrings in Python 2 

15 

16 tzname() API changed in Python 3. It used to return bytes, but was changed 

17 to unicode strings 

18 """ 

19 if PY2: 

20 @wraps(namefunc) 

21 def adjust_encoding(*args, **kwargs): 

22 name = namefunc(*args, **kwargs) 

23 if name is not None: 

24 name = name.encode() 

25 

26 return name 

27 

28 return adjust_encoding 

29 else: 

30 return namefunc 

31 

32 

33# The following is adapted from Alexander Belopolsky's tz library 

34# https://github.com/abalkin/tz 

35if hasattr(datetime, 'fold'): 

36 # This is the pre-python 3.6 fold situation 

37 def enfold(dt, fold=1): 

38 """ 

39 Provides a unified interface for assigning the ``fold`` attribute to 

40 datetimes both before and after the implementation of PEP-495. 

41 

42 :param fold: 

43 The value for the ``fold`` attribute in the returned datetime. This 

44 should be either 0 or 1. 

45 

46 :return: 

47 Returns an object for which ``getattr(dt, 'fold', 0)`` returns 

48 ``fold`` for all versions of Python. In versions prior to 

49 Python 3.6, this is a ``_DatetimeWithFold`` object, which is a 

50 subclass of :py:class:`datetime.datetime` with the ``fold`` 

51 attribute added, if ``fold`` is 1. 

52 

53 .. versionadded:: 2.6.0 

54 """ 

55 return dt.replace(fold=fold) 

56 

57else: 

58 class _DatetimeWithFold(datetime): 

59 """ 

60 This is a class designed to provide a PEP 495-compliant interface for 

61 Python versions before 3.6. It is used only for dates in a fold, so 

62 the ``fold`` attribute is fixed at ``1``. 

63 

64 .. versionadded:: 2.6.0 

65 """ 

66 __slots__ = () 

67 

68 def replace(self, *args, **kwargs): 

69 """ 

70 Return a datetime with the same attributes, except for those 

71 attributes given new values by whichever keyword arguments are 

72 specified. Note that tzinfo=None can be specified to create a naive 

73 datetime from an aware datetime with no conversion of date and time 

74 data. 

75 

76 This is reimplemented in ``_DatetimeWithFold`` because pypy3 will 

77 return a ``datetime.datetime`` even if ``fold`` is unchanged. 

78 """ 

79 argnames = ( 

80 'year', 'month', 'day', 'hour', 'minute', 'second', 

81 'microsecond', 'tzinfo' 

82 ) 

83 

84 for arg, argname in zip(args, argnames): 

85 if argname in kwargs: 

86 raise TypeError('Duplicate argument: {}'.format(argname)) 

87 

88 kwargs[argname] = arg 

89 

90 for argname in argnames: 

91 if argname not in kwargs: 

92 kwargs[argname] = getattr(self, argname) 

93 

94 dt_class = self.__class__ if kwargs.get('fold', 1) else datetime 

95 

96 return dt_class(**kwargs) 

97 

98 @property 

99 def fold(self): 

100 return 1 

101 

102 def enfold(dt, fold=1): 

103 """ 

104 Provides a unified interface for assigning the ``fold`` attribute to 

105 datetimes both before and after the implementation of PEP-495. 

106 

107 :param fold: 

108 The value for the ``fold`` attribute in the returned datetime. This 

109 should be either 0 or 1. 

110 

111 :return: 

112 Returns an object for which ``getattr(dt, 'fold', 0)`` returns 

113 ``fold`` for all versions of Python. In versions prior to 

114 Python 3.6, this is a ``_DatetimeWithFold`` object, which is a 

115 subclass of :py:class:`datetime.datetime` with the ``fold`` 

116 attribute added, if ``fold`` is 1. 

117 

118 .. versionadded:: 2.6.0 

119 """ 

120 if getattr(dt, 'fold', 0) == fold: 

121 return dt 

122 

123 args = dt.timetuple()[:6] 

124 args += (dt.microsecond, dt.tzinfo) 

125 

126 if fold: 

127 return _DatetimeWithFold(*args) 

128 else: 

129 return datetime(*args) 

130 

131 

132def _validate_fromutc_inputs(f): 

133 """ 

134 The CPython version of ``fromutc`` checks that the input is a ``datetime`` 

135 object and that ``self`` is attached as its ``tzinfo``. 

136 """ 

137 @wraps(f) 

138 def fromutc(self, dt): 

139 if not isinstance(dt, datetime): 

140 raise TypeError("fromutc() requires a datetime argument") 

141 if dt.tzinfo is not self: 

142 raise ValueError("dt.tzinfo is not self") 

143 

144 return f(self, dt) 

145 

146 return fromutc 

147 

148 

149class _tzinfo(tzinfo): 

150 """ 

151 Base class for all ``dateutil`` ``tzinfo`` objects. 

152 """ 

153 

154 def is_ambiguous(self, dt): 

155 """ 

156 Whether or not the "wall time" of a given datetime is ambiguous in this 

157 zone. 

158 

159 :param dt: 

160 A :py:class:`datetime.datetime`, naive or time zone aware. 

161 

162 

163 :return: 

164 Returns ``True`` if ambiguous, ``False`` otherwise. 

165 

166 .. versionadded:: 2.6.0 

167 """ 

168 

169 dt = dt.replace(tzinfo=self) 

170 

171 wall_0 = enfold(dt, fold=0) 

172 wall_1 = enfold(dt, fold=1) 

173 

174 same_offset = wall_0.utcoffset() == wall_1.utcoffset() 

175 same_dt = wall_0.replace(tzinfo=None) == wall_1.replace(tzinfo=None) 

176 

177 return same_dt and not same_offset 

178 

179 def _fold_status(self, dt_utc, dt_wall): 

180 """ 

181 Determine the fold status of a "wall" datetime, given a representation 

182 of the same datetime as a (naive) UTC datetime. This is calculated based 

183 on the assumption that ``dt.utcoffset() - dt.dst()`` is constant for all 

184 datetimes, and that this offset is the actual number of hours separating 

185 ``dt_utc`` and ``dt_wall``. 

186 

187 :param dt_utc: 

188 Representation of the datetime as UTC 

189 

190 :param dt_wall: 

191 Representation of the datetime as "wall time". This parameter must 

192 either have a `fold` attribute or have a fold-naive 

193 :class:`datetime.tzinfo` attached, otherwise the calculation may 

194 fail. 

195 """ 

196 if self.is_ambiguous(dt_wall): 

197 delta_wall = dt_wall - dt_utc 

198 _fold = int(delta_wall == (dt_utc.utcoffset() - dt_utc.dst())) 

199 else: 

200 _fold = 0 

201 

202 return _fold 

203 

204 def _fold(self, dt): 

205 return getattr(dt, 'fold', 0) 

206 

207 def _fromutc(self, dt): 

208 """ 

209 Given a timezone-aware datetime in a given timezone, calculates a 

210 timezone-aware datetime in a new timezone. 

211 

212 Since this is the one time that we *know* we have an unambiguous 

213 datetime object, we take this opportunity to determine whether the 

214 datetime is ambiguous and in a "fold" state (e.g. if it's the first 

215 occurrence, chronologically, of the ambiguous datetime). 

216 

217 :param dt: 

218 A timezone-aware :class:`datetime.datetime` object. 

219 """ 

220 

221 # Re-implement the algorithm from Python's datetime.py 

222 dtoff = dt.utcoffset() 

223 if dtoff is None: 

224 raise ValueError("fromutc() requires a non-None utcoffset() " 

225 "result") 

226 

227 # The original datetime.py code assumes that `dst()` defaults to 

228 # zero during ambiguous times. PEP 495 inverts this presumption, so 

229 # for pre-PEP 495 versions of python, we need to tweak the algorithm. 

230 dtdst = dt.dst() 

231 if dtdst is None: 

232 raise ValueError("fromutc() requires a non-None dst() result") 

233 delta = dtoff - dtdst 

234 

235 dt += delta 

236 # Set fold=1 so we can default to being in the fold for 

237 # ambiguous dates. 

238 dtdst = enfold(dt, fold=1).dst() 

239 if dtdst is None: 

240 raise ValueError("fromutc(): dt.dst gave inconsistent " 

241 "results; cannot convert") 

242 return dt + dtdst 

243 

244 @_validate_fromutc_inputs 

245 def fromutc(self, dt): 

246 """ 

247 Given a timezone-aware datetime in a given timezone, calculates a 

248 timezone-aware datetime in a new timezone. 

249 

250 Since this is the one time that we *know* we have an unambiguous 

251 datetime object, we take this opportunity to determine whether the 

252 datetime is ambiguous and in a "fold" state (e.g. if it's the first 

253 occurrence, chronologically, of the ambiguous datetime). 

254 

255 :param dt: 

256 A timezone-aware :class:`datetime.datetime` object. 

257 """ 

258 dt_wall = self._fromutc(dt) 

259 

260 # Calculate the fold status given the two datetimes. 

261 _fold = self._fold_status(dt, dt_wall) 

262 

263 # Set the default fold value for ambiguous dates 

264 return enfold(dt_wall, fold=_fold) 

265 

266 

267class tzrangebase(_tzinfo): 

268 """ 

269 This is an abstract base class for time zones represented by an annual 

270 transition into and out of DST. Child classes should implement the following 

271 methods: 

272 

273 * ``__init__(self, *args, **kwargs)`` 

274 * ``transitions(self, year)`` - this is expected to return a tuple of 

275 datetimes representing the DST on and off transitions in standard 

276 time. 

277 

278 A fully initialized ``tzrangebase`` subclass should also provide the 

279 following attributes: 

280 * ``hasdst``: Boolean whether or not the zone uses DST. 

281 * ``_dst_offset`` / ``_std_offset``: :class:`datetime.timedelta` objects 

282 representing the respective UTC offsets. 

283 * ``_dst_abbr`` / ``_std_abbr``: Strings representing the timezone short 

284 abbreviations in DST and STD, respectively. 

285 * ``_hasdst``: Whether or not the zone has DST. 

286 

287 .. versionadded:: 2.6.0 

288 """ 

289 def __init__(self): 

290 raise NotImplementedError('tzrangebase is an abstract base class') 

291 

292 def utcoffset(self, dt): 

293 isdst = self._isdst(dt) 

294 

295 if isdst is None: 

296 return None 

297 elif isdst: 

298 return self._dst_offset 

299 else: 

300 return self._std_offset 

301 

302 def dst(self, dt): 

303 isdst = self._isdst(dt) 

304 

305 if isdst is None: 

306 return None 

307 elif isdst: 

308 return self._dst_base_offset 

309 else: 

310 return ZERO 

311 

312 @tzname_in_python2 

313 def tzname(self, dt): 

314 if self._isdst(dt): 

315 return self._dst_abbr 

316 else: 

317 return self._std_abbr 

318 

319 def fromutc(self, dt): 

320 """ Given a datetime in UTC, return local time """ 

321 if not isinstance(dt, datetime): 

322 raise TypeError("fromutc() requires a datetime argument") 

323 

324 if dt.tzinfo is not self: 

325 raise ValueError("dt.tzinfo is not self") 

326 

327 # Get transitions - if there are none, fixed offset 

328 transitions = self.transitions(dt.year) 

329 if transitions is None: 

330 return dt + self.utcoffset(dt) 

331 

332 # Get the transition times in UTC 

333 dston, dstoff = transitions 

334 

335 dston -= self._std_offset 

336 dstoff -= self._std_offset 

337 

338 utc_transitions = (dston, dstoff) 

339 dt_utc = dt.replace(tzinfo=None) 

340 

341 isdst = self._naive_isdst(dt_utc, utc_transitions) 

342 

343 if isdst: 

344 dt_wall = dt + self._dst_offset 

345 else: 

346 dt_wall = dt + self._std_offset 

347 

348 _fold = int(not isdst and self.is_ambiguous(dt_wall)) 

349 

350 return enfold(dt_wall, fold=_fold) 

351 

352 def is_ambiguous(self, dt): 

353 """ 

354 Whether or not the "wall time" of a given datetime is ambiguous in this 

355 zone. 

356 

357 :param dt: 

358 A :py:class:`datetime.datetime`, naive or time zone aware. 

359 

360 

361 :return: 

362 Returns ``True`` if ambiguous, ``False`` otherwise. 

363 

364 .. versionadded:: 2.6.0 

365 """ 

366 if not self.hasdst: 

367 return False 

368 

369 start, end = self.transitions(dt.year) 

370 

371 dt = dt.replace(tzinfo=None) 

372 return (end <= dt < end + self._dst_base_offset) 

373 

374 def _isdst(self, dt): 

375 if not self.hasdst: 

376 return False 

377 elif dt is None: 

378 return None 

379 

380 transitions = self.transitions(dt.year) 

381 

382 if transitions is None: 

383 return False 

384 

385 dt = dt.replace(tzinfo=None) 

386 

387 isdst = self._naive_isdst(dt, transitions) 

388 

389 # Handle ambiguous dates 

390 if not isdst and self.is_ambiguous(dt): 

391 return not self._fold(dt) 

392 else: 

393 return isdst 

394 

395 def _naive_isdst(self, dt, transitions): 

396 dston, dstoff = transitions 

397 

398 dt = dt.replace(tzinfo=None) 

399 

400 if dston < dstoff: 

401 isdst = dston <= dt < dstoff 

402 else: 

403 isdst = not dstoff <= dt < dston 

404 

405 return isdst 

406 

407 @property 

408 def _dst_base_offset(self): 

409 return self._dst_offset - self._std_offset 

410 

411 __hash__ = None 

412 

413 def __ne__(self, other): 

414 return not (self == other) 

415 

416 def __repr__(self): 

417 return "%s(...)" % self.__class__.__name__ 

418 

419 __reduce__ = object.__reduce__