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

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

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

10import datetime 

11import struct 

12import time 

13import sys 

14import os 

15import bisect 

16import weakref 

17from collections import OrderedDict 

18 

19import six 

20from six import string_types 

21from six.moves import _thread 

22from ._common import tzname_in_python2, _tzinfo 

23from ._common import tzrangebase, enfold 

24from ._common import _validate_fromutc_inputs 

25 

26from ._factories import _TzSingleton, _TzOffsetFactory 

27from ._factories import _TzStrFactory 

28try: 

29 from .win import tzwin, tzwinlocal 

30except ImportError: 

31 tzwin = tzwinlocal = None 

32 

33# For warning about rounding tzinfo 

34from warnings import warn 

35 

36ZERO = datetime.timedelta(0) 

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

38EPOCHORDINAL = EPOCH.toordinal() 

39 

40 

41@six.add_metaclass(_TzSingleton) 

42class tzutc(datetime.tzinfo): 

43 """ 

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

45 

46 **Examples:** 

47 

48 .. doctest:: 

49 

50 >>> from datetime import * 

51 >>> from dateutil.tz import * 

52 

53 >>> datetime.now() 

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

55 

56 >>> datetime.now(tzutc()) 

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

58 

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

60 'UTC' 

61 

62 .. versionchanged:: 2.7.0 

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

64 always return the same object. 

65 

66 .. doctest:: 

67 

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

69 >>> tzutc() is tzutc() 

70 True 

71 >>> tzutc() is UTC 

72 True 

73 """ 

74 def utcoffset(self, dt): 

75 return ZERO 

76 

77 def dst(self, dt): 

78 return ZERO 

79 

80 @tzname_in_python2 

81 def tzname(self, dt): 

82 return "UTC" 

83 

84 def is_ambiguous(self, dt): 

85 """ 

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

87 zone. 

88 

89 :param dt: 

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

91 

92 

93 :return: 

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

95 

96 .. versionadded:: 2.6.0 

97 """ 

98 return False 

99 

100 @_validate_fromutc_inputs 

101 def fromutc(self, dt): 

102 """ 

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

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

105 """ 

106 return dt 

107 

108 def __eq__(self, other): 

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

110 return NotImplemented 

111 

112 return (isinstance(other, tzutc) or 

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

114 

115 __hash__ = None 

116 

117 def __ne__(self, other): 

118 return not (self == other) 

119 

120 def __repr__(self): 

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

122 

123 __reduce__ = object.__reduce__ 

124 

125 

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

127#: 

128#: .. versionadded:: 2.7.0 

129UTC = tzutc() 

130 

131 

132@six.add_metaclass(_TzOffsetFactory) 

133class tzoffset(datetime.tzinfo): 

134 """ 

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

136 

137 :param name: 

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

139 :param offset: 

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

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

142 """ 

143 def __init__(self, name, offset): 

144 self._name = name 

145 

146 try: 

147 # Allow a timedelta 

148 offset = offset.total_seconds() 

149 except (TypeError, AttributeError): 

150 pass 

151 

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

153 

154 def utcoffset(self, dt): 

155 return self._offset 

156 

157 def dst(self, dt): 

158 return ZERO 

159 

160 @tzname_in_python2 

161 def tzname(self, dt): 

162 return self._name 

163 

164 @_validate_fromutc_inputs 

165 def fromutc(self, dt): 

166 return dt + self._offset 

167 

168 def is_ambiguous(self, dt): 

169 """ 

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

171 zone. 

172 

173 :param dt: 

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

175 :return: 

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

177 

178 .. versionadded:: 2.6.0 

179 """ 

180 return False 

181 

182 def __eq__(self, other): 

183 if not isinstance(other, tzoffset): 

184 return NotImplemented 

185 

186 return self._offset == other._offset 

187 

188 __hash__ = None 

189 

190 def __ne__(self, other): 

191 return not (self == other) 

192 

193 def __repr__(self): 

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

195 repr(self._name), 

196 int(self._offset.total_seconds())) 

197 

198 __reduce__ = object.__reduce__ 

199 

200 

201class tzlocal(_tzinfo): 

202 """ 

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

204 """ 

205 def __init__(self): 

206 super(tzlocal, self).__init__() 

207 

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

209 if time.daylight: 

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

211 else: 

212 self._dst_offset = self._std_offset 

213 

214 self._dst_saved = self._dst_offset - self._std_offset 

215 self._hasdst = bool(self._dst_saved) 

216 self._tznames = tuple(time.tzname) 

217 

218 def utcoffset(self, dt): 

219 if dt is None and self._hasdst: 

220 return None 

221 

222 if self._isdst(dt): 

223 return self._dst_offset 

224 else: 

225 return self._std_offset 

226 

227 def dst(self, dt): 

228 if dt is None and self._hasdst: 

229 return None 

230 

231 if self._isdst(dt): 

232 return self._dst_offset - self._std_offset 

233 else: 

234 return ZERO 

235 

236 @tzname_in_python2 

237 def tzname(self, dt): 

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

239 

240 def is_ambiguous(self, dt): 

241 """ 

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

243 zone. 

244 

245 :param dt: 

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

247 

248 

249 :return: 

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

251 

252 .. versionadded:: 2.6.0 

253 """ 

254 naive_dst = self._naive_is_dst(dt) 

255 return (not naive_dst and 

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

257 

258 def _naive_is_dst(self, dt): 

259 timestamp = _datetime_to_timestamp(dt) 

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

261 

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

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

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

265 # 

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

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

268 # return time.localtime(timestamp).tm_isdst 

269 # 

270 # The code above yields the following result: 

271 # 

272 # >>> import tz, datetime 

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

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

275 # 'BRDT' 

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

277 # 'BRST' 

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

279 # 'BRST' 

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

281 # 'BRDT' 

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

283 # 'BRDT' 

284 # 

285 # Here is a more stable implementation: 

286 # 

287 if not self._hasdst: 

288 return False 

289 

290 # Check for ambiguous times: 

291 dstval = self._naive_is_dst(dt) 

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

293 

294 if self.is_ambiguous(dt): 

295 if fold is not None: 

296 return not self._fold(dt) 

297 else: 

298 return True 

299 

300 return dstval 

301 

302 def __eq__(self, other): 

303 if isinstance(other, tzlocal): 

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

305 self._dst_offset == other._dst_offset) 

306 elif isinstance(other, tzutc): 

307 return (not self._hasdst and 

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

309 self._std_offset == ZERO) 

310 elif isinstance(other, tzoffset): 

311 return (not self._hasdst and 

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

313 self._std_offset == other._offset) 

314 else: 

315 return NotImplemented 

316 

317 __hash__ = None 

318 

319 def __ne__(self, other): 

320 return not (self == other) 

321 

322 def __repr__(self): 

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

324 

325 __reduce__ = object.__reduce__ 

326 

327 

328class _ttinfo(object): 

329 __slots__ = ["offset", "delta", "isdst", "abbr", 

330 "isstd", "isgmt", "dstoffset"] 

331 

332 def __init__(self): 

333 for attr in self.__slots__: 

334 setattr(self, attr, None) 

335 

336 def __repr__(self): 

337 l = [] 

338 for attr in self.__slots__: 

339 value = getattr(self, attr) 

340 if value is not None: 

341 l.append("%s=%s" % (attr, repr(value))) 

342 return "%s(%s)" % (self.__class__.__name__, ", ".join(l)) 

343 

344 def __eq__(self, other): 

345 if not isinstance(other, _ttinfo): 

346 return NotImplemented 

347 

348 return (self.offset == other.offset and 

349 self.delta == other.delta and 

350 self.isdst == other.isdst and 

351 self.abbr == other.abbr and 

352 self.isstd == other.isstd and 

353 self.isgmt == other.isgmt and 

354 self.dstoffset == other.dstoffset) 

355 

356 __hash__ = None 

357 

358 def __ne__(self, other): 

359 return not (self == other) 

360 

361 def __getstate__(self): 

362 state = {} 

363 for name in self.__slots__: 

364 state[name] = getattr(self, name, None) 

365 return state 

366 

367 def __setstate__(self, state): 

368 for name in self.__slots__: 

369 if name in state: 

370 setattr(self, name, state[name]) 

371 

372 

373class _tzfile(object): 

374 """ 

375 Lightweight class for holding the relevant transition and time zone 

376 information read from binary tzfiles. 

377 """ 

378 attrs = ['trans_list', 'trans_list_utc', 'trans_idx', 'ttinfo_list', 

379 'ttinfo_std', 'ttinfo_dst', 'ttinfo_before', 'ttinfo_first'] 

380 

381 def __init__(self, **kwargs): 

382 for attr in self.attrs: 

383 setattr(self, attr, kwargs.get(attr, None)) 

384 

385 

386class tzfile(_tzinfo): 

387 """ 

388 This is a ``tzinfo`` subclass that allows one to use the ``tzfile(5)`` 

389 format timezone files to extract current and historical zone information. 

390 

391 :param fileobj: 

392 This can be an opened file stream or a file name that the time zone 

393 information can be read from. 

394 

395 :param filename: 

396 This is an optional parameter specifying the source of the time zone 

397 information in the event that ``fileobj`` is a file object. If omitted 

398 and ``fileobj`` is a file stream, this parameter will be set either to 

399 ``fileobj``'s ``name`` attribute or to ``repr(fileobj)``. 

400 

401 See `Sources for Time Zone and Daylight Saving Time Data 

402 <https://data.iana.org/time-zones/tz-link.html>`_ for more information. 

403 Time zone files can be compiled from the `IANA Time Zone database files 

404 <https://www.iana.org/time-zones>`_ with the `zic time zone compiler 

405 <https://www.freebsd.org/cgi/man.cgi?query=zic&sektion=8>`_ 

406 

407 .. note:: 

408 

409 Only construct a ``tzfile`` directly if you have a specific timezone 

410 file on disk that you want to read into a Python ``tzinfo`` object. 

411 If you want to get a ``tzfile`` representing a specific IANA zone, 

412 (e.g. ``'America/New_York'``), you should call 

413 :func:`dateutil.tz.gettz` with the zone identifier. 

414 

415 

416 **Examples:** 

417 

418 Using the US Eastern time zone as an example, we can see that a ``tzfile`` 

419 provides time zone information for the standard Daylight Saving offsets: 

420 

421 .. testsetup:: tzfile 

422 

423 from dateutil.tz import gettz 

424 from datetime import datetime 

425 

426 .. doctest:: tzfile 

427 

428 >>> NYC = gettz('America/New_York') 

429 >>> NYC 

430 tzfile('/usr/share/zoneinfo/America/New_York') 

431 

432 >>> print(datetime(2016, 1, 3, tzinfo=NYC)) # EST 

433 2016-01-03 00:00:00-05:00 

434 

435 >>> print(datetime(2016, 7, 7, tzinfo=NYC)) # EDT 

436 2016-07-07 00:00:00-04:00 

437 

438 

439 The ``tzfile`` structure contains a fully history of the time zone, 

440 so historical dates will also have the right offsets. For example, before 

441 the adoption of the UTC standards, New York used local solar mean time: 

442 

443 .. doctest:: tzfile 

444 

445 >>> print(datetime(1901, 4, 12, tzinfo=NYC)) # LMT 

446 1901-04-12 00:00:00-04:56 

447 

448 And during World War II, New York was on "Eastern War Time", which was a 

449 state of permanent daylight saving time: 

450 

451 .. doctest:: tzfile 

452 

453 >>> print(datetime(1944, 2, 7, tzinfo=NYC)) # EWT 

454 1944-02-07 00:00:00-04:00 

455 

456 """ 

457 

458 def __init__(self, fileobj, filename=None): 

459 super(tzfile, self).__init__() 

460 

461 file_opened_here = False 

462 if isinstance(fileobj, string_types): 

463 self._filename = fileobj 

464 fileobj = open(fileobj, 'rb') 

465 file_opened_here = True 

466 elif filename is not None: 

467 self._filename = filename 

468 elif hasattr(fileobj, "name"): 

469 self._filename = fileobj.name 

470 else: 

471 self._filename = repr(fileobj) 

472 

473 if fileobj is not None: 

474 if not file_opened_here: 

475 fileobj = _nullcontext(fileobj) 

476 

477 with fileobj as file_stream: 

478 tzobj = self._read_tzfile(file_stream) 

479 

480 self._set_tzdata(tzobj) 

481 

482 def _set_tzdata(self, tzobj): 

483 """ Set the time zone data of this object from a _tzfile object """ 

484 # Copy the relevant attributes over as private attributes 

485 for attr in _tzfile.attrs: 

486 setattr(self, '_' + attr, getattr(tzobj, attr)) 

487 

488 def _read_tzfile(self, fileobj): 

489 out = _tzfile() 

490 

491 # From tzfile(5): 

492 # 

493 # The time zone information files used by tzset(3) 

494 # begin with the magic characters "TZif" to identify 

495 # them as time zone information files, followed by 

496 # sixteen bytes reserved for future use, followed by 

497 # six four-byte values of type long, written in a 

498 # ``standard'' byte order (the high-order byte 

499 # of the value is written first). 

500 if fileobj.read(4).decode() != "TZif": 

501 raise ValueError("magic not found") 

502 

503 fileobj.read(16) 

504 

505 ( 

506 # The number of UTC/local indicators stored in the file. 

507 ttisgmtcnt, 

508 

509 # The number of standard/wall indicators stored in the file. 

510 ttisstdcnt, 

511 

512 # The number of leap seconds for which data is 

513 # stored in the file. 

514 leapcnt, 

515 

516 # The number of "transition times" for which data 

517 # is stored in the file. 

518 timecnt, 

519 

520 # The number of "local time types" for which data 

521 # is stored in the file (must not be zero). 

522 typecnt, 

523 

524 # The number of characters of "time zone 

525 # abbreviation strings" stored in the file. 

526 charcnt, 

527 

528 ) = struct.unpack(">6l", fileobj.read(24)) 

529 

530 # The above header is followed by tzh_timecnt four-byte 

531 # values of type long, sorted in ascending order. 

532 # These values are written in ``standard'' byte order. 

533 # Each is used as a transition time (as returned by 

534 # time(2)) at which the rules for computing local time 

535 # change. 

536 

537 if timecnt: 

538 out.trans_list_utc = list(struct.unpack(">%dl" % timecnt, 

539 fileobj.read(timecnt*4))) 

540 else: 

541 out.trans_list_utc = [] 

542 

543 # Next come tzh_timecnt one-byte values of type unsigned 

544 # char; each one tells which of the different types of 

545 # ``local time'' types described in the file is associated 

546 # with the same-indexed transition time. These values 

547 # serve as indices into an array of ttinfo structures that 

548 # appears next in the file. 

549 

550 if timecnt: 

551 out.trans_idx = struct.unpack(">%dB" % timecnt, 

552 fileobj.read(timecnt)) 

553 else: 

554 out.trans_idx = [] 

555 

556 # Each ttinfo structure is written as a four-byte value 

557 # for tt_gmtoff of type long, in a standard byte 

558 # order, followed by a one-byte value for tt_isdst 

559 # and a one-byte value for tt_abbrind. In each 

560 # structure, tt_gmtoff gives the number of 

561 # seconds to be added to UTC, tt_isdst tells whether 

562 # tm_isdst should be set by localtime(3), and 

563 # tt_abbrind serves as an index into the array of 

564 # time zone abbreviation characters that follow the 

565 # ttinfo structure(s) in the file. 

566 

567 ttinfo = [] 

568 

569 for i in range(typecnt): 

570 ttinfo.append(struct.unpack(">lbb", fileobj.read(6))) 

571 

572 abbr = fileobj.read(charcnt).decode() 

573 

574 # Then there are tzh_leapcnt pairs of four-byte 

575 # values, written in standard byte order; the 

576 # first value of each pair gives the time (as 

577 # returned by time(2)) at which a leap second 

578 # occurs; the second gives the total number of 

579 # leap seconds to be applied after the given time. 

580 # The pairs of values are sorted in ascending order 

581 # by time. 

582 

583 # Not used, for now (but seek for correct file position) 

584 if leapcnt: 

585 fileobj.seek(leapcnt * 8, os.SEEK_CUR) 

586 

587 # Then there are tzh_ttisstdcnt standard/wall 

588 # indicators, each stored as a one-byte value; 

589 # they tell whether the transition times associated 

590 # with local time types were specified as standard 

591 # time or wall clock time, and are used when 

592 # a time zone file is used in handling POSIX-style 

593 # time zone environment variables. 

594 

595 if ttisstdcnt: 

596 isstd = struct.unpack(">%db" % ttisstdcnt, 

597 fileobj.read(ttisstdcnt)) 

598 

599 # Finally, there are tzh_ttisgmtcnt UTC/local 

600 # indicators, each stored as a one-byte value; 

601 # they tell whether the transition times associated 

602 # with local time types were specified as UTC or 

603 # local time, and are used when a time zone file 

604 # is used in handling POSIX-style time zone envi- 

605 # ronment variables. 

606 

607 if ttisgmtcnt: 

608 isgmt = struct.unpack(">%db" % ttisgmtcnt, 

609 fileobj.read(ttisgmtcnt)) 

610 

611 # Build ttinfo list 

612 out.ttinfo_list = [] 

613 for i in range(typecnt): 

614 gmtoff, isdst, abbrind = ttinfo[i] 

615 gmtoff = _get_supported_offset(gmtoff) 

616 tti = _ttinfo() 

617 tti.offset = gmtoff 

618 tti.dstoffset = datetime.timedelta(0) 

619 tti.delta = datetime.timedelta(seconds=gmtoff) 

620 tti.isdst = isdst 

621 tti.abbr = abbr[abbrind:abbr.find('\x00', abbrind)] 

622 tti.isstd = (ttisstdcnt > i and isstd[i] != 0) 

623 tti.isgmt = (ttisgmtcnt > i and isgmt[i] != 0) 

624 out.ttinfo_list.append(tti) 

625 

626 # Replace ttinfo indexes for ttinfo objects. 

627 out.trans_idx = [out.ttinfo_list[idx] for idx in out.trans_idx] 

628 

629 # Set standard, dst, and before ttinfos. before will be 

630 # used when a given time is before any transitions, 

631 # and will be set to the first non-dst ttinfo, or to 

632 # the first dst, if all of them are dst. 

633 out.ttinfo_std = None 

634 out.ttinfo_dst = None 

635 out.ttinfo_before = None 

636 if out.ttinfo_list: 

637 if not out.trans_list_utc: 

638 out.ttinfo_std = out.ttinfo_first = out.ttinfo_list[0] 

639 else: 

640 for i in range(timecnt-1, -1, -1): 

641 tti = out.trans_idx[i] 

642 if not out.ttinfo_std and not tti.isdst: 

643 out.ttinfo_std = tti 

644 elif not out.ttinfo_dst and tti.isdst: 

645 out.ttinfo_dst = tti 

646 

647 if out.ttinfo_std and out.ttinfo_dst: 

648 break 

649 else: 

650 if out.ttinfo_dst and not out.ttinfo_std: 

651 out.ttinfo_std = out.ttinfo_dst 

652 

653 for tti in out.ttinfo_list: 

654 if not tti.isdst: 

655 out.ttinfo_before = tti 

656 break 

657 else: 

658 out.ttinfo_before = out.ttinfo_list[0] 

659 

660 # Now fix transition times to become relative to wall time. 

661 # 

662 # I'm not sure about this. In my tests, the tz source file 

663 # is setup to wall time, and in the binary file isstd and 

664 # isgmt are off, so it should be in wall time. OTOH, it's 

665 # always in gmt time. Let me know if you have comments 

666 # about this. 

667 lastdst = None 

668 lastoffset = None 

669 lastdstoffset = None 

670 lastbaseoffset = None 

671 out.trans_list = [] 

672 

673 for i, tti in enumerate(out.trans_idx): 

674 offset = tti.offset 

675 dstoffset = 0 

676 

677 if lastdst is not None: 

678 if tti.isdst: 

679 if not lastdst: 

680 dstoffset = offset - lastoffset 

681 

682 if not dstoffset and lastdstoffset: 

683 dstoffset = lastdstoffset 

684 

685 tti.dstoffset = datetime.timedelta(seconds=dstoffset) 

686 lastdstoffset = dstoffset 

687 

688 # If a time zone changes its base offset during a DST transition, 

689 # then you need to adjust by the previous base offset to get the 

690 # transition time in local time. Otherwise you use the current 

691 # base offset. Ideally, I would have some mathematical proof of 

692 # why this is true, but I haven't really thought about it enough. 

693 baseoffset = offset - dstoffset 

694 adjustment = baseoffset 

695 if (lastbaseoffset is not None and baseoffset != lastbaseoffset 

696 and tti.isdst != lastdst): 

697 # The base DST has changed 

698 adjustment = lastbaseoffset 

699 

700 lastdst = tti.isdst 

701 lastoffset = offset 

702 lastbaseoffset = baseoffset 

703 

704 out.trans_list.append(out.trans_list_utc[i] + adjustment) 

705 

706 out.trans_idx = tuple(out.trans_idx) 

707 out.trans_list = tuple(out.trans_list) 

708 out.trans_list_utc = tuple(out.trans_list_utc) 

709 

710 return out 

711 

712 def _find_last_transition(self, dt, in_utc=False): 

713 # If there's no list, there are no transitions to find 

714 if not self._trans_list: 

715 return None 

716 

717 timestamp = _datetime_to_timestamp(dt) 

718 

719 # Find where the timestamp fits in the transition list - if the 

720 # timestamp is a transition time, it's part of the "after" period. 

721 trans_list = self._trans_list_utc if in_utc else self._trans_list 

722 idx = bisect.bisect_right(trans_list, timestamp) 

723 

724 # We want to know when the previous transition was, so subtract off 1 

725 return idx - 1 

726 

727 def _get_ttinfo(self, idx): 

728 # For no list or after the last transition, default to _ttinfo_std 

729 if idx is None or (idx + 1) >= len(self._trans_list): 

730 return self._ttinfo_std 

731 

732 # If there is a list and the time is before it, return _ttinfo_before 

733 if idx < 0: 

734 return self._ttinfo_before 

735 

736 return self._trans_idx[idx] 

737 

738 def _find_ttinfo(self, dt): 

739 idx = self._resolve_ambiguous_time(dt) 

740 

741 return self._get_ttinfo(idx) 

742 

743 def fromutc(self, dt): 

744 """ 

745 The ``tzfile`` implementation of :py:func:`datetime.tzinfo.fromutc`. 

746 

747 :param dt: 

748 A :py:class:`datetime.datetime` object. 

749 

750 :raises TypeError: 

751 Raised if ``dt`` is not a :py:class:`datetime.datetime` object. 

752 

753 :raises ValueError: 

754 Raised if this is called with a ``dt`` which does not have this 

755 ``tzinfo`` attached. 

756 

757 :return: 

758 Returns a :py:class:`datetime.datetime` object representing the 

759 wall time in ``self``'s time zone. 

760 """ 

761 # These isinstance checks are in datetime.tzinfo, so we'll preserve 

762 # them, even if we don't care about duck typing. 

763 if not isinstance(dt, datetime.datetime): 

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

765 

766 if dt.tzinfo is not self: 

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

768 

769 # First treat UTC as wall time and get the transition we're in. 

770 idx = self._find_last_transition(dt, in_utc=True) 

771 tti = self._get_ttinfo(idx) 

772 

773 dt_out = dt + datetime.timedelta(seconds=tti.offset) 

774 

775 fold = self.is_ambiguous(dt_out, idx=idx) 

776 

777 return enfold(dt_out, fold=int(fold)) 

778 

779 def is_ambiguous(self, dt, idx=None): 

780 """ 

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

782 zone. 

783 

784 :param dt: 

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

786 

787 

788 :return: 

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

790 

791 .. versionadded:: 2.6.0 

792 """ 

793 if idx is None: 

794 idx = self._find_last_transition(dt) 

795 

796 # Calculate the difference in offsets from current to previous 

797 timestamp = _datetime_to_timestamp(dt) 

798 tti = self._get_ttinfo(idx) 

799 

800 if idx is None or idx <= 0: 

801 return False 

802 

803 od = self._get_ttinfo(idx - 1).offset - tti.offset 

804 tt = self._trans_list[idx] # Transition time 

805 

806 return timestamp < tt + od 

807 

808 def _resolve_ambiguous_time(self, dt): 

809 idx = self._find_last_transition(dt) 

810 

811 # If we have no transitions, return the index 

812 _fold = self._fold(dt) 

813 if idx is None or idx == 0: 

814 return idx 

815 

816 # If it's ambiguous and we're in a fold, shift to a different index. 

817 idx_offset = int(not _fold and self.is_ambiguous(dt, idx)) 

818 

819 return idx - idx_offset 

820 

821 def utcoffset(self, dt): 

822 if dt is None: 

823 return None 

824 

825 if not self._ttinfo_std: 

826 return ZERO 

827 

828 return self._find_ttinfo(dt).delta 

829 

830 def dst(self, dt): 

831 if dt is None: 

832 return None 

833 

834 if not self._ttinfo_dst: 

835 return ZERO 

836 

837 tti = self._find_ttinfo(dt) 

838 

839 if not tti.isdst: 

840 return ZERO 

841 

842 # The documentation says that utcoffset()-dst() must 

843 # be constant for every dt. 

844 return tti.dstoffset 

845 

846 @tzname_in_python2 

847 def tzname(self, dt): 

848 if not self._ttinfo_std or dt is None: 

849 return None 

850 return self._find_ttinfo(dt).abbr 

851 

852 def __eq__(self, other): 

853 if not isinstance(other, tzfile): 

854 return NotImplemented 

855 return (self._trans_list == other._trans_list and 

856 self._trans_idx == other._trans_idx and 

857 self._ttinfo_list == other._ttinfo_list) 

858 

859 __hash__ = None 

860 

861 def __ne__(self, other): 

862 return not (self == other) 

863 

864 def __repr__(self): 

865 return "%s(%s)" % (self.__class__.__name__, repr(self._filename)) 

866 

867 def __reduce__(self): 

868 return self.__reduce_ex__(None) 

869 

870 def __reduce_ex__(self, protocol): 

871 return (self.__class__, (None, self._filename), self.__dict__) 

872 

873 

874class tzrange(tzrangebase): 

875 """ 

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

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

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

879 start, end and offsets. 

880 

881 :param stdabbr: 

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

883 

884 :param stdoffset: 

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

886 specifying the base offset from UTC. 

887 

888 If unspecified, +00:00 is used. 

889 

890 :param dstabbr: 

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

892 

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

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

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

896 is assumed that this zone has no DST. 

897 

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

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

900 unchanged. 

901 

902 :param dstoffset: 

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

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

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

906 

907 :param start: 

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

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

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

911 March, pass: 

912 

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

914 

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

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

917 

918 :param end: 

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

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

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

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

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

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

925 

926 

927 **Examples:** 

928 

929 .. testsetup:: tzrange 

930 

931 from dateutil.tz import tzrange, tzstr 

932 

933 .. doctest:: tzrange 

934 

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

936 True 

937 

938 >>> from dateutil.relativedelta import * 

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

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

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

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

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

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

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

946 True 

947 

948 """ 

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

950 dstabbr=None, dstoffset=None, 

951 start=None, end=None): 

952 

953 global relativedelta 

954 from dateutil import relativedelta 

955 

956 self._std_abbr = stdabbr 

957 self._dst_abbr = dstabbr 

958 

959 try: 

960 stdoffset = stdoffset.total_seconds() 

961 except (TypeError, AttributeError): 

962 pass 

963 

964 try: 

965 dstoffset = dstoffset.total_seconds() 

966 except (TypeError, AttributeError): 

967 pass 

968 

969 if stdoffset is not None: 

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

971 else: 

972 self._std_offset = ZERO 

973 

974 if dstoffset is not None: 

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

976 elif dstabbr and stdoffset is not None: 

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

978 else: 

979 self._dst_offset = ZERO 

980 

981 if dstabbr and start is None: 

982 self._start_delta = relativedelta.relativedelta( 

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

984 else: 

985 self._start_delta = start 

986 

987 if dstabbr and end is None: 

988 self._end_delta = relativedelta.relativedelta( 

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

990 else: 

991 self._end_delta = end 

992 

993 self._dst_base_offset_ = self._dst_offset - self._std_offset 

994 self.hasdst = bool(self._start_delta) 

995 

996 def transitions(self, year): 

997 """ 

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

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

1000 function returns ``None``. 

1001 

1002 :param year: 

1003 The year whose transitions you would like to query. 

1004 

1005 :return: 

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

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

1008 ``None`` for fixed offset zones. 

1009 """ 

1010 if not self.hasdst: 

1011 return None 

1012 

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

1014 

1015 start = base_year + self._start_delta 

1016 end = base_year + self._end_delta 

1017 

1018 return (start, end) 

1019 

1020 def __eq__(self, other): 

1021 if not isinstance(other, tzrange): 

1022 return NotImplemented 

1023 

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

1025 self._dst_abbr == other._dst_abbr and 

1026 self._std_offset == other._std_offset and 

1027 self._dst_offset == other._dst_offset and 

1028 self._start_delta == other._start_delta and 

1029 self._end_delta == other._end_delta) 

1030 

1031 @property 

1032 def _dst_base_offset(self): 

1033 return self._dst_base_offset_ 

1034 

1035 

1036@six.add_metaclass(_TzStrFactory) 

1037class tzstr(tzrange): 

1038 """ 

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

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

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

1042 

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

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

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

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

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

1048 

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

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

1051 strings. 

1052 

1053 :param s: 

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

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

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

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

1058 

1059 :param posix_offset: 

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

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

1062 POSIX standard. 

1063 

1064 .. caution:: 

1065 

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

1067 in the format: 

1068 

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

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

1071 

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

1073 will raise a :class:`DeprecatedTZFormatWarning` until 

1074 support is removed in a future version. 

1075 

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

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

1078 """ 

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

1080 global parser 

1081 from dateutil.parser import _parser as parser 

1082 

1083 self._s = s 

1084 

1085 res = parser._parsetz(s) 

1086 if res is None or res.any_unused_tokens: 

1087 raise ValueError("unknown string format") 

1088 

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

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

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

1092 res.stdoffset *= -1 

1093 

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

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

1096 # to avoid building it two times. 

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

1098 res.dstabbr, res.dstoffset, 

1099 start=False, end=False) 

1100 

1101 if not res.dstabbr: 

1102 self._start_delta = None 

1103 self._end_delta = None 

1104 else: 

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

1106 if self._start_delta: 

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

1108 

1109 self.hasdst = bool(self._start_delta) 

1110 

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

1112 from dateutil import relativedelta 

1113 kwargs = {} 

1114 if x.month is not None: 

1115 kwargs["month"] = x.month 

1116 if x.weekday is not None: 

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

1118 if x.week > 0: 

1119 kwargs["day"] = 1 

1120 else: 

1121 kwargs["day"] = 31 

1122 elif x.day: 

1123 kwargs["day"] = x.day 

1124 elif x.yday is not None: 

1125 kwargs["yearday"] = x.yday 

1126 elif x.jyday is not None: 

1127 kwargs["nlyearday"] = x.jyday 

1128 if not kwargs: 

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

1130 # on last sunday of october. 

1131 if not isend: 

1132 kwargs["month"] = 4 

1133 kwargs["day"] = 1 

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

1135 else: 

1136 kwargs["month"] = 10 

1137 kwargs["day"] = 31 

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

1139 if x.time is not None: 

1140 kwargs["seconds"] = x.time 

1141 else: 

1142 # Default is 2AM. 

1143 kwargs["seconds"] = 7200 

1144 if isend: 

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

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

1147 # of the tzinfo class. 

1148 delta = self._dst_offset - self._std_offset 

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

1150 return relativedelta.relativedelta(**kwargs) 

1151 

1152 def __repr__(self): 

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

1154 

1155 

1156class _tzicalvtzcomp(object): 

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

1158 tzname=None, rrule=None): 

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

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

1161 self.tzoffsetdiff = self.tzoffsetto - self.tzoffsetfrom 

1162 self.isdst = isdst 

1163 self.tzname = tzname 

1164 self.rrule = rrule 

1165 

1166 

1167class _tzicalvtz(_tzinfo): 

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

1169 super(_tzicalvtz, self).__init__() 

1170 

1171 self._tzid = tzid 

1172 self._comps = comps 

1173 self._cachedate = [] 

1174 self._cachecomp = [] 

1175 self._cache_lock = _thread.allocate_lock() 

1176 

1177 def _find_comp(self, dt): 

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

1179 return self._comps[0] 

1180 

1181 dt = dt.replace(tzinfo=None) 

1182 

1183 try: 

1184 with self._cache_lock: 

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

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

1187 except ValueError: 

1188 pass 

1189 

1190 lastcompdt = None 

1191 lastcomp = None 

1192 

1193 for comp in self._comps: 

1194 compdt = self._find_compdt(comp, dt) 

1195 

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

1197 lastcompdt = compdt 

1198 lastcomp = comp 

1199 

1200 if not lastcomp: 

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

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

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

1204 # none is found. 

1205 for comp in self._comps: 

1206 if not comp.isdst: 

1207 lastcomp = comp 

1208 break 

1209 else: 

1210 lastcomp = comp[0] 

1211 

1212 with self._cache_lock: 

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

1214 self._cachecomp.insert(0, lastcomp) 

1215 

1216 if len(self._cachedate) > 10: 

1217 self._cachedate.pop() 

1218 self._cachecomp.pop() 

1219 

1220 return lastcomp 

1221 

1222 def _find_compdt(self, comp, dt): 

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

1224 dt -= comp.tzoffsetdiff 

1225 

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

1227 

1228 return compdt 

1229 

1230 def utcoffset(self, dt): 

1231 if dt is None: 

1232 return None 

1233 

1234 return self._find_comp(dt).tzoffsetto 

1235 

1236 def dst(self, dt): 

1237 comp = self._find_comp(dt) 

1238 if comp.isdst: 

1239 return comp.tzoffsetdiff 

1240 else: 

1241 return ZERO 

1242 

1243 @tzname_in_python2 

1244 def tzname(self, dt): 

1245 return self._find_comp(dt).tzname 

1246 

1247 def __repr__(self): 

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

1249 

1250 __reduce__ = object.__reduce__ 

1251 

1252 

1253class tzical(object): 

1254 """ 

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

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

1257 

1258 :param `fileobj`: 

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

1260 with CRLF endings. 

1261 

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

1263 """ 

1264 def __init__(self, fileobj): 

1265 global rrule 

1266 from dateutil import rrule 

1267 

1268 if isinstance(fileobj, string_types): 

1269 self._s = fileobj 

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

1271 fileobj = open(fileobj, 'r') 

1272 else: 

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

1274 fileobj = _nullcontext(fileobj) 

1275 

1276 self._vtz = {} 

1277 

1278 with fileobj as fobj: 

1279 self._parse_rfc(fobj.read()) 

1280 

1281 def keys(self): 

1282 """ 

1283 Retrieves the available time zones as a list. 

1284 """ 

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

1286 

1287 def get(self, tzid=None): 

1288 """ 

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

1290 

1291 :param tzid: 

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

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

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

1295 

1296 :raises ValueError: 

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

1298 or fewer than 1 zone defined. 

1299 

1300 :returns: 

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

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

1303 not found. 

1304 """ 

1305 if tzid is None: 

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

1307 raise ValueError("no timezones defined") 

1308 elif len(self._vtz) > 1: 

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

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

1311 

1312 return self._vtz.get(tzid) 

1313 

1314 def _parse_offset(self, s): 

1315 s = s.strip() 

1316 if not s: 

1317 raise ValueError("empty offset") 

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

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

1320 s = s[1:] 

1321 else: 

1322 signal = +1 

1323 if len(s) == 4: 

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

1325 elif len(s) == 6: 

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

1327 else: 

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

1329 

1330 def _parse_rfc(self, s): 

1331 lines = s.splitlines() 

1332 if not lines: 

1333 raise ValueError("empty string") 

1334 

1335 # Unfold 

1336 i = 0 

1337 while i < len(lines): 

1338 line = lines[i].rstrip() 

1339 if not line: 

1340 del lines[i] 

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

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

1343 del lines[i] 

1344 else: 

1345 i += 1 

1346 

1347 tzid = None 

1348 comps = [] 

1349 invtz = False 

1350 comptype = None 

1351 for line in lines: 

1352 if not line: 

1353 continue 

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

1355 parms = name.split(';') 

1356 if not parms: 

1357 raise ValueError("empty property name") 

1358 name = parms[0].upper() 

1359 parms = parms[1:] 

1360 if invtz: 

1361 if name == "BEGIN": 

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

1363 # Process component 

1364 pass 

1365 else: 

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

1367 comptype = value 

1368 founddtstart = False 

1369 tzoffsetfrom = None 

1370 tzoffsetto = None 

1371 rrulelines = [] 

1372 tzname = None 

1373 elif name == "END": 

1374 if value == "VTIMEZONE": 

1375 if comptype: 

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

1377 if not tzid: 

1378 raise ValueError("mandatory TZID not found") 

1379 if not comps: 

1380 raise ValueError( 

1381 "at least one component is needed") 

1382 # Process vtimezone 

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

1384 invtz = False 

1385 elif value == comptype: 

1386 if not founddtstart: 

1387 raise ValueError("mandatory DTSTART not found") 

1388 if tzoffsetfrom is None: 

1389 raise ValueError( 

1390 "mandatory TZOFFSETFROM not found") 

1391 if tzoffsetto is None: 

1392 raise ValueError( 

1393 "mandatory TZOFFSETFROM not found") 

1394 # Process component 

1395 rr = None 

1396 if rrulelines: 

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

1398 compatible=True, 

1399 ignoretz=True, 

1400 cache=True) 

1401 comp = _tzicalvtzcomp(tzoffsetfrom, tzoffsetto, 

1402 (comptype == "DAYLIGHT"), 

1403 tzname, rr) 

1404 comps.append(comp) 

1405 comptype = None 

1406 else: 

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

1408 elif comptype: 

1409 if name == "DTSTART": 

1410 # DTSTART in VTIMEZONE takes a subset of valid RRULE 

1411 # values under RFC 5545. 

1412 for parm in parms: 

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

1414 msg = ('Unsupported DTSTART param in ' + 

1415 'VTIMEZONE: ' + parm) 

1416 raise ValueError(msg) 

1417 rrulelines.append(line) 

1418 founddtstart = True 

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

1420 rrulelines.append(line) 

1421 elif name == "TZOFFSETFROM": 

1422 if parms: 

1423 raise ValueError( 

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

1425 tzoffsetfrom = self._parse_offset(value) 

1426 elif name == "TZOFFSETTO": 

1427 if parms: 

1428 raise ValueError( 

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

1430 tzoffsetto = self._parse_offset(value) 

1431 elif name == "TZNAME": 

1432 if parms: 

1433 raise ValueError( 

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

1435 tzname = value 

1436 elif name == "COMMENT": 

1437 pass 

1438 else: 

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

1440 else: 

1441 if name == "TZID": 

1442 if parms: 

1443 raise ValueError( 

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

1445 tzid = value 

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

1447 pass 

1448 else: 

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

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

1451 tzid = None 

1452 comps = [] 

1453 invtz = True 

1454 

1455 def __repr__(self): 

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

1457 

1458 

1459if sys.platform != "win32": 

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

1461 TZPATHS = ["/usr/share/zoneinfo", 

1462 "/usr/lib/zoneinfo", 

1463 "/usr/share/lib/zoneinfo", 

1464 "/etc/zoneinfo"] 

1465else: 

1466 TZFILES = [] 

1467 TZPATHS = [] 

1468 

1469 

1470def __get_gettz(): 

1471 tzlocal_classes = (tzlocal,) 

1472 if tzwinlocal is not None: 

1473 tzlocal_classes += (tzwinlocal,) 

1474 

1475 class GettzFunc(object): 

1476 """ 

1477 Retrieve a time zone object from a string representation 

1478 

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

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

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

1482 

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

1484 is returned: 

1485 

1486 .. code-block:: python3 

1487 

1488 >>> gettz() 

1489 tzfile('/etc/localtime') 

1490 

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

1492 to :class:`tzfile` objects: 

1493 

1494 .. code-block:: python3 

1495 

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

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

1498 

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

1500 zone names provided by the operating system: 

1501 

1502 .. code-block:: python3 

1503 

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

1505 tzwin('Egypt Standard Time') 

1506 

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

1508 :class:`tzstr` object: 

1509 

1510 .. code-block:: python3 

1511 

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

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

1514 

1515 :param name: 

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

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

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

1519 as local time. 

1520 

1521 :return: 

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

1523 subclasses. 

1524 

1525 .. versionchanged:: 2.7.0 

1526 

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

1528 input strings will return the same object: 

1529 

1530 .. code-block:: python3 

1531 

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

1533 True 

1534 

1535 In addition to improving performance, this ensures that 

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

1537 

1538 

1539 .. _`TZ variable`: 

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

1541 

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

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

1544 """ 

1545 def __init__(self): 

1546 

1547 self.__instances = weakref.WeakValueDictionary() 

1548 self.__strong_cache_size = 8 

1549 self.__strong_cache = OrderedDict() 

1550 self._cache_lock = _thread.allocate_lock() 

1551 

1552 def __call__(self, name=None): 

1553 with self._cache_lock: 

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

1555 

1556 if rv is None: 

1557 rv = self.nocache(name=name) 

1558 if not (name is None 

1559 or isinstance(rv, tzlocal_classes) 

1560 or rv is None): 

1561 # tzlocal is slightly more complicated than the other 

1562 # time zone providers because it depends on environment 

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

1564 # 

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

1566 # will also not store that. 

1567 self.__instances[name] = rv 

1568 else: 

1569 # No need for strong caching, return immediately 

1570 return rv 

1571 

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

1573 

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

1575 self.__strong_cache.popitem(last=False) 

1576 

1577 return rv 

1578 

1579 def set_cache_size(self, size): 

1580 with self._cache_lock: 

1581 self.__strong_cache_size = size 

1582 while len(self.__strong_cache) > size: 

1583 self.__strong_cache.popitem(last=False) 

1584 

1585 def cache_clear(self): 

1586 with self._cache_lock: 

1587 self.__instances = weakref.WeakValueDictionary() 

1588 self.__strong_cache.clear() 

1589 

1590 @staticmethod 

1591 def nocache(name=None): 

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

1593 tz = None 

1594 if not name: 

1595 try: 

1596 name = os.environ["TZ"] 

1597 except KeyError: 

1598 pass 

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

1600 for filepath in TZFILES: 

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

1602 filename = filepath 

1603 for path in TZPATHS: 

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

1605 if os.path.isfile(filepath): 

1606 break 

1607 else: 

1608 continue 

1609 if os.path.isfile(filepath): 

1610 try: 

1611 tz = tzfile(filepath) 

1612 break 

1613 except (IOError, OSError, ValueError): 

1614 pass 

1615 else: 

1616 tz = tzlocal() 

1617 else: 

1618 try: 

1619 if name.startswith(":"): 

1620 name = name[1:] 

1621 except TypeError as e: 

1622 if isinstance(name, bytes): 

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

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

1625 else: 

1626 raise 

1627 if os.path.isabs(name): 

1628 if os.path.isfile(name): 

1629 tz = tzfile(name) 

1630 else: 

1631 tz = None 

1632 else: 

1633 for path in TZPATHS: 

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

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

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

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

1638 continue 

1639 try: 

1640 tz = tzfile(filepath) 

1641 break 

1642 except (IOError, OSError, ValueError): 

1643 pass 

1644 else: 

1645 tz = None 

1646 if tzwin is not None: 

1647 try: 

1648 tz = tzwin(name) 

1649 except (WindowsError, UnicodeEncodeError): 

1650 # UnicodeEncodeError is for Python 2.7 compat 

1651 tz = None 

1652 

1653 if not tz: 

1654 from dateutil.zoneinfo import get_zonefile_instance 

1655 tz = get_zonefile_instance().get(name) 

1656 

1657 if not tz: 

1658 for c in name: 

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

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

1661 # explicit for loop seems to be the fastest way 

1662 # To determine if a string contains a digit 

1663 if c in "0123456789": 

1664 try: 

1665 tz = tzstr(name) 

1666 except ValueError: 

1667 pass 

1668 break 

1669 else: 

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

1671 tz = UTC 

1672 elif name in time.tzname: 

1673 tz = tzlocal() 

1674 return tz 

1675 

1676 return GettzFunc() 

1677 

1678 

1679gettz = __get_gettz() 

1680del __get_gettz 

1681 

1682 

1683def datetime_exists(dt, tz=None): 

1684 """ 

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

1686 would fall in a gap. 

1687 

1688 :param dt: 

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

1690 is provided.) 

1691 

1692 :param tz: 

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

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

1695 

1696 :return: 

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

1698 ``tz``. 

1699 

1700 .. versionadded:: 2.7.0 

1701 """ 

1702 if tz is None: 

1703 if dt.tzinfo is None: 

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

1705 tz = dt.tzinfo 

1706 

1707 dt = dt.replace(tzinfo=None) 

1708 

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

1710 # a round trip to UTC. 

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

1712 dt_rt = dt_rt.replace(tzinfo=None) 

1713 

1714 return dt == dt_rt 

1715 

1716 

1717def datetime_ambiguous(dt, tz=None): 

1718 """ 

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

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

1721 status). 

1722 

1723 :param dt: 

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

1725 is provided.) 

1726 

1727 :param tz: 

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

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

1730 

1731 :return: 

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

1733 ``tz``. 

1734 

1735 .. versionadded:: 2.6.0 

1736 """ 

1737 if tz is None: 

1738 if dt.tzinfo is None: 

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

1740 

1741 tz = dt.tzinfo 

1742 

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

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

1745 if is_ambiguous_fn is not None: 

1746 try: 

1747 return tz.is_ambiguous(dt) 

1748 except Exception: 

1749 pass 

1750 

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

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

1753 dt = dt.replace(tzinfo=tz) 

1754 wall_0 = enfold(dt, fold=0) 

1755 wall_1 = enfold(dt, fold=1) 

1756 

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

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

1759 

1760 return not (same_offset and same_dst) 

1761 

1762 

1763def resolve_imaginary(dt): 

1764 """ 

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

1766 

1767 This function assumes that an imaginary datetime represents what the 

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

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

1770 

1771 .. doctest:: 

1772 

1773 >>> from dateutil import tz 

1774 >>> from datetime import datetime 

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

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

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

1778 

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

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

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

1782 

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

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

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

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

1787 about this behavior). 

1788 

1789 :param dt: 

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

1791 

1792 :return: 

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

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

1795 passed to the function. 

1796 

1797 .. versionadded:: 2.7.0 

1798 """ 

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

1800 

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

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

1803 

1804 dt += curr_offset - old_offset 

1805 

1806 return dt 

1807 

1808 

1809def _datetime_to_timestamp(dt): 

1810 """ 

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

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

1813 """ 

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

1815 

1816 

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

1818 def _get_supported_offset(second_offset): 

1819 return second_offset 

1820else: 

1821 def _get_supported_offset(second_offset): 

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

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

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

1825 # for some information. 

1826 old_offset = second_offset 

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

1828 return calculated_offset 

1829 

1830 

1831try: 

1832 # Python 3.7 feature 

1833 from contextlib import nullcontext as _nullcontext 

1834except ImportError: 

1835 class _nullcontext(object): 

1836 """ 

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

1838 with statement. 

1839 """ 

1840 def __init__(self, context): 

1841 self.context = context 

1842 

1843 def __enter__(self): 

1844 return self.context 

1845 

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

1847 pass 

1848 

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