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

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

595 statements  

1""" 

2This module offers timezone implementations subclassing the abstract 

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

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

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

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

7timezone. 

8""" 

9 

10import _thread 

11import contextlib 

12import datetime 

13import os 

14import time 

15import warnings 

16import weakref 

17from collections import OrderedDict 

18 

19from .. import _tzdata_impl 

20from . import _tzpath 

21from ._common import _tzinfo, _validate_fromutc_inputs, enfold, tzrangebase 

22from ._factories import _TzOffsetFactory, _TzSingleton, _TzStrFactory 

23from ._tzfile import tzfile 

24 

25try: 

26 from .win import tzwin, tzwinlocal 

27except ImportError: 

28 tzwin = tzwinlocal = None 

29 

30ZERO = datetime.timedelta(0) 

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

32 

33 

34class tzutc(datetime.tzinfo, metaclass=_TzSingleton): 

35 """ 

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

37 

38 **Examples:** 

39 

40 .. doctest:: 

41 

42 >>> from datetime import * 

43 >>> from dateutil.tz import * 

44 

45 >>> datetime.now() 

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

47 

48 >>> datetime.now(tzutc()) 

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

50 

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

52 'UTC' 

53 

54 .. versionchanged:: 2.7.0 

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

56 always return the same object. 

57 

58 .. doctest:: 

59 

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

61 >>> tzutc() is tzutc() 

62 True 

63 >>> tzutc() is UTC 

64 True 

65 """ 

66 def utcoffset(self, dt): 

67 return ZERO 

68 

69 def dst(self, dt): 

70 return ZERO 

71 

72 def tzname(self, dt): 

73 return "UTC" 

74 

75 def is_ambiguous(self, dt): 

76 """ 

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

78 zone. 

79 

80 :param dt: 

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

82 

83 

84 :return: 

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

86 

87 .. versionadded:: 2.6.0 

88 """ 

89 return False 

90 

91 @_validate_fromutc_inputs 

92 def fromutc(self, dt): 

93 """ 

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

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

96 """ 

97 return dt 

98 

99 def __eq__(self, other): 

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

101 return NotImplemented 

102 

103 return (isinstance(other, tzutc) or 

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

105 

106 __hash__ = None 

107 

108 def __repr__(self): 

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

110 

111 def __reduce__(self): 

112 return (self.__class__, tuple()) 

113 

114 

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

116#: 

117#: .. versionadded:: 2.7.0 

118UTC = tzutc() 

119 

120 

121class tzoffset(datetime.tzinfo, metaclass=_TzOffsetFactory): 

122 """ 

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

124 

125 :param name: 

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

127 :param offset: 

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

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

130 """ 

131 def __init__(self, name, offset): 

132 self._name = name 

133 

134 try: 

135 # Allow a timedelta 

136 offset = offset.total_seconds() 

137 except (TypeError, AttributeError): 

138 pass 

139 

140 self._offset = datetime.timedelta(seconds=offset) 

141 

142 def utcoffset(self, dt): 

143 return self._offset 

144 

145 def dst(self, dt): 

146 return ZERO 

147 

148 def tzname(self, dt): 

149 return self._name 

150 

151 @_validate_fromutc_inputs 

152 def fromutc(self, dt): 

153 return dt + self._offset 

154 

155 def is_ambiguous(self, dt): 

156 """ 

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

158 zone. 

159 

160 :param dt: 

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

162 :return: 

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

164 

165 .. versionadded:: 2.6.0 

166 """ 

167 return False 

168 

169 def __eq__(self, other): 

170 if not isinstance(other, tzoffset): 

171 return NotImplemented 

172 

173 return self._offset == other._offset 

174 

175 __hash__ = None 

176 

177 def __repr__(self): 

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

179 repr(self._name), 

180 int(self._offset.total_seconds())) 

181 

182 def __reduce__(self): 

183 return (self.__class__, (self._name, self._offset)) 

184 

185 

186class tzlocal(_tzinfo): 

187 """ 

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

189 """ 

190 def __init__(self): 

191 super().__init__() 

192 

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

194 if time.daylight: 

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

196 else: 

197 self._dst_offset = self._std_offset 

198 

199 self._dst_saved = self._dst_offset - self._std_offset 

200 self._hasdst = bool(self._dst_saved) 

201 self._tznames = tuple(time.tzname) 

202 

203 def utcoffset(self, dt): 

204 if dt is None and self._hasdst: 

205 return None 

206 

207 if self._isdst(dt): 

208 return self._dst_offset 

209 else: 

210 return self._std_offset 

211 

212 def dst(self, dt): 

213 if dt is None and self._hasdst: 

214 return None 

215 

216 if self._isdst(dt): 

217 return self._dst_offset - self._std_offset 

218 else: 

219 return ZERO 

220 

221 def tzname(self, dt): 

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

223 

224 def is_ambiguous(self, dt): 

225 """ 

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

227 zone. 

228 

229 :param dt: 

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

231 

232 

233 :return: 

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

235 

236 .. versionadded:: 2.6.0 

237 """ 

238 naive_dst = self._naive_is_dst(dt) 

239 return (not naive_dst and 

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

241 

242 def _naive_is_dst(self, dt): 

243 timestamp = _datetime_to_timestamp(dt) 

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

245 

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

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

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

249 # 

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

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

252 # return time.localtime(timestamp).tm_isdst 

253 # 

254 # The code above yields the following result: 

255 # 

256 # >>> import tz, datetime 

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

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

259 # 'BRDT' 

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

261 # 'BRST' 

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

263 # 'BRST' 

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

265 # 'BRDT' 

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

267 # 'BRDT' 

268 # 

269 # Here is a more stable implementation: 

270 # 

271 if not self._hasdst: 

272 return False 

273 

274 # Check for ambiguous times: 

275 dstval = self._naive_is_dst(dt) 

276 

277 if self.is_ambiguous(dt): 

278 return not dt.fold 

279 

280 return dstval 

281 

282 def __eq__(self, other): 

283 if isinstance(other, tzlocal): 

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

285 self._dst_offset == other._dst_offset) 

286 elif isinstance(other, tzutc): 

287 return (not self._hasdst and 

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

289 self._std_offset == ZERO) 

290 elif isinstance(other, tzoffset): 

291 return (not self._hasdst and 

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

293 self._std_offset == other._offset) 

294 else: 

295 return NotImplemented 

296 

297 __hash__ = None 

298 

299 def __repr__(self): 

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

301 

302 __reduce__ = object.__reduce__ 

303 

304 

305class tzrange(tzrangebase): 

306 """ 

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

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

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

310 start, end and offsets. 

311 

312 :param stdabbr: 

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

314 

315 :param stdoffset: 

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

317 specifying the base offset from UTC. 

318 

319 If unspecified, +00:00 is used. 

320 

321 :param dstabbr: 

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

323 

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

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

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

327 is assumed that this zone has no DST. 

328 

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

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

331 unchanged. 

332 

333 :param dstoffset: 

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

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

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

337 

338 :param start: 

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

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

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

342 March, pass: 

343 

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

345 

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

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

348 

349 :param end: 

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

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

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

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

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

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

356 

357 

358 **Examples:** 

359 

360 .. testsetup:: tzrange 

361 

362 from dateutil.tz import tzrange, tzstr 

363 

364 .. doctest:: tzrange 

365 

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

367 True 

368 

369 >>> from dateutil.relativedelta import * 

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

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

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

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

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

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

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

377 True 

378 

379 """ 

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

381 dstabbr=None, dstoffset=None, 

382 start=None, end=None): 

383 

384 global relativedelta 

385 from dateutil import relativedelta 

386 

387 self._std_abbr = stdabbr 

388 self._dst_abbr = dstabbr 

389 

390 try: 

391 stdoffset = stdoffset.total_seconds() 

392 except (TypeError, AttributeError): 

393 pass 

394 

395 try: 

396 dstoffset = dstoffset.total_seconds() 

397 except (TypeError, AttributeError): 

398 pass 

399 

400 if stdoffset is not None: 

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

402 else: 

403 self._std_offset = ZERO 

404 

405 if dstoffset is not None: 

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

407 elif dstabbr and stdoffset is not None: 

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

409 else: 

410 self._dst_offset = ZERO 

411 

412 if dstabbr and start is None: 

413 self._start_delta = relativedelta.relativedelta( 

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

415 else: 

416 self._start_delta = start 

417 

418 if dstabbr and end is None: 

419 self._end_delta = relativedelta.relativedelta( 

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

421 else: 

422 self._end_delta = end 

423 

424 self._dst_base_offset_ = self._dst_offset - self._std_offset 

425 self.hasdst = bool(self._start_delta) 

426 

427 def transitions(self, year): 

428 """ 

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

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

431 function returns ``None``. 

432 

433 :param year: 

434 The year whose transitions you would like to query. 

435 

436 :return: 

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

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

439 ``None`` for fixed offset zones. 

440 """ 

441 if not self.hasdst: 

442 return None 

443 

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

445 

446 start = base_year + self._start_delta 

447 end = base_year + self._end_delta 

448 

449 return (start, end) 

450 

451 def __eq__(self, other): 

452 if not isinstance(other, tzrange): 

453 return NotImplemented 

454 

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

456 self._dst_abbr == other._dst_abbr and 

457 self._std_offset == other._std_offset and 

458 self._dst_offset == other._dst_offset and 

459 self._start_delta == other._start_delta and 

460 self._end_delta == other._end_delta) 

461 

462 @property 

463 def _dst_base_offset(self): 

464 return self._dst_base_offset_ 

465 

466 

467class tzstr(tzrange, metaclass=_TzStrFactory): 

468 """ 

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

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

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

472 

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

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

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

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

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

478 

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

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

481 strings. 

482 

483 :param s: 

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

485 :class:`bytes`, :class:`str` or a stream emitting Unicode characters 

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

487 

488 :param posix_offset: 

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

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

491 POSIX standard. 

492 

493 .. caution:: 

494 

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

496 in the format: 

497 

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

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

500 

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

502 will raise a :class:`DeprecatedTZFormatWarning` until 

503 support is removed in a future version. 

504 

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

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

507 """ 

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

509 global parser 

510 from dateutil.parser import _parser as parser 

511 

512 self._s = s 

513 

514 res = parser._parsetz(s) 

515 if res is None or res.any_unused_tokens: 

516 raise ValueError("unknown string format") 

517 

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

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

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

521 res.stdoffset *= -1 

522 

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

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

525 # to avoid building it two times. 

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

527 res.dstabbr, res.dstoffset, 

528 start=False, end=False) 

529 

530 if not res.dstabbr: 

531 self._start_delta = None 

532 self._end_delta = None 

533 else: 

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

535 if self._start_delta: 

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

537 

538 self.hasdst = bool(self._start_delta) 

539 

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

541 from dateutil import relativedelta 

542 kwargs = {} 

543 if x.month is not None: 

544 kwargs["month"] = x.month 

545 if x.weekday is not None: 

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

547 if x.week > 0: 

548 kwargs["day"] = 1 

549 else: 

550 kwargs["day"] = 31 

551 elif x.day: 

552 kwargs["day"] = x.day 

553 elif x.yday is not None: 

554 kwargs["yearday"] = x.yday 

555 elif x.jyday is not None: 

556 kwargs["nlyearday"] = x.jyday 

557 if not kwargs: 

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

559 # on last sunday of october. 

560 if not isend: 

561 kwargs["month"] = 4 

562 kwargs["day"] = 1 

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

564 else: 

565 kwargs["month"] = 10 

566 kwargs["day"] = 31 

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

568 if x.time is not None: 

569 kwargs["seconds"] = x.time 

570 else: 

571 # Default is 2AM. 

572 kwargs["seconds"] = 7200 

573 if isend: 

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

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

576 # of the tzinfo class. 

577 delta = self._dst_offset - self._std_offset 

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

579 return relativedelta.relativedelta(**kwargs) 

580 

581 def __repr__(self): 

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

583 

584 

585class _tzicalvtzcomp: 

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

587 tzname=None, rrule=None): 

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

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

590 self.tzoffsetdiff = self.tzoffsetto - self.tzoffsetfrom 

591 self.isdst = isdst 

592 self.tzname = tzname 

593 self.rrule = rrule 

594 

595 

596class _tzicalvtz(_tzinfo): 

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

598 super().__init__() 

599 

600 self._tzid = tzid 

601 self._comps = comps 

602 self._cachedate = [] 

603 self._cachecomp = [] 

604 self._cache_lock = _thread.allocate_lock() 

605 

606 def _find_comp(self, dt): 

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

608 return self._comps[0] 

609 

610 dt = dt.replace(tzinfo=None) 

611 

612 try: 

613 with self._cache_lock: 

614 return self._cachecomp[self._cachedate.index((dt, dt.fold))] 

615 except ValueError: 

616 pass 

617 

618 lastcompdt = None 

619 lastcomp = None 

620 

621 for comp in self._comps: 

622 compdt = self._find_compdt(comp, dt) 

623 

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

625 lastcompdt = compdt 

626 lastcomp = comp 

627 

628 if not lastcomp: 

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

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

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

632 # none is found. 

633 for comp in self._comps: 

634 if not comp.isdst: 

635 lastcomp = comp 

636 break 

637 else: 

638 lastcomp = comp[0] 

639 

640 with self._cache_lock: 

641 self._cachedate.insert(0, (dt, dt.fold)) 

642 self._cachecomp.insert(0, lastcomp) 

643 

644 if len(self._cachedate) > 10: 

645 self._cachedate.pop() 

646 self._cachecomp.pop() 

647 

648 return lastcomp 

649 

650 def _find_compdt(self, comp, dt): 

651 if comp.tzoffsetdiff < ZERO and dt.fold: 

652 dt -= comp.tzoffsetdiff 

653 

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

655 

656 return compdt 

657 

658 def utcoffset(self, dt): 

659 if dt is None: 

660 return None 

661 

662 return self._find_comp(dt).tzoffsetto 

663 

664 def dst(self, dt): 

665 comp = self._find_comp(dt) 

666 if comp.isdst: 

667 return comp.tzoffsetdiff 

668 else: 

669 return ZERO 

670 

671 def tzname(self, dt): 

672 return self._find_comp(dt).tzname 

673 

674 def __repr__(self): 

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

676 

677 __reduce__ = object.__reduce__ 

678 

679 

680class tzical: 

681 """ 

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

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

684 

685 :param `fileobj`: 

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

687 with CRLF endings. 

688 

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

690 """ 

691 def __init__(self, fileobj): 

692 global rrule 

693 from dateutil import rrule 

694 

695 if isinstance(fileobj, str): 

696 self._s = fileobj 

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

698 fileobj = open(fileobj, 'r') 

699 else: 

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

701 fileobj = contextlib.nullcontext(fileobj) 

702 

703 self._vtz = {} 

704 

705 with fileobj as fobj: 

706 self._parse_rfc(fobj.read()) 

707 

708 def keys(self): 

709 """ 

710 Retrieves the available time zones as a list. 

711 """ 

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

713 

714 def get(self, tzid=None): 

715 """ 

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

717 

718 :param tzid: 

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

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

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

722 

723 :raises ValueError: 

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

725 or fewer than 1 zone defined. 

726 

727 :returns: 

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

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

730 not found. 

731 """ 

732 if tzid is None: 

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

734 raise ValueError("no timezones defined") 

735 elif len(self._vtz) > 1: 

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

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

738 

739 return self._vtz.get(tzid) 

740 

741 def _parse_offset(self, s): 

742 s = s.strip() 

743 if not s: 

744 raise ValueError("empty offset") 

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

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

747 s = s[1:] 

748 else: 

749 signal = +1 

750 if not (s.isascii() and s.isdigit()): 

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

752 if len(s) == 4: 

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

754 elif len(s) == 6: 

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

756 else: 

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

758 

759 def _parse_rfc(self, s): 

760 lines = s.splitlines() 

761 if not lines: 

762 raise ValueError("empty string") 

763 

764 # Unfold 

765 i = 0 

766 while i < len(lines): 

767 line = lines[i].rstrip() 

768 if not line: 

769 del lines[i] 

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

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

772 del lines[i] 

773 else: 

774 i += 1 

775 

776 tzid = None 

777 comps = [] 

778 invtz = False 

779 comptype = None 

780 for line in lines: 

781 if not line: 

782 continue 

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

784 parms = name.split(';') 

785 if not parms: 

786 raise ValueError("empty property name") 

787 name = parms[0].upper() 

788 parms = parms[1:] 

789 if invtz: 

790 if name == "BEGIN": 

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

792 # Process component 

793 pass 

794 else: 

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

796 comptype = value 

797 founddtstart = False 

798 tzoffsetfrom = None 

799 tzoffsetto = None 

800 rrulelines = [] 

801 tzname = None 

802 elif name == "END": 

803 if value == "VTIMEZONE": 

804 if comptype: 

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

806 if not tzid: 

807 raise ValueError("mandatory TZID not found") 

808 if not comps: 

809 raise ValueError( 

810 "at least one component is needed") 

811 # Process vtimezone 

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

813 invtz = False 

814 elif value == comptype: 

815 if not founddtstart: 

816 raise ValueError("mandatory DTSTART not found") 

817 if tzoffsetfrom is None: 

818 raise ValueError( 

819 "mandatory TZOFFSETFROM not found") 

820 if tzoffsetto is None: 

821 raise ValueError( 

822 "mandatory TZOFFSETFROM not found") 

823 # Process component 

824 rr = None 

825 if rrulelines: 

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

827 compatible=True, 

828 ignoretz=True, 

829 cache=True) 

830 comp = _tzicalvtzcomp(tzoffsetfrom, tzoffsetto, 

831 (comptype == "DAYLIGHT"), 

832 tzname, rr) 

833 comps.append(comp) 

834 comptype = None 

835 else: 

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

837 elif comptype: 

838 if name == "DTSTART": 

839 # DTSTART in VTIMEZONE takes a subset of valid RRULE 

840 # values under RFC 5545. 

841 for parm in parms: 

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

843 msg = ('Unsupported DTSTART param in ' + 

844 'VTIMEZONE: ' + parm) 

845 raise ValueError(msg) 

846 rrulelines.append(line) 

847 founddtstart = True 

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

849 rrulelines.append(line) 

850 elif name == "TZOFFSETFROM": 

851 if parms: 

852 raise ValueError( 

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

854 tzoffsetfrom = self._parse_offset(value) 

855 elif name == "TZOFFSETTO": 

856 if parms: 

857 raise ValueError( 

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

859 tzoffsetto = self._parse_offset(value) 

860 elif name == "TZNAME": 

861 if parms: 

862 raise ValueError( 

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

864 tzname = value 

865 elif name == "COMMENT": 

866 pass 

867 else: 

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

869 else: 

870 if name == "TZID": 

871 if parms: 

872 raise ValueError( 

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

874 tzid = value 

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

876 pass 

877 else: 

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

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

880 tzid = None 

881 comps = [] 

882 invtz = True 

883 

884 def __repr__(self): 

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

886 

887 

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

889 

890def __get_gettz(): 

891 tzlocal_classes = (tzlocal,) 

892 if tzwinlocal is not None: 

893 tzlocal_classes += (tzwinlocal,) 

894 

895 class GettzFunc: 

896 """ 

897 Retrieve a time zone object from a string representation 

898 

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

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

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

902 

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

904 is returned: 

905 

906 .. code-block:: python3 

907 

908 >>> gettz() 

909 tzfile('/etc/localtime') 

910 

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

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

913 following order: 

914 

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

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

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

918 

919 .. code-block:: python3 

920 

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

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

923 

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

925 zone names provided by the operating system: 

926 

927 .. code-block:: python3 

928 

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

930 tzwin('Egypt Standard Time') 

931 

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

933 :class:`tzstr` object: 

934 

935 .. code-block:: python3 

936 

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

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

939 

940 :param name: 

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

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

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

944 as local time. 

945 

946 :return: 

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

948 subclasses. 

949 

950 .. versionchanged:: 2.7.0 

951 

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

953 input strings will return the same object: 

954 

955 .. code-block:: python3 

956 

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

958 True 

959 

960 In addition to improving performance, this ensures that 

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

962 

963 

964 .. _`TZ variable`: 

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

966 

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

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

969 """ 

970 def __init__(self): 

971 

972 self.__instances = weakref.WeakValueDictionary() 

973 self.__strong_cache_size = 8 

974 self.__strong_cache = OrderedDict() 

975 self._cache_lock = _thread.allocate_lock() 

976 

977 def __call__(self, name=None): 

978 with self._cache_lock: 

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

980 

981 if rv is None: 

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

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

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

985 # including cache hits for unrelated keys. 

986 rv = self.nocache(name=name) 

987 if not (name is None 

988 or isinstance(rv, tzlocal_classes) 

989 or rv is None): 

990 # tzlocal is slightly more complicated than the other 

991 # time zone providers because it depends on environment 

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

993 # 

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

995 # will also not store that. 

996 self.__instances[name] = rv 

997 else: 

998 # No need for strong caching, return immediately 

999 return rv 

1000 

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

1002 

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

1004 self.__strong_cache.popitem(last=False) 

1005 

1006 return rv 

1007 

1008 def set_cache_size(self, size): 

1009 with self._cache_lock: 

1010 self.__strong_cache_size = size 

1011 while len(self.__strong_cache) > size: 

1012 self.__strong_cache.popitem(last=False) 

1013 

1014 def cache_clear(self): 

1015 with self._cache_lock: 

1016 self.__instances = weakref.WeakValueDictionary() 

1017 self.__strong_cache.clear() 

1018 

1019 @staticmethod 

1020 def nocache(name=None): 

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

1022 tz = None 

1023 if not name: 

1024 try: 

1025 name = os.environ["TZ"] 

1026 except KeyError: 

1027 pass 

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

1029 for filepath in TZFILES: 

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

1031 filename = filepath 

1032 for path in _tzpath.TZPATH: 

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

1034 if os.path.isfile(filepath): 

1035 break 

1036 else: 

1037 continue 

1038 if os.path.isfile(filepath): 

1039 try: 

1040 tz = tzfile(filepath) 

1041 break 

1042 except (OSError, ValueError): 

1043 pass 

1044 else: 

1045 tz = tzlocal() 

1046 else: 

1047 try: 

1048 if name.startswith(":"): 

1049 name = name[1:] 

1050 except TypeError as e: 

1051 if isinstance(name, bytes): 

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

1053 raise TypeError(new_msg) from e 

1054 else: 

1055 raise 

1056 

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

1058 # depending on the platform and Python version it raises 

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

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

1061 if "\x00" in name: 

1062 return None 

1063 

1064 if os.path.isabs(name): 

1065 if os.path.isfile(name): 

1066 tz = tzfile(name) 

1067 else: 

1068 tz = None 

1069 else: 

1070 if _escapes_tzpath(name): 

1071 _warn_tzpath_escape(name) 

1072 

1073 for path in _tzpath.TZPATH: 

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

1075 if not os.path.isfile(filepath): 

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

1077 if not os.path.isfile(filepath): 

1078 continue 

1079 try: 

1080 tz = tzfile(filepath, key=name) 

1081 break 

1082 except (OSError, ValueError): 

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

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

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

1086 # entry and then in tzdata. Consider warning 

1087 # here, since this usually means the system time 

1088 # zone data is broken. 

1089 pass 

1090 else: 

1091 tz = None 

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

1093 # the name with spaces replaced by underscores. 

1094 candidates = [name] 

1095 if " " in name: 

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

1097 

1098 for candidate in candidates: 

1099 try: 

1100 with _tzdata_impl._load_tzdata(candidate) as f: 

1101 return tzfile(f, key=name) 

1102 except ValueError: 

1103 # TZFileNotFound is a ValueError, and so is 

1104 # the error raised for files in tzdata that 

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

1106 # which are skipped just as on TZPATH. 

1107 pass 

1108 

1109 if tzwin is not None: 

1110 try: 

1111 tz = tzwin(name) 

1112 except OSError: 

1113 tz = None 

1114 

1115 if not tz: 

1116 for c in name: 

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

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

1119 # explicit for loop seems to be the fastest way 

1120 # To determine if a string contains a digit 

1121 if c in "0123456789": 

1122 try: 

1123 tz = tzstr(name) 

1124 except ValueError: 

1125 pass 

1126 break 

1127 else: 

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

1129 tz = UTC 

1130 elif name in time.tzname: 

1131 tz = tzlocal() 

1132 return tz 

1133 

1134 return GettzFunc() 

1135 

1136 

1137gettz = __get_gettz() 

1138del __get_gettz 

1139 

1140 

1141class DeprecatedTzKeyWarning(FutureWarning): 

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

1143 

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

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

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

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

1148 

1149 warnings.filterwarnings( 

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

1151 ) 

1152 

1153 Absolute paths to TZif files are not affected. 

1154 """ 

1155 

1156 

1157def _escapes_tzpath(name): 

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

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

1160 # directory. 

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

1162 

1163 

1164def _warn_tzpath_escape(name): 

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

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

1167 warnings.filterwarnings( 

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

1169 ) 

1170 warnings.warn( 

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

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

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

1174 DeprecatedTzKeyWarning, 

1175 stacklevel=4, 

1176 ) 

1177 

1178 

1179def available_iana_timezones(): 

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

1181 

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

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

1184 

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

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

1187 package if it is installed. 

1188 

1189 :return: 

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

1191 return a fresh set. 

1192 

1193 .. caution:: 

1194 

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

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

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

1198 

1199 .. note:: 

1200 

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

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

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

1204 strings. 

1205 """ 

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

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

1208 try: 

1209 valid_zones = set(_tzdata_impl._load_tzdata_keys()) 

1210 except ImportError: 

1211 valid_zones = set() 

1212 

1213 def valid_key(fpath): 

1214 try: 

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

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

1217 except Exception: # pragma: nocover 

1218 return False 

1219 

1220 for tz_root in _tzpath.TZPATH: 

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

1222 continue 

1223 

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

1225 if root == tz_root: 

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

1227 # included in the output of available zones 

1228 if "right" in dirnames: 

1229 dirnames.remove("right") 

1230 if "posix" in dirnames: 

1231 dirnames.remove("posix") 

1232 

1233 for file in files: 

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

1235 

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

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

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

1239 

1240 if not key or key in valid_zones: 

1241 continue 

1242 

1243 if valid_key(fpath): 

1244 valid_zones.add(key) 

1245 

1246 if "posixrules" in valid_zones: 

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

1248 # should not be included in the output 

1249 valid_zones.remove("posixrules") 

1250 if "localtime" in valid_zones: 

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

1252 # should not be included in the output 

1253 valid_zones.remove("localtime") 

1254 

1255 return valid_zones 

1256 

1257 

1258def datetime_exists(dt, tz=None): 

1259 """ 

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

1261 would fall in a gap. 

1262 

1263 :param dt: 

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

1265 is provided.) 

1266 

1267 :param tz: 

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

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

1270 

1271 :return: 

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

1273 ``tz``. 

1274 

1275 .. versionadded:: 2.7.0 

1276 """ 

1277 if tz is None: 

1278 if dt.tzinfo is None: 

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

1280 tz = dt.tzinfo 

1281 

1282 dt = dt.replace(tzinfo=None) 

1283 

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

1285 # a round trip to UTC. 

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

1287 dt_rt = dt_rt.replace(tzinfo=None) 

1288 

1289 return dt == dt_rt 

1290 

1291 

1292def datetime_ambiguous(dt, tz=None): 

1293 """ 

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

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

1296 status). 

1297 

1298 :param dt: 

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

1300 is provided.) 

1301 

1302 :param tz: 

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

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

1305 

1306 :return: 

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

1308 ``tz``. 

1309 

1310 .. versionadded:: 2.6.0 

1311 """ 

1312 if tz is None: 

1313 if dt.tzinfo is None: 

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

1315 

1316 tz = dt.tzinfo 

1317 

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

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

1320 if is_ambiguous_fn is not None: 

1321 try: 

1322 return tz.is_ambiguous(dt) 

1323 except Exception: 

1324 pass 

1325 

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

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

1328 dt = dt.replace(tzinfo=tz) 

1329 wall_0 = enfold(dt, fold=0) 

1330 wall_1 = enfold(dt, fold=1) 

1331 

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

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

1334 

1335 return not (same_offset and same_dst) 

1336 

1337 

1338def resolve_imaginary(dt): 

1339 """ 

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

1341 

1342 This function assumes that an imaginary datetime represents what the 

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

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

1345 

1346 .. doctest:: 

1347 

1348 >>> from dateutil import tz 

1349 >>> from datetime import datetime 

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

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

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

1353 

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

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

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

1357 

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

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

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

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

1362 about this behavior). 

1363 

1364 :param dt: 

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

1366 

1367 :return: 

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

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

1370 passed to the function. 

1371 

1372 .. versionadded:: 2.7.0 

1373 """ 

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

1375 

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

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

1378 

1379 dt += curr_offset - old_offset 

1380 

1381 return dt 

1382 

1383 

1384def _datetime_to_timestamp(dt): 

1385 """ 

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

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

1388 """ 

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

1390 

1391 

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