Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/dateutil/tz/tz.py: 21%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

637 statements  

1# -*- coding: utf-8 -*- 

2""" 

3This module offers timezone implementations subclassing the abstract 

4:py:class:`datetime.tzinfo` type. There are classes to handle tzfile format 

5files (usually are in :file:`/etc/localtime`, :file:`/usr/share/zoneinfo`, 

6etc), TZ environment string (in all known formats), given ranges (with help 

7from relative deltas), local machine timezone, fixed offset timezone, and UTC 

8timezone. 

9""" 

10 

11import bisect 

12import datetime 

13import os 

14import struct 

15import sys 

16import time 

17import warnings 

18import weakref 

19from collections import OrderedDict 

20 

21import six 

22from six import string_types 

23from six.moves import _thread 

24 

25from .. import _tzdata_impl 

26from . import _tzpath 

27from ._common import ( 

28 _tzinfo, 

29 _validate_fromutc_inputs, 

30 enfold, 

31 tzname_in_python2, 

32 tzrangebase, 

33) 

34from ._factories import _TzOffsetFactory, _TzSingleton, _TzStrFactory 

35from ._tzfile import tzfile 

36 

37try: 

38 from .win import tzwin, tzwinlocal 

39except ImportError: 

40 tzwin = tzwinlocal = None 

41 

42# For warning about rounding tzinfo 

43from warnings import warn 

44 

45ZERO = datetime.timedelta(0) 

46EPOCH = datetime.datetime(1970, 1, 1, 0, 0) 

47 

48 

49@six.add_metaclass(_TzSingleton) 

50class tzutc(datetime.tzinfo): 

51 """ 

52 This is a tzinfo object that represents the UTC time zone. 

53 

54 **Examples:** 

55 

56 .. doctest:: 

57 

58 >>> from datetime import * 

59 >>> from dateutil.tz import * 

60 

61 >>> datetime.now() 

62 datetime.datetime(2003, 9, 27, 9, 40, 1, 521290) 

63 

64 >>> datetime.now(tzutc()) 

65 datetime.datetime(2003, 9, 27, 12, 40, 12, 156379, tzinfo=tzutc()) 

66 

67 >>> datetime.now(tzutc()).tzname() 

68 'UTC' 

69 

70 .. versionchanged:: 2.7.0 

71 ``tzutc()`` is now a singleton, so the result of ``tzutc()`` will 

72 always return the same object. 

73 

74 .. doctest:: 

75 

76 >>> from dateutil.tz import tzutc, UTC 

77 >>> tzutc() is tzutc() 

78 True 

79 >>> tzutc() is UTC 

80 True 

81 """ 

82 def utcoffset(self, dt): 

83 return ZERO 

84 

85 def dst(self, dt): 

86 return ZERO 

87 

88 @tzname_in_python2 

89 def tzname(self, dt): 

90 return "UTC" 

91 

92 def is_ambiguous(self, dt): 

93 """ 

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

95 zone. 

96 

97 :param dt: 

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

99 

100 

101 :return: 

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

103 

104 .. versionadded:: 2.6.0 

105 """ 

106 return False 

107 

108 @_validate_fromutc_inputs 

109 def fromutc(self, dt): 

110 """ 

111 Fast track version of fromutc() returns the original ``dt`` object for 

112 any valid :py:class:`datetime.datetime` object. 

113 """ 

114 return dt 

115 

116 def __eq__(self, other): 

117 if not isinstance(other, (tzutc, tzoffset)): 

118 return NotImplemented 

119 

120 return (isinstance(other, tzutc) or 

121 (isinstance(other, tzoffset) and other._offset == ZERO)) 

122 

123 __hash__ = None 

124 

125 def __ne__(self, other): 

126 return not (self == other) 

127 

128 def __repr__(self): 

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

130 

131 __reduce__ = object.__reduce__ 

132 

133 

134#: Convenience constant providing a :class:`tzutc()` instance 

135#: 

136#: .. versionadded:: 2.7.0 

137UTC = tzutc() 

138 

139 

140@six.add_metaclass(_TzOffsetFactory) 

141class tzoffset(datetime.tzinfo): 

142 """ 

143 A simple class for representing a fixed offset from UTC. 

144 

145 :param name: 

146 The timezone name, to be returned when ``tzname()`` is called. 

147 :param offset: 

148 The time zone offset in seconds, or (since version 2.6.0, represented 

149 as a :py:class:`datetime.timedelta` object). 

150 """ 

151 def __init__(self, name, offset): 

152 self._name = name 

153 

154 try: 

155 # Allow a timedelta 

156 offset = offset.total_seconds() 

157 except (TypeError, AttributeError): 

158 pass 

159 

160 self._offset = datetime.timedelta(seconds=_get_supported_offset(offset)) 

161 

162 def utcoffset(self, dt): 

163 return self._offset 

164 

165 def dst(self, dt): 

166 return ZERO 

167 

168 @tzname_in_python2 

169 def tzname(self, dt): 

170 return self._name 

171 

172 @_validate_fromutc_inputs 

173 def fromutc(self, dt): 

174 return dt + self._offset 

175 

176 def is_ambiguous(self, dt): 

177 """ 

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

179 zone. 

180 

181 :param dt: 

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

183 :return: 

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

185 

186 .. versionadded:: 2.6.0 

187 """ 

188 return False 

189 

190 def __eq__(self, other): 

191 if not isinstance(other, tzoffset): 

192 return NotImplemented 

193 

194 return self._offset == other._offset 

195 

196 __hash__ = None 

197 

198 def __ne__(self, other): 

199 return not (self == other) 

200 

201 def __repr__(self): 

202 return "%s(%s, %s)" % (self.__class__.__name__, 

203 repr(self._name), 

204 int(self._offset.total_seconds())) 

205 

206 __reduce__ = object.__reduce__ 

207 

208 

209class tzlocal(_tzinfo): 

210 """ 

211 A :class:`tzinfo` subclass built around the ``time`` timezone functions. 

212 """ 

213 def __init__(self): 

214 super(tzlocal, self).__init__() 

215 

216 self._std_offset = datetime.timedelta(seconds=-time.timezone) 

217 if time.daylight: 

218 self._dst_offset = datetime.timedelta(seconds=-time.altzone) 

219 else: 

220 self._dst_offset = self._std_offset 

221 

222 self._dst_saved = self._dst_offset - self._std_offset 

223 self._hasdst = bool(self._dst_saved) 

224 self._tznames = tuple(time.tzname) 

225 

226 def utcoffset(self, dt): 

227 if dt is None and self._hasdst: 

228 return None 

229 

230 if self._isdst(dt): 

231 return self._dst_offset 

232 else: 

233 return self._std_offset 

234 

235 def dst(self, dt): 

236 if dt is None and self._hasdst: 

237 return None 

238 

239 if self._isdst(dt): 

240 return self._dst_offset - self._std_offset 

241 else: 

242 return ZERO 

243 

244 @tzname_in_python2 

245 def tzname(self, dt): 

246 return self._tznames[self._isdst(dt)] 

247 

248 def is_ambiguous(self, dt): 

249 """ 

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

251 zone. 

252 

253 :param dt: 

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

255 

256 

257 :return: 

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

259 

260 .. versionadded:: 2.6.0 

261 """ 

262 naive_dst = self._naive_is_dst(dt) 

263 return (not naive_dst and 

264 (naive_dst != self._naive_is_dst(dt - self._dst_saved))) 

265 

266 def _naive_is_dst(self, dt): 

267 timestamp = _datetime_to_timestamp(dt) 

268 return time.localtime(timestamp + time.timezone).tm_isdst 

269 

270 def _isdst(self, dt, fold_naive=True): 

271 # We can't use mktime here. It is unstable when deciding if 

272 # the hour near to a change is DST or not. 

273 # 

274 # timestamp = time.mktime((dt.year, dt.month, dt.day, dt.hour, 

275 # dt.minute, dt.second, dt.weekday(), 0, -1)) 

276 # return time.localtime(timestamp).tm_isdst 

277 # 

278 # The code above yields the following result: 

279 # 

280 # >>> import tz, datetime 

281 # >>> t = tz.tzlocal() 

282 # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname() 

283 # 'BRDT' 

284 # >>> datetime.datetime(2003,2,16,0,tzinfo=t).tzname() 

285 # 'BRST' 

286 # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname() 

287 # 'BRST' 

288 # >>> datetime.datetime(2003,2,15,22,tzinfo=t).tzname() 

289 # 'BRDT' 

290 # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname() 

291 # 'BRDT' 

292 # 

293 # Here is a more stable implementation: 

294 # 

295 if not self._hasdst: 

296 return False 

297 

298 # Check for ambiguous times: 

299 dstval = self._naive_is_dst(dt) 

300 fold = getattr(dt, 'fold', None) 

301 

302 if self.is_ambiguous(dt): 

303 if fold is not None: 

304 return not self._fold(dt) 

305 else: 

306 return True 

307 

308 return dstval 

309 

310 def __eq__(self, other): 

311 if isinstance(other, tzlocal): 

312 return (self._std_offset == other._std_offset and 

313 self._dst_offset == other._dst_offset) 

314 elif isinstance(other, tzutc): 

315 return (not self._hasdst and 

316 self._tznames[0] in {'UTC', 'GMT'} and 

317 self._std_offset == ZERO) 

318 elif isinstance(other, tzoffset): 

319 return (not self._hasdst and 

320 self._tznames[0] == other._name and 

321 self._std_offset == other._offset) 

322 else: 

323 return NotImplemented 

324 

325 __hash__ = None 

326 

327 def __ne__(self, other): 

328 return not (self == other) 

329 

330 def __repr__(self): 

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

332 

333 __reduce__ = object.__reduce__ 

334 

335 

336class tzrange(tzrangebase): 

337 """ 

338 The ``tzrange`` object is a time zone specified by a set of offsets and 

339 abbreviations, equivalent to the way the ``TZ`` variable can be specified 

340 in POSIX-like systems, but using Python delta objects to specify DST 

341 start, end and offsets. 

342 

343 :param stdabbr: 

344 The abbreviation for standard time (e.g. ``'EST'``). 

345 

346 :param stdoffset: 

347 An integer or :class:`datetime.timedelta` object or equivalent 

348 specifying the base offset from UTC. 

349 

350 If unspecified, +00:00 is used. 

351 

352 :param dstabbr: 

353 The abbreviation for DST / "Summer" time (e.g. ``'EDT'``). 

354 

355 If specified, with no other DST information, DST is assumed to occur 

356 and the default behavior or ``dstoffset``, ``start`` and ``end`` is 

357 used. If unspecified and no other DST information is specified, it 

358 is assumed that this zone has no DST. 

359 

360 If this is unspecified and other DST information is *is* specified, 

361 DST occurs in the zone but the time zone abbreviation is left 

362 unchanged. 

363 

364 :param dstoffset: 

365 A an integer or :class:`datetime.timedelta` object or equivalent 

366 specifying the UTC offset during DST. If unspecified and any other DST 

367 information is specified, it is assumed to be the STD offset +1 hour. 

368 

369 :param start: 

370 A :class:`relativedelta.relativedelta` object or equivalent specifying 

371 the time and time of year that daylight savings time starts. To 

372 specify, for example, that DST starts at 2AM on the 2nd Sunday in 

373 March, pass: 

374 

375 ``relativedelta(hours=2, month=3, day=1, weekday=SU(+2))`` 

376 

377 If unspecified and any other DST information is specified, the default 

378 value is 2 AM on the first Sunday in April. 

379 

380 :param end: 

381 A :class:`relativedelta.relativedelta` object or equivalent 

382 representing the time and time of year that daylight savings time 

383 ends, with the same specification method as in ``start``. One note is 

384 that this should point to the first time in the *standard* zone, so if 

385 a transition occurs at 2AM in the DST zone and the clocks are set back 

386 1 hour to 1AM, set the ``hours`` parameter to +1. 

387 

388 

389 **Examples:** 

390 

391 .. testsetup:: tzrange 

392 

393 from dateutil.tz import tzrange, tzstr 

394 

395 .. doctest:: tzrange 

396 

397 >>> tzstr('EST5EDT') == tzrange("EST", -18000, "EDT") 

398 True 

399 

400 >>> from dateutil.relativedelta import * 

401 >>> range1 = tzrange("EST", -18000, "EDT") 

402 >>> range2 = tzrange("EST", -18000, "EDT", -14400, 

403 ... relativedelta(hours=+2, month=4, day=1, 

404 ... weekday=SU(+1)), 

405 ... relativedelta(hours=+1, month=10, day=31, 

406 ... weekday=SU(-1))) 

407 >>> tzstr('EST5EDT') == range1 == range2 

408 True 

409 

410 """ 

411 def __init__(self, stdabbr, stdoffset=None, 

412 dstabbr=None, dstoffset=None, 

413 start=None, end=None): 

414 

415 global relativedelta 

416 from dateutil import relativedelta 

417 

418 self._std_abbr = stdabbr 

419 self._dst_abbr = dstabbr 

420 

421 try: 

422 stdoffset = stdoffset.total_seconds() 

423 except (TypeError, AttributeError): 

424 pass 

425 

426 try: 

427 dstoffset = dstoffset.total_seconds() 

428 except (TypeError, AttributeError): 

429 pass 

430 

431 if stdoffset is not None: 

432 self._std_offset = datetime.timedelta(seconds=stdoffset) 

433 else: 

434 self._std_offset = ZERO 

435 

436 if dstoffset is not None: 

437 self._dst_offset = datetime.timedelta(seconds=dstoffset) 

438 elif dstabbr and stdoffset is not None: 

439 self._dst_offset = self._std_offset + datetime.timedelta(hours=+1) 

440 else: 

441 self._dst_offset = ZERO 

442 

443 if dstabbr and start is None: 

444 self._start_delta = relativedelta.relativedelta( 

445 hours=+2, month=4, day=1, weekday=relativedelta.SU(+1)) 

446 else: 

447 self._start_delta = start 

448 

449 if dstabbr and end is None: 

450 self._end_delta = relativedelta.relativedelta( 

451 hours=+1, month=10, day=31, weekday=relativedelta.SU(-1)) 

452 else: 

453 self._end_delta = end 

454 

455 self._dst_base_offset_ = self._dst_offset - self._std_offset 

456 self.hasdst = bool(self._start_delta) 

457 

458 def transitions(self, year): 

459 """ 

460 For a given year, get the DST on and off transition times, expressed 

461 always on the standard time side. For zones with no transitions, this 

462 function returns ``None``. 

463 

464 :param year: 

465 The year whose transitions you would like to query. 

466 

467 :return: 

468 Returns a :class:`tuple` of :class:`datetime.datetime` objects, 

469 ``(dston, dstoff)`` for zones with an annual DST transition, or 

470 ``None`` for fixed offset zones. 

471 """ 

472 if not self.hasdst: 

473 return None 

474 

475 base_year = datetime.datetime(year, 1, 1) 

476 

477 start = base_year + self._start_delta 

478 end = base_year + self._end_delta 

479 

480 return (start, end) 

481 

482 def __eq__(self, other): 

483 if not isinstance(other, tzrange): 

484 return NotImplemented 

485 

486 return (self._std_abbr == other._std_abbr and 

487 self._dst_abbr == other._dst_abbr and 

488 self._std_offset == other._std_offset and 

489 self._dst_offset == other._dst_offset and 

490 self._start_delta == other._start_delta and 

491 self._end_delta == other._end_delta) 

492 

493 @property 

494 def _dst_base_offset(self): 

495 return self._dst_base_offset_ 

496 

497 

498@six.add_metaclass(_TzStrFactory) 

499class tzstr(tzrange): 

500 """ 

501 ``tzstr`` objects are time zone objects specified by a time-zone string as 

502 it would be passed to a ``TZ`` variable on POSIX-style systems (see 

503 the `GNU C Library: TZ Variable`_ for more details). 

504 

505 There is one notable exception, which is that POSIX-style time zones use an 

506 inverted offset format, so normally ``GMT+3`` would be parsed as an offset 

507 3 hours *behind* GMT. The ``tzstr`` time zone object will parse this as an 

508 offset 3 hours *ahead* of GMT. If you would like to maintain the POSIX 

509 behavior, pass a ``True`` value to ``posix_offset``. 

510 

511 The :class:`tzrange` object provides the same functionality, but is 

512 specified using :class:`relativedelta.relativedelta` objects. rather than 

513 strings. 

514 

515 :param s: 

516 A time zone string in ``TZ`` variable format. This can be a 

517 :class:`bytes` (2.x: :class:`str`), :class:`str` (2.x: 

518 :class:`unicode`) or a stream emitting unicode characters 

519 (e.g. :class:`StringIO`). 

520 

521 :param posix_offset: 

522 Optional. If set to ``True``, interpret strings such as ``GMT+3`` or 

523 ``UTC+3`` as being 3 hours *behind* UTC rather than ahead, per the 

524 POSIX standard. 

525 

526 .. caution:: 

527 

528 Prior to version 2.7.0, this function also supported time zones 

529 in the format: 

530 

531 * ``EST5EDT,4,0,6,7200,10,0,26,7200,3600`` 

532 * ``EST5EDT,4,1,0,7200,10,-1,0,7200,3600`` 

533 

534 This format is non-standard and has been deprecated; this function 

535 will raise a :class:`DeprecatedTZFormatWarning` until 

536 support is removed in a future version. 

537 

538 .. _`GNU C Library: TZ Variable`: 

539 https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html 

540 """ 

541 def __init__(self, s, posix_offset=False): 

542 global parser 

543 from dateutil.parser import _parser as parser 

544 

545 self._s = s 

546 

547 res = parser._parsetz(s) 

548 if res is None or res.any_unused_tokens: 

549 raise ValueError("unknown string format") 

550 

551 # Here we break the compatibility with the TZ variable handling. 

552 # GMT-3 actually *means* the timezone -3. 

553 if res.stdabbr in ("GMT", "UTC") and not posix_offset: 

554 res.stdoffset *= -1 

555 

556 # We must initialize it first, since _delta() needs 

557 # _std_offset and _dst_offset set. Use False in start/end 

558 # to avoid building it two times. 

559 tzrange.__init__(self, res.stdabbr, res.stdoffset, 

560 res.dstabbr, res.dstoffset, 

561 start=False, end=False) 

562 

563 if not res.dstabbr: 

564 self._start_delta = None 

565 self._end_delta = None 

566 else: 

567 self._start_delta = self._delta(res.start) 

568 if self._start_delta: 

569 self._end_delta = self._delta(res.end, isend=1) 

570 

571 self.hasdst = bool(self._start_delta) 

572 

573 def _delta(self, x, isend=0): 

574 from dateutil import relativedelta 

575 kwargs = {} 

576 if x.month is not None: 

577 kwargs["month"] = x.month 

578 if x.weekday is not None: 

579 kwargs["weekday"] = relativedelta.weekday(x.weekday, x.week) 

580 if x.week > 0: 

581 kwargs["day"] = 1 

582 else: 

583 kwargs["day"] = 31 

584 elif x.day: 

585 kwargs["day"] = x.day 

586 elif x.yday is not None: 

587 kwargs["yearday"] = x.yday 

588 elif x.jyday is not None: 

589 kwargs["nlyearday"] = x.jyday 

590 if not kwargs: 

591 # Default is to start on first sunday of april, and end 

592 # on last sunday of october. 

593 if not isend: 

594 kwargs["month"] = 4 

595 kwargs["day"] = 1 

596 kwargs["weekday"] = relativedelta.SU(+1) 

597 else: 

598 kwargs["month"] = 10 

599 kwargs["day"] = 31 

600 kwargs["weekday"] = relativedelta.SU(-1) 

601 if x.time is not None: 

602 kwargs["seconds"] = x.time 

603 else: 

604 # Default is 2AM. 

605 kwargs["seconds"] = 7200 

606 if isend: 

607 # Convert to standard time, to follow the documented way 

608 # of working with the extra hour. See the documentation 

609 # of the tzinfo class. 

610 delta = self._dst_offset - self._std_offset 

611 kwargs["seconds"] -= delta.seconds + delta.days * 86400 

612 return relativedelta.relativedelta(**kwargs) 

613 

614 def __repr__(self): 

615 return "%s(%s)" % (self.__class__.__name__, repr(self._s)) 

616 

617 

618class _tzicalvtzcomp(object): 

619 def __init__(self, tzoffsetfrom, tzoffsetto, isdst, 

620 tzname=None, rrule=None): 

621 self.tzoffsetfrom = datetime.timedelta(seconds=tzoffsetfrom) 

622 self.tzoffsetto = datetime.timedelta(seconds=tzoffsetto) 

623 self.tzoffsetdiff = self.tzoffsetto - self.tzoffsetfrom 

624 self.isdst = isdst 

625 self.tzname = tzname 

626 self.rrule = rrule 

627 

628 

629class _tzicalvtz(_tzinfo): 

630 def __init__(self, tzid, comps=[]): 

631 super(_tzicalvtz, self).__init__() 

632 

633 self._tzid = tzid 

634 self._comps = comps 

635 self._cachedate = [] 

636 self._cachecomp = [] 

637 self._cache_lock = _thread.allocate_lock() 

638 

639 def _find_comp(self, dt): 

640 if len(self._comps) == 1: 

641 return self._comps[0] 

642 

643 dt = dt.replace(tzinfo=None) 

644 

645 try: 

646 with self._cache_lock: 

647 return self._cachecomp[self._cachedate.index( 

648 (dt, self._fold(dt)))] 

649 except ValueError: 

650 pass 

651 

652 lastcompdt = None 

653 lastcomp = None 

654 

655 for comp in self._comps: 

656 compdt = self._find_compdt(comp, dt) 

657 

658 if compdt and (not lastcompdt or lastcompdt < compdt): 

659 lastcompdt = compdt 

660 lastcomp = comp 

661 

662 if not lastcomp: 

663 # RFC says nothing about what to do when a given 

664 # time is before the first onset date. We'll look for the 

665 # first standard component, or the first component, if 

666 # none is found. 

667 for comp in self._comps: 

668 if not comp.isdst: 

669 lastcomp = comp 

670 break 

671 else: 

672 lastcomp = comp[0] 

673 

674 with self._cache_lock: 

675 self._cachedate.insert(0, (dt, self._fold(dt))) 

676 self._cachecomp.insert(0, lastcomp) 

677 

678 if len(self._cachedate) > 10: 

679 self._cachedate.pop() 

680 self._cachecomp.pop() 

681 

682 return lastcomp 

683 

684 def _find_compdt(self, comp, dt): 

685 if comp.tzoffsetdiff < ZERO and self._fold(dt): 

686 dt -= comp.tzoffsetdiff 

687 

688 compdt = comp.rrule.before(dt, inc=True) 

689 

690 return compdt 

691 

692 def utcoffset(self, dt): 

693 if dt is None: 

694 return None 

695 

696 return self._find_comp(dt).tzoffsetto 

697 

698 def dst(self, dt): 

699 comp = self._find_comp(dt) 

700 if comp.isdst: 

701 return comp.tzoffsetdiff 

702 else: 

703 return ZERO 

704 

705 @tzname_in_python2 

706 def tzname(self, dt): 

707 return self._find_comp(dt).tzname 

708 

709 def __repr__(self): 

710 return "<tzicalvtz %s>" % repr(self._tzid) 

711 

712 __reduce__ = object.__reduce__ 

713 

714 

715class tzical(object): 

716 """ 

717 This object is designed to parse an iCalendar-style ``VTIMEZONE`` structure 

718 as set out in `RFC 5545`_ Section 4.6.5 into one or more `tzinfo` objects. 

719 

720 :param `fileobj`: 

721 A file or stream in iCalendar format, which should be UTF-8 encoded 

722 with CRLF endings. 

723 

724 .. _`RFC 5545`: https://tools.ietf.org/html/rfc5545 

725 """ 

726 def __init__(self, fileobj): 

727 global rrule 

728 from dateutil import rrule 

729 

730 if isinstance(fileobj, string_types): 

731 self._s = fileobj 

732 # ical should be encoded in UTF-8 with CRLF 

733 fileobj = open(fileobj, 'r') 

734 else: 

735 self._s = getattr(fileobj, 'name', repr(fileobj)) 

736 fileobj = _nullcontext(fileobj) 

737 

738 self._vtz = {} 

739 

740 with fileobj as fobj: 

741 self._parse_rfc(fobj.read()) 

742 

743 def keys(self): 

744 """ 

745 Retrieves the available time zones as a list. 

746 """ 

747 return list(self._vtz.keys()) 

748 

749 def get(self, tzid=None): 

750 """ 

751 Retrieve a :py:class:`datetime.tzinfo` object by its ``tzid``. 

752 

753 :param tzid: 

754 If there is exactly one time zone available, omitting ``tzid`` 

755 or passing :py:const:`None` value returns it. Otherwise a valid 

756 key (which can be retrieved from :func:`keys`) is required. 

757 

758 :raises ValueError: 

759 Raised if ``tzid`` is not specified but there are either more 

760 or fewer than 1 zone defined. 

761 

762 :returns: 

763 Returns either a :py:class:`datetime.tzinfo` object representing 

764 the relevant time zone or :py:const:`None` if the ``tzid`` was 

765 not found. 

766 """ 

767 if tzid is None: 

768 if len(self._vtz) == 0: 

769 raise ValueError("no timezones defined") 

770 elif len(self._vtz) > 1: 

771 raise ValueError("more than one timezone available") 

772 tzid = next(iter(self._vtz)) 

773 

774 return self._vtz.get(tzid) 

775 

776 def _parse_offset(self, s): 

777 s = s.strip() 

778 if not s: 

779 raise ValueError("empty offset") 

780 if s[0] in ('+', '-'): 

781 signal = (-1, +1)[s[0] == '+'] 

782 s = s[1:] 

783 else: 

784 signal = +1 

785 if len(s) == 4: 

786 return (int(s[:2]) * 3600 + int(s[2:]) * 60) * signal 

787 elif len(s) == 6: 

788 return (int(s[:2]) * 3600 + int(s[2:4]) * 60 + int(s[4:])) * signal 

789 else: 

790 raise ValueError("invalid offset: " + s) 

791 

792 def _parse_rfc(self, s): 

793 lines = s.splitlines() 

794 if not lines: 

795 raise ValueError("empty string") 

796 

797 # Unfold 

798 i = 0 

799 while i < len(lines): 

800 line = lines[i].rstrip() 

801 if not line: 

802 del lines[i] 

803 elif i > 0 and line[0] == " ": 

804 lines[i-1] += line[1:] 

805 del lines[i] 

806 else: 

807 i += 1 

808 

809 tzid = None 

810 comps = [] 

811 invtz = False 

812 comptype = None 

813 for line in lines: 

814 if not line: 

815 continue 

816 name, value = line.split(':', 1) 

817 parms = name.split(';') 

818 if not parms: 

819 raise ValueError("empty property name") 

820 name = parms[0].upper() 

821 parms = parms[1:] 

822 if invtz: 

823 if name == "BEGIN": 

824 if value in ("STANDARD", "DAYLIGHT"): 

825 # Process component 

826 pass 

827 else: 

828 raise ValueError("unknown component: "+value) 

829 comptype = value 

830 founddtstart = False 

831 tzoffsetfrom = None 

832 tzoffsetto = None 

833 rrulelines = [] 

834 tzname = None 

835 elif name == "END": 

836 if value == "VTIMEZONE": 

837 if comptype: 

838 raise ValueError("component not closed: "+comptype) 

839 if not tzid: 

840 raise ValueError("mandatory TZID not found") 

841 if not comps: 

842 raise ValueError( 

843 "at least one component is needed") 

844 # Process vtimezone 

845 self._vtz[tzid] = _tzicalvtz(tzid, comps) 

846 invtz = False 

847 elif value == comptype: 

848 if not founddtstart: 

849 raise ValueError("mandatory DTSTART not found") 

850 if tzoffsetfrom is None: 

851 raise ValueError( 

852 "mandatory TZOFFSETFROM not found") 

853 if tzoffsetto is None: 

854 raise ValueError( 

855 "mandatory TZOFFSETFROM not found") 

856 # Process component 

857 rr = None 

858 if rrulelines: 

859 rr = rrule.rrulestr("\n".join(rrulelines), 

860 compatible=True, 

861 ignoretz=True, 

862 cache=True) 

863 comp = _tzicalvtzcomp(tzoffsetfrom, tzoffsetto, 

864 (comptype == "DAYLIGHT"), 

865 tzname, rr) 

866 comps.append(comp) 

867 comptype = None 

868 else: 

869 raise ValueError("invalid component end: "+value) 

870 elif comptype: 

871 if name == "DTSTART": 

872 # DTSTART in VTIMEZONE takes a subset of valid RRULE 

873 # values under RFC 5545. 

874 for parm in parms: 

875 if parm != 'VALUE=DATE-TIME': 

876 msg = ('Unsupported DTSTART param in ' + 

877 'VTIMEZONE: ' + parm) 

878 raise ValueError(msg) 

879 rrulelines.append(line) 

880 founddtstart = True 

881 elif name in ("RRULE", "RDATE", "EXRULE", "EXDATE"): 

882 rrulelines.append(line) 

883 elif name == "TZOFFSETFROM": 

884 if parms: 

885 raise ValueError( 

886 "unsupported %s parm: %s " % (name, parms[0])) 

887 tzoffsetfrom = self._parse_offset(value) 

888 elif name == "TZOFFSETTO": 

889 if parms: 

890 raise ValueError( 

891 "unsupported TZOFFSETTO parm: "+parms[0]) 

892 tzoffsetto = self._parse_offset(value) 

893 elif name == "TZNAME": 

894 if parms: 

895 raise ValueError( 

896 "unsupported TZNAME parm: "+parms[0]) 

897 tzname = value 

898 elif name == "COMMENT": 

899 pass 

900 else: 

901 raise ValueError("unsupported property: "+name) 

902 else: 

903 if name == "TZID": 

904 if parms: 

905 raise ValueError( 

906 "unsupported TZID parm: "+parms[0]) 

907 tzid = value 

908 elif name in ("TZURL", "LAST-MODIFIED", "COMMENT"): 

909 pass 

910 else: 

911 raise ValueError("unsupported property: "+name) 

912 elif name == "BEGIN" and value == "VTIMEZONE": 

913 tzid = None 

914 comps = [] 

915 invtz = True 

916 

917 def __repr__(self): 

918 return "%s(%s)" % (self.__class__.__name__, repr(self._s)) 

919 

920 

921TZFILES = ["/etc/localtime", "localtime"] 

922 

923def __get_gettz(): 

924 tzlocal_classes = (tzlocal,) 

925 if tzwinlocal is not None: 

926 tzlocal_classes += (tzwinlocal,) 

927 

928 class GettzFunc(object): 

929 """ 

930 Retrieve a time zone object from a string representation 

931 

932 This function is intended to retrieve the :py:class:`tzinfo` subclass 

933 that best represents the time zone that would be used if a POSIX 

934 `TZ variable`_ were set to the same value. 

935 

936 If no argument or an empty string is passed to ``gettz``, local time 

937 is returned: 

938 

939 .. code-block:: python3 

940 

941 >>> gettz() 

942 tzfile('/etc/localtime') 

943 

944 This function is also the preferred way to map IANA tz database keys 

945 to :class:`tzfile` objects. It searches for these keys in the 

946 following order: 

947 

948 1. On the search path defined in :data:`TZPATH`. 

949 2. In the `tzdata <https://pypi.org/project/tzdata/>`_ Python package. 

950 3. Using the system-specific mechanisms (e.g. the Windows registry). 

951 

952 .. code-block:: python3 

953 

954 >>> gettz('Pacific/Kiritimati') 

955 tzfile('/usr/share/zoneinfo/Pacific/Kiritimati') 

956 

957 On Windows, the standard is extended to include the Windows-specific 

958 zone names provided by the operating system: 

959 

960 .. code-block:: python3 

961 

962 >>> gettz('Egypt Standard Time') 

963 tzwin('Egypt Standard Time') 

964 

965 Passing a GNU ``TZ`` style string time zone specification returns a 

966 :class:`tzstr` object: 

967 

968 .. code-block:: python3 

969 

970 >>> gettz('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3') 

971 tzstr('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3') 

972 

973 :param name: 

974 A time zone name (IANA, or, on Windows, Windows keys), location of 

975 a ``tzfile(5)`` zoneinfo file or ``TZ`` variable style time zone 

976 specifier. An empty string, no argument or ``None`` is interpreted 

977 as local time. 

978 

979 :return: 

980 Returns an instance of one of ``dateutil``'s :py:class:`tzinfo` 

981 subclasses. 

982 

983 .. versionchanged:: 2.7.0 

984 

985 After version 2.7.0, any two calls to ``gettz`` using the same 

986 input strings will return the same object: 

987 

988 .. code-block:: python3 

989 

990 >>> tz.gettz('America/Chicago') is tz.gettz('America/Chicago') 

991 True 

992 

993 In addition to improving performance, this ensures that 

994 `"same zone" semantics`_ are used for datetimes in the same zone. 

995 

996 

997 .. _`TZ variable`: 

998 https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html 

999 

1000 .. _`"same zone" semantics`: 

1001 https://blog.ganssle.io/articles/2018/02/aware-datetime-arithmetic.html 

1002 """ 

1003 def __init__(self): 

1004 

1005 self.__instances = weakref.WeakValueDictionary() 

1006 self.__strong_cache_size = 8 

1007 self.__strong_cache = OrderedDict() 

1008 self._cache_lock = _thread.allocate_lock() 

1009 

1010 def __call__(self, name=None): 

1011 with self._cache_lock: 

1012 rv = self.__instances.get(name, None) 

1013 

1014 if rv is None: 

1015 # TODO: This holds the cache lock while nocache() reads 

1016 # the file, so a slow lookup (e.g. walking TZPATH on a 

1017 # network file system) blocks every other gettz call, 

1018 # including cache hits for unrelated keys. 

1019 rv = self.nocache(name=name) 

1020 if not (name is None 

1021 or isinstance(rv, tzlocal_classes) 

1022 or rv is None): 

1023 # tzlocal is slightly more complicated than the other 

1024 # time zone providers because it depends on environment 

1025 # at construction time, so don't cache that. 

1026 # 

1027 # We also cannot store weak references to None, so we 

1028 # will also not store that. 

1029 self.__instances[name] = rv 

1030 else: 

1031 # No need for strong caching, return immediately 

1032 return rv 

1033 

1034 self.__strong_cache[name] = self.__strong_cache.pop(name, rv) 

1035 

1036 if len(self.__strong_cache) > self.__strong_cache_size: 

1037 self.__strong_cache.popitem(last=False) 

1038 

1039 return rv 

1040 

1041 def set_cache_size(self, size): 

1042 with self._cache_lock: 

1043 self.__strong_cache_size = size 

1044 while len(self.__strong_cache) > size: 

1045 self.__strong_cache.popitem(last=False) 

1046 

1047 def cache_clear(self): 

1048 with self._cache_lock: 

1049 self.__instances = weakref.WeakValueDictionary() 

1050 self.__strong_cache.clear() 

1051 

1052 @staticmethod 

1053 def nocache(name=None): 

1054 """A non-cached version of gettz""" 

1055 tz = None 

1056 if not name: 

1057 try: 

1058 name = os.environ["TZ"] 

1059 except KeyError: 

1060 pass 

1061 if name is None or name in ("", ":"): 

1062 for filepath in TZFILES: 

1063 if not os.path.isabs(filepath): 

1064 filename = filepath 

1065 for path in _tzpath.TZPATH: 

1066 filepath = os.path.join(path, filename) 

1067 if _isfile(filepath): 

1068 break 

1069 else: 

1070 continue 

1071 if _isfile(filepath): 

1072 try: 

1073 tz = tzfile(filepath) 

1074 break 

1075 except (IOError, OSError, ValueError): 

1076 pass 

1077 else: 

1078 tz = tzlocal() 

1079 else: 

1080 try: 

1081 if name.startswith(":"): 

1082 name = name[1:] 

1083 except TypeError as e: 

1084 if isinstance(name, bytes): 

1085 new_msg = "gettz argument should be str, not bytes" 

1086 six.raise_from(TypeError(new_msg), e) 

1087 else: 

1088 raise 

1089 

1090 # A NUL byte can never be part of a key or a path, and 

1091 # depending on the platform and Python version it raises 

1092 # ValueError from os.path, winreg or open(), so reject it 

1093 # up front rather than catching it in every branch. 

1094 if "\x00" in name: 

1095 return None 

1096 

1097 if os.path.isabs(name): 

1098 if _isfile(name): 

1099 tz = tzfile(name) 

1100 else: 

1101 tz = None 

1102 else: 

1103 if _escapes_tzpath(name): 

1104 _warn_tzpath_escape(name) 

1105 

1106 for path in _tzpath.TZPATH: 

1107 filepath = os.path.join(path, name) 

1108 if not _isfile(filepath): 

1109 filepath = filepath.replace(' ', '_') 

1110 if not _isfile(filepath): 

1111 continue 

1112 try: 

1113 tz = tzfile(filepath, key=name) 

1114 break 

1115 except (IOError, OSError, ValueError): 

1116 # TODO: A file that exists but can't be read or 

1117 # isn't a valid TZif file is silently skipped, 

1118 # and the key is looked up in the next TZPATH 

1119 # entry and then in tzdata. Consider warning 

1120 # here, since this usually means the system time 

1121 # zone data is broken. 

1122 pass 

1123 else: 

1124 tz = None 

1125 # As with the search path lookup above, also try 

1126 # the name with spaces replaced by underscores. 

1127 candidates = [name] 

1128 if " " in name: 

1129 candidates.append(name.replace(" ", "_")) 

1130 

1131 for candidate in candidates: 

1132 try: 

1133 with _tzdata_impl._load_tzdata(candidate) as f: 

1134 return tzfile(f, key=name) 

1135 except ValueError: 

1136 # TZFileNotFound is a ValueError, and so is 

1137 # the error raised for files in tzdata that 

1138 # are not TZif files (e.g. "zone.tab"), 

1139 # which are skipped just as on TZPATH. 

1140 pass 

1141 

1142 if tzwin is not None: 

1143 try: 

1144 tz = tzwin(name) 

1145 except (WindowsError, UnicodeEncodeError): 

1146 # UnicodeEncodeError is for Python 2.7 compat 

1147 tz = None 

1148 

1149 if not tz: 

1150 for c in name: 

1151 # name is not a tzstr unless it has at least 

1152 # one offset. For short values of "name", an 

1153 # explicit for loop seems to be the fastest way 

1154 # To determine if a string contains a digit 

1155 if c in "0123456789": 

1156 try: 

1157 tz = tzstr(name) 

1158 except ValueError: 

1159 pass 

1160 break 

1161 else: 

1162 if name in ("GMT", "UTC"): 

1163 tz = UTC 

1164 elif name in time.tzname: 

1165 tz = tzlocal() 

1166 return tz 

1167 

1168 return GettzFunc() 

1169 

1170 

1171gettz = __get_gettz() 

1172del __get_gettz 

1173 

1174 

1175class DeprecatedTzKeyWarning(FutureWarning): 

1176 """Warning raised when :func:`gettz` is given a key outside of TZPATH. 

1177 

1178 Relative keys like ``"../../etc/localtime"`` resolve to files outside of 

1179 every directory on :data:`TZPATH`. Support for these is deprecated, and a 

1180 warnings filter turns this warning into an error by default. To keep the 

1181 old behavior for now, filter it out, e.g.:: 

1182 

1183 warnings.filterwarnings( 

1184 "ignore", category=dateutil.tz.DeprecatedTzKeyWarning 

1185 ) 

1186 

1187 Absolute paths to TZif files are not affected. 

1188 """ 

1189 

1190 

1191def _escapes_tzpath(name): 

1192 # A relative key stays inside every TZPATH entry unless, once normalized 

1193 # (which also turns os.altsep into os.sep), it starts by going up a 

1194 # directory. 

1195 return os.path.normpath(name).split(os.sep, 1)[0] == os.pardir 

1196 

1197 

1198def _warn_tzpath_escape(name): 

1199 # As in dateutil.zoneinfo.rebuild, the filter is appended at call time, 

1200 # so any filter the user has installed for this category wins. 

1201 warnings.filterwarnings( 

1202 "error", category=DeprecatedTzKeyWarning, append=True 

1203 ) 

1204 warnings.warn( 

1205 "gettz(%r) refers to a file outside of TZPATH; support for keys " 

1206 "like this is deprecated and will be removed in a future version. " 

1207 "Use an absolute path instead." % (name,), 

1208 DeprecatedTzKeyWarning, 

1209 stacklevel=4, 

1210 ) 

1211 

1212 

1213def available_iana_timezones(): 

1214 """Returns a set containing all available IANA time zones keys. 

1215 

1216 These are suitable for use with :func:`gettz`. This is essentially a direct 

1217 port of :py:func:`zoneinfo.available_timezones`. 

1218 

1219 The search includes time zones found on :data:`TZPATH` as well as any 

1220 time zones provided by the `tzdata <https://pypi.org/project/tzdata/>`_ 

1221 package if it is installed. 

1222 

1223 :return: 

1224 Returns a mutable set containing strings of time zones. Each call will 

1225 return a fresh set. 

1226 

1227 .. caution:: 

1228 

1229 This may attempt to open a large number of files, since the best way to 

1230 determine if a given file on the time zone search path is to open it 

1231 and check for the "magic string" at the beginning. 

1232 

1233 .. note:: 

1234 

1235 These values are not designed to be exposed to end-users; for 

1236 user-facing elements, applications should use something like CLDR (the 

1237 Unicode Common Locale Data Repository) to get more user-friendly 

1238 strings. 

1239 """ 

1240 # Start with loading from the tzdata package if it exists: this has a 

1241 # pre-assembled list of zones that only requires opening one file. 

1242 try: 

1243 valid_zones = set(_tzdata_impl._load_tzdata_keys()) 

1244 except ImportError: 

1245 valid_zones = set() 

1246 

1247 def valid_key(fpath): 

1248 try: 

1249 with open(fpath, "rb") as f: 

1250 return f.read(4) == b"TZif" 

1251 except Exception: # pragma: nocover 

1252 return False 

1253 

1254 for tz_root in _tzpath.TZPATH: 

1255 if not os.path.exists(tz_root): 

1256 continue 

1257 

1258 for root, dirnames, files in os.walk(tz_root): 

1259 if root == tz_root: 

1260 # right/ and posix/ are special directories and shouldn't be 

1261 # included in the output of available zones 

1262 if "right" in dirnames: 

1263 dirnames.remove("right") 

1264 if "posix" in dirnames: 

1265 dirnames.remove("posix") 

1266 

1267 for file in files: 

1268 fpath = os.path.join(root, file) 

1269 

1270 key = os.path.relpath(fpath, start=tz_root) 

1271 if os.sep != "/": # pragma: nocover 

1272 key = key.replace(os.sep, "/") 

1273 

1274 if not key or key in valid_zones: 

1275 continue 

1276 

1277 if valid_key(fpath): 

1278 valid_zones.add(key) 

1279 

1280 if "posixrules" in valid_zones: 

1281 # posixrules is a special symlink-only time zone where it exists, it 

1282 # should not be included in the output 

1283 valid_zones.remove("posixrules") 

1284 if "localtime" in valid_zones: 

1285 # localtime is a special symlink-only time zone where it exists, it 

1286 # should not be included in the output 

1287 valid_zones.remove("localtime") 

1288 

1289 return valid_zones 

1290 

1291 

1292def datetime_exists(dt, tz=None): 

1293 """ 

1294 Given a datetime and a time zone, determine whether or not a given datetime 

1295 would fall in a gap. 

1296 

1297 :param dt: 

1298 A :class:`datetime.datetime` (whose time zone will be ignored if ``tz`` 

1299 is provided.) 

1300 

1301 :param tz: 

1302 A :class:`datetime.tzinfo` with support for the ``fold`` attribute. If 

1303 ``None`` or not provided, the datetime's own time zone will be used. 

1304 

1305 :return: 

1306 Returns a boolean value whether or not the "wall time" exists in 

1307 ``tz``. 

1308 

1309 .. versionadded:: 2.7.0 

1310 """ 

1311 if tz is None: 

1312 if dt.tzinfo is None: 

1313 raise ValueError('Datetime is naive and no time zone provided.') 

1314 tz = dt.tzinfo 

1315 

1316 dt = dt.replace(tzinfo=None) 

1317 

1318 # This is essentially a test of whether or not the datetime can survive 

1319 # a round trip to UTC. 

1320 dt_rt = dt.replace(tzinfo=tz).astimezone(UTC).astimezone(tz) 

1321 dt_rt = dt_rt.replace(tzinfo=None) 

1322 

1323 return dt == dt_rt 

1324 

1325 

1326def datetime_ambiguous(dt, tz=None): 

1327 """ 

1328 Given a datetime and a time zone, determine whether or not a given datetime 

1329 is ambiguous (i.e if there are two times differentiated only by their DST 

1330 status). 

1331 

1332 :param dt: 

1333 A :class:`datetime.datetime` (whose time zone will be ignored if ``tz`` 

1334 is provided.) 

1335 

1336 :param tz: 

1337 A :class:`datetime.tzinfo` with support for the ``fold`` attribute. If 

1338 ``None`` or not provided, the datetime's own time zone will be used. 

1339 

1340 :return: 

1341 Returns a boolean value whether or not the "wall time" is ambiguous in 

1342 ``tz``. 

1343 

1344 .. versionadded:: 2.6.0 

1345 """ 

1346 if tz is None: 

1347 if dt.tzinfo is None: 

1348 raise ValueError('Datetime is naive and no time zone provided.') 

1349 

1350 tz = dt.tzinfo 

1351 

1352 # If a time zone defines its own "is_ambiguous" function, we'll use that. 

1353 is_ambiguous_fn = getattr(tz, 'is_ambiguous', None) 

1354 if is_ambiguous_fn is not None: 

1355 try: 

1356 return tz.is_ambiguous(dt) 

1357 except Exception: 

1358 pass 

1359 

1360 # If it doesn't come out and tell us it's ambiguous, we'll just check if 

1361 # the fold attribute has any effect on this particular date and time. 

1362 dt = dt.replace(tzinfo=tz) 

1363 wall_0 = enfold(dt, fold=0) 

1364 wall_1 = enfold(dt, fold=1) 

1365 

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

1367 same_dst = wall_0.dst() == wall_1.dst() 

1368 

1369 return not (same_offset and same_dst) 

1370 

1371 

1372def resolve_imaginary(dt): 

1373 """ 

1374 Given a datetime that may be imaginary, return an existing datetime. 

1375 

1376 This function assumes that an imaginary datetime represents what the 

1377 wall time would be in a zone had the offset transition not occurred, so 

1378 it will always fall forward by the transition's change in offset. 

1379 

1380 .. doctest:: 

1381 

1382 >>> from dateutil import tz 

1383 >>> from datetime import datetime 

1384 >>> NYC = tz.gettz('America/New_York') 

1385 >>> print(tz.resolve_imaginary(datetime(2017, 3, 12, 2, 30, tzinfo=NYC))) 

1386 2017-03-12 03:30:00-04:00 

1387 

1388 >>> KIR = tz.gettz('Pacific/Kiritimati') 

1389 >>> print(tz.resolve_imaginary(datetime(1995, 1, 1, 12, 30, tzinfo=KIR))) 

1390 1995-01-02 12:30:00+14:00 

1391 

1392 As a note, :func:`datetime.astimezone` is guaranteed to produce a valid, 

1393 existing datetime, so a round-trip to and from UTC is sufficient to get 

1394 an extant datetime, however, this generally "falls back" to an earlier time 

1395 rather than falling forward to the STD side (though no guarantees are made 

1396 about this behavior). 

1397 

1398 :param dt: 

1399 A :class:`datetime.datetime` which may or may not exist. 

1400 

1401 :return: 

1402 Returns an existing :class:`datetime.datetime`. If ``dt`` was not 

1403 imaginary, the datetime returned is guaranteed to be the same object 

1404 passed to the function. 

1405 

1406 .. versionadded:: 2.7.0 

1407 """ 

1408 if dt.tzinfo is not None and not datetime_exists(dt): 

1409 

1410 curr_offset = (dt + datetime.timedelta(hours=24)).utcoffset() 

1411 old_offset = (dt - datetime.timedelta(hours=24)).utcoffset() 

1412 

1413 dt += curr_offset - old_offset 

1414 

1415 return dt 

1416 

1417 

1418def _datetime_to_timestamp(dt): 

1419 """ 

1420 Convert a :class:`datetime.datetime` object to an epoch timestamp in 

1421 seconds since January 1, 1970, ignoring the time zone. 

1422 """ 

1423 return (dt.replace(tzinfo=None) - EPOCH).total_seconds() 

1424 

1425 

1426if sys.version_info >= (3, 6): 

1427 def _get_supported_offset(second_offset): 

1428 return second_offset 

1429else: 

1430 def _get_supported_offset(second_offset): 

1431 # For python pre-3.6, round to full-minutes if that's not the case. 

1432 # Python's datetime doesn't accept sub-minute timezones. Check 

1433 # http://python.org/sf/1447945 or https://bugs.python.org/issue5288 

1434 # for some information. 

1435 old_offset = second_offset 

1436 calculated_offset = 60 * ((second_offset + 30) // 60) 

1437 return calculated_offset 

1438 

1439 

1440if sys.version_info < (3, 8): 

1441 

1442 def _isfile(path): 

1443 # bpo-33721: In Python 3.8 non-UTF8 paths return False rather than 

1444 # raising an error. See https://bugs.python.org/issue33721 

1445 try: 

1446 return os.path.isfile(path) 

1447 except ValueError: 

1448 return False 

1449 

1450else: 

1451 _isfile = os.path.isfile 

1452 

1453 

1454try: 

1455 # Python 3.7 feature 

1456 from contextlib import nullcontext as _nullcontext 

1457except ImportError: 

1458 class _nullcontext(object): 

1459 """ 

1460 Class for wrapping contexts so that they are passed through in a 

1461 with statement. 

1462 """ 

1463 def __init__(self, context): 

1464 self.context = context 

1465 

1466 def __enter__(self): 

1467 return self.context 

1468 

1469 def __exit__(*args, **kwargs): 

1470 pass 

1471 

1472# vim:ts=4:sw=4:et