Coverage for /usr/lib/python3/dist-packages/pytz/__init__.py: 42%
216 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1'''
2datetime.tzinfo timezone definitions generated from the
3Olson timezone database:
5 ftp://elsie.nci.nih.gov/pub/tz*.tar.gz
7See the datetime section of the Python Library Reference for information
8on how to use these modules.
9'''
11import sys
12import datetime
13import os.path
14import pathlib
15import re
16import zoneinfo
18from pytz.exceptions import AmbiguousTimeError
19from pytz.exceptions import InvalidTimeError
20from pytz.exceptions import NonExistentTimeError
21from pytz.exceptions import UnknownTimeZoneError
22from pytz.lazy import LazyDict, LazyList, LazySet # noqa
23from pytz.tzinfo import unpickler, BaseTzInfo
24from pytz.tzfile import build_tzinfo
27def _read_olson_version() -> str:
28 tzdata_zi = pathlib.Path("/usr/share/zoneinfo/tzdata.zi")
29 with tzdata_zi.open(encoding="utf-8") as tzdata_zi_file:
30 line = tzdata_zi_file.readline()
31 match = re.match(r"^#\s*version\s*([0-9a-z]*)\s*$", line)
32 if not match:
33 return "unknown"
34 return match.group(1)
37# The IANA (nee Olson) database is updated several times a year.
38OLSON_VERSION = _read_olson_version()
39VERSION = '2024.1' # pip compatible version number.
40__version__ = VERSION
42OLSEN_VERSION = OLSON_VERSION # Old releases had this misspelling
44__all__ = [
45 'timezone', 'utc', 'country_timezones', 'country_names',
46 'AmbiguousTimeError', 'InvalidTimeError',
47 'NonExistentTimeError', 'UnknownTimeZoneError',
48 'all_timezones', 'all_timezones_set',
49 'common_timezones', 'common_timezones_set',
50 'BaseTzInfo', 'FixedOffset',
51]
54if sys.version_info[0] > 2: # Python 3.x
56 # Python 3.x doesn't have unicode(), making writing code
57 # for Python 2.3 and Python 3.x a pain.
58 unicode = str
60 def ascii(s):
61 r"""
62 >>> ascii('Hello')
63 'Hello'
64 >>> ascii('\N{TRADE MARK SIGN}') #doctest: +IGNORE_EXCEPTION_DETAIL
65 Traceback (most recent call last):
66 ...
67 UnicodeEncodeError: ...
68 """
69 if type(s) == bytes:
70 s = s.decode('ASCII')
71 else:
72 s.encode('ASCII') # Raise an exception if not ASCII
73 return s # But the string - not a byte string.
75else: # Python 2.x
77 def ascii(s):
78 r"""
79 >>> ascii('Hello')
80 'Hello'
81 >>> ascii(u'Hello')
82 'Hello'
83 >>> ascii(u'\N{TRADE MARK SIGN}') #doctest: +IGNORE_EXCEPTION_DETAIL
84 Traceback (most recent call last):
85 ...
86 UnicodeEncodeError: ...
87 """
88 return s.encode('ASCII')
91def open_resource(name):
92 """Open a resource from the zoneinfo subdir for reading.
94 Uses the pkg_resources module if available and no standard file
95 found at the calculated location.
97 It is possible to specify different location for zoneinfo
98 subdir by using the PYTZ_TZDATADIR environment variable.
99 """
100 name_parts = name.lstrip('/').split('/')
101 for part in name_parts:
102 if part == os.path.pardir or os.sep in part:
103 raise ValueError('Bad path segment: %r' % part)
104 zoneinfo_dir = os.environ.get('PYTZ_TZDATADIR', None)
105 if zoneinfo_dir is not None:
106 filename = os.path.join(zoneinfo_dir, *name_parts)
107 else:
108 filename = os.path.join('/usr','share',
109 'zoneinfo', *name_parts)
110 if not os.path.exists(filename):
111 # http://bugs.launchpad.net/bugs/383171 - we avoid using this
112 # unless absolutely necessary to help when a broken version of
113 # pkg_resources is installed.
114 try:
115 from pkg_resources import resource_stream
116 except ImportError:
117 resource_stream = None
119 if resource_stream is not None:
120 return resource_stream(__name__, 'zoneinfo/' + name)
121 return open(filename, 'rb')
124def resource_exists(name):
125 """Return true if the given resource exists"""
126 try:
127 if os.environ.get('PYTZ_SKIPEXISTSCHECK', ''):
128 # In "standard" distributions, we can assume that
129 # all the listed timezones are present. As an
130 # import-speed optimization, you can set the
131 # PYTZ_SKIPEXISTSCHECK flag to skip checking
132 # for the presence of the resource file on disk.
133 return True
134 open_resource(name).close()
135 return True
136 except IOError:
137 return False
140_tzinfo_cache = {}
143def timezone(zone):
144 r''' Return a datetime.tzinfo implementation for the given timezone
146 >>> from datetime import datetime, timedelta
147 >>> utc = timezone('UTC')
148 >>> eastern = timezone('US/Eastern')
149 >>> eastern.zone
150 'US/Eastern'
151 >>> timezone(unicode('US/Eastern')) is eastern
152 True
153 >>> utc_dt = datetime(2002, 10, 27, 6, 0, 0, tzinfo=utc)
154 >>> loc_dt = utc_dt.astimezone(eastern)
155 >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)'
156 >>> loc_dt.strftime(fmt)
157 '2002-10-27 01:00:00 EST (-0500)'
158 >>> (loc_dt - timedelta(minutes=10)).strftime(fmt)
159 '2002-10-27 00:50:00 EST (-0500)'
160 >>> eastern.normalize(loc_dt - timedelta(minutes=10)).strftime(fmt)
161 '2002-10-27 01:50:00 EDT (-0400)'
162 >>> (loc_dt + timedelta(minutes=10)).strftime(fmt)
163 '2002-10-27 01:10:00 EST (-0500)'
165 Raises UnknownTimeZoneError if passed an unknown zone.
167 >>> try:
168 ... timezone('Asia/Shangri-La')
169 ... except UnknownTimeZoneError:
170 ... print('Unknown')
171 Unknown
173 >>> try:
174 ... timezone(unicode('\N{TRADE MARK SIGN}'))
175 ... except UnknownTimeZoneError:
176 ... print('Unknown')
177 Unknown
179 '''
180 if zone is None:
181 raise UnknownTimeZoneError(None)
183 if zone.upper() == 'UTC':
184 return utc
186 try:
187 zone = ascii(zone)
188 except UnicodeEncodeError:
189 # All valid timezones are ASCII
190 raise UnknownTimeZoneError(zone)
192 zone = _case_insensitive_zone_lookup(_unmunge_zone(zone))
193 if zone not in _tzinfo_cache:
194 if zone in all_timezones_set: # noqa
195 fp = open_resource(zone)
196 try:
197 _tzinfo_cache[zone] = build_tzinfo(zone, fp)
198 finally:
199 fp.close()
200 else:
201 raise UnknownTimeZoneError(zone)
203 return _tzinfo_cache[zone]
206def _unmunge_zone(zone):
207 """Undo the time zone name munging done by older versions of pytz."""
208 return zone.replace('_plus_', '+').replace('_minus_', '-')
211_all_timezones_lower_to_standard = None
214def _case_insensitive_zone_lookup(zone):
215 """case-insensitively matching timezone, else return zone unchanged"""
216 global _all_timezones_lower_to_standard
217 if _all_timezones_lower_to_standard is None:
218 _all_timezones_lower_to_standard = dict((tz.lower(), tz) for tz in all_timezones) # noqa
219 return _all_timezones_lower_to_standard.get(zone.lower()) or zone # noqa
222ZERO = datetime.timedelta(0)
223HOUR = datetime.timedelta(hours=1)
226class UTC(BaseTzInfo):
227 """UTC
229 Optimized UTC implementation. It unpickles using the single module global
230 instance defined beneath this class declaration.
231 """
232 zone = "UTC"
234 _utcoffset = ZERO
235 _dst = ZERO
236 _tzname = zone
238 def fromutc(self, dt):
239 if dt.tzinfo is None:
240 return self.localize(dt)
241 return super(utc.__class__, self).fromutc(dt)
243 def utcoffset(self, dt):
244 return ZERO
246 def tzname(self, dt):
247 return "UTC"
249 def dst(self, dt):
250 return ZERO
252 def __reduce__(self):
253 return _UTC, ()
255 def localize(self, dt, is_dst=False):
256 '''Convert naive time to local time'''
257 if dt.tzinfo is not None:
258 raise ValueError('Not naive datetime (tzinfo is already set)')
259 return dt.replace(tzinfo=self)
261 def normalize(self, dt, is_dst=False):
262 '''Correct the timezone information on the given datetime'''
263 if dt.tzinfo is self:
264 return dt
265 if dt.tzinfo is None:
266 raise ValueError('Naive time - no tzinfo set')
267 return dt.astimezone(self)
269 def __repr__(self):
270 return "<UTC>"
272 def __str__(self):
273 return "UTC"
276UTC = utc = UTC() # UTC is a singleton
279def _UTC():
280 """Factory function for utc unpickling.
282 Makes sure that unpickling a utc instance always returns the same
283 module global.
285 These examples belong in the UTC class above, but it is obscured; or in
286 the README.rst, but we are not depending on Python 2.4 so integrating
287 the README.rst examples with the unit tests is not trivial.
289 >>> import datetime, pickle
290 >>> dt = datetime.datetime(2005, 3, 1, 14, 13, 21, tzinfo=utc)
291 >>> naive = dt.replace(tzinfo=None)
292 >>> p = pickle.dumps(dt, 1)
293 >>> naive_p = pickle.dumps(naive, 1)
294 >>> len(p) - len(naive_p)
295 17
296 >>> new = pickle.loads(p)
297 >>> new == dt
298 True
299 >>> new is dt
300 False
301 >>> new.tzinfo is dt.tzinfo
302 True
303 >>> utc is UTC is timezone('UTC')
304 True
305 >>> utc is timezone('GMT')
306 False
307 """
308 return utc
311_UTC.__safe_for_unpickling__ = True
314def _p(*args):
315 """Factory function for unpickling pytz tzinfo instances.
317 Just a wrapper around tzinfo.unpickler to save a few bytes in each pickle
318 by shortening the path.
319 """
320 return unpickler(*args)
323_p.__safe_for_unpickling__ = True
326class _CountryTimezoneDict(LazyDict):
327 """Map ISO 3166 country code to a list of timezone names commonly used
328 in that country.
330 iso3166_code is the two letter code used to identify the country.
332 >>> def print_list(list_of_strings):
333 ... 'We use a helper so doctests work under Python 2.3 -> 3.x'
334 ... for s in list_of_strings:
335 ... print(s)
337 >>> print_list(country_timezones['nz'])
338 Pacific/Auckland
339 Pacific/Chatham
340 >>> print_list(country_timezones['ch'])
341 Europe/Zurich
342 >>> print_list(country_timezones['CH'])
343 Europe/Zurich
344 >>> print_list(country_timezones[unicode('ch')])
345 Europe/Zurich
346 >>> print_list(country_timezones['XXX'])
347 Traceback (most recent call last):
348 ...
349 KeyError: 'XXX'
351 Previously, this information was exposed as a function rather than a
352 dictionary. This is still supported::
354 >>> print_list(country_timezones('nz'))
355 Pacific/Auckland
356 Pacific/Chatham
357 """
358 def __call__(self, iso3166_code):
359 """Backwards compatibility."""
360 return self[iso3166_code]
362 def _fill(self):
363 data = {}
364 zone_tab = open_resource('zone.tab')
365 try:
366 for line in zone_tab:
367 line = line.decode('UTF-8')
368 if line.startswith('#'):
369 continue
370 code, coordinates, zone = line.split(None, 4)[:3]
371 if zone not in all_timezones_set: # noqa
372 continue
373 try:
374 data[code].append(zone)
375 except KeyError:
376 data[code] = [zone]
377 self.data = data
378 finally:
379 zone_tab.close()
382country_timezones = _CountryTimezoneDict()
385class _CountryNameDict(LazyDict):
386 '''Dictionary proving ISO3166 code -> English name.
388 >>> print(country_names['au'])
389 Australia
390 '''
391 def _fill(self):
392 data = {}
393 zone_tab = open_resource('iso3166.tab')
394 try:
395 for line in zone_tab.readlines():
396 line = line.decode('UTF-8')
397 if line.startswith('#'):
398 continue
399 code, name = line.split(None, 1)
400 data[code] = name.strip()
401 self.data = data
402 finally:
403 zone_tab.close()
406country_names = _CountryNameDict()
409# Time-zone info based solely on fixed offsets
411class _FixedOffset(datetime.tzinfo):
413 zone = None # to match the standard pytz API
415 def __init__(self, minutes):
416 if abs(minutes) >= 1440:
417 raise ValueError("absolute offset is too large", minutes)
418 self._minutes = minutes
419 self._offset = datetime.timedelta(minutes=minutes)
421 def utcoffset(self, dt):
422 return self._offset
424 def __reduce__(self):
425 return FixedOffset, (self._minutes, )
427 def dst(self, dt):
428 return ZERO
430 def tzname(self, dt):
431 return None
433 def __repr__(self):
434 return 'pytz.FixedOffset(%d)' % self._minutes
436 def localize(self, dt, is_dst=False):
437 '''Convert naive time to local time'''
438 if dt.tzinfo is not None:
439 raise ValueError('Not naive datetime (tzinfo is already set)')
440 return dt.replace(tzinfo=self)
442 def normalize(self, dt, is_dst=False):
443 '''Correct the timezone information on the given datetime'''
444 if dt.tzinfo is self:
445 return dt
446 if dt.tzinfo is None:
447 raise ValueError('Naive time - no tzinfo set')
448 return dt.astimezone(self)
451def FixedOffset(offset, _tzinfos={}):
452 """return a fixed-offset timezone based off a number of minutes.
454 >>> one = FixedOffset(-330)
455 >>> one
456 pytz.FixedOffset(-330)
457 >>> str(one.utcoffset(datetime.datetime.now()))
458 '-1 day, 18:30:00'
459 >>> str(one.dst(datetime.datetime.now()))
460 '0:00:00'
462 >>> two = FixedOffset(1380)
463 >>> two
464 pytz.FixedOffset(1380)
465 >>> str(two.utcoffset(datetime.datetime.now()))
466 '23:00:00'
467 >>> str(two.dst(datetime.datetime.now()))
468 '0:00:00'
470 The datetime.timedelta must be between the range of -1 and 1 day,
471 non-inclusive.
473 >>> FixedOffset(1440)
474 Traceback (most recent call last):
475 ...
476 ValueError: ('absolute offset is too large', 1440)
478 >>> FixedOffset(-1440)
479 Traceback (most recent call last):
480 ...
481 ValueError: ('absolute offset is too large', -1440)
483 An offset of 0 is special-cased to return UTC.
485 >>> FixedOffset(0) is UTC
486 True
488 There should always be only one instance of a FixedOffset per timedelta.
489 This should be true for multiple creation calls.
491 >>> FixedOffset(-330) is one
492 True
493 >>> FixedOffset(1380) is two
494 True
496 It should also be true for pickling.
498 >>> import pickle
499 >>> pickle.loads(pickle.dumps(one)) is one
500 True
501 >>> pickle.loads(pickle.dumps(two)) is two
502 True
503 """
504 if offset == 0:
505 return UTC
507 info = _tzinfos.get(offset)
508 if info is None:
509 # We haven't seen this one before. we need to save it.
511 # Use setdefault to avoid a race condition and make sure we have
512 # only one
513 info = _tzinfos.setdefault(offset, _FixedOffset(offset))
515 return info
518FixedOffset.__safe_for_unpickling__ = True
521def _test():
522 import doctest
523 sys.path.insert(0, os.pardir)
524 import pytz
525 return doctest.testmod(pytz)
527def _read_timezones_from_zone_tab() -> set[str]:
528 timezones = set()
529 zone_tab = pathlib.Path("/usr/share/zoneinfo/zone1970.tab")
530 for line in zone_tab.read_text(encoding="utf-8").splitlines():
531 if line.startswith("#"):
532 continue
533 timezones.add(line.split("\t")[2])
534 return timezones
537if __name__ == '__main__':
538 _test()
540_extra_common_timezones_set = {
541 'Africa/Accra',
542 'Africa/Addis_Ababa',
543 'Africa/Asmara',
544 'Africa/Bamako',
545 'Africa/Bangui',
546 'Africa/Banjul',
547 'Africa/Blantyre',
548 'Africa/Brazzaville',
549 'Africa/Bujumbura',
550 'Africa/Conakry',
551 'Africa/Dakar',
552 'Africa/Dar_es_Salaam',
553 'Africa/Djibouti',
554 'Africa/Douala',
555 'Africa/Freetown',
556 'Africa/Gaborone',
557 'Africa/Harare',
558 'Africa/Kampala',
559 'Africa/Kigali',
560 'Africa/Kinshasa',
561 'Africa/Libreville',
562 'Africa/Lome',
563 'Africa/Luanda',
564 'Africa/Lubumbashi',
565 'Africa/Lusaka',
566 'Africa/Malabo',
567 'Africa/Maseru',
568 'Africa/Mbabane',
569 'Africa/Mogadishu',
570 'Africa/Niamey',
571 'Africa/Nouakchott',
572 'Africa/Ouagadougou',
573 'Africa/Porto-Novo',
574 'America/Anguilla',
575 'America/Antigua',
576 'America/Aruba',
577 'America/Atikokan',
578 'America/Blanc-Sablon',
579 'America/Cayman',
580 'America/Creston',
581 'America/Curacao',
582 'America/Dominica',
583 'America/Grenada',
584 'America/Guadeloupe',
585 'America/Kralendijk',
586 'America/Lower_Princes',
587 'America/Marigot',
588 'America/Montserrat',
589 'America/Nassau',
590 'America/Port_of_Spain',
591 'America/St_Barthelemy',
592 'America/St_Kitts',
593 'America/St_Lucia',
594 'America/St_Thomas',
595 'America/St_Vincent',
596 'America/Tortola',
597 'Antarctica/DumontDUrville',
598 'Antarctica/McMurdo',
599 'Antarctica/Syowa',
600 'Antarctica/Vostok',
601 'Arctic/Longyearbyen',
602 'Asia/Aden',
603 'Asia/Bahrain',
604 'Asia/Kuala_Lumpur',
605 'Asia/Kuwait',
606 'Asia/Muscat',
607 'Asia/Phnom_Penh',
608 'Asia/Vientiane',
609 'Atlantic/Reykjavik',
610 'Atlantic/St_Helena',
611 'Canada/Atlantic',
612 'Canada/Central',
613 'Canada/Eastern',
614 'Canada/Mountain',
615 'Canada/Newfoundland',
616 'Canada/Pacific',
617 'Europe/Amsterdam',
618 'Europe/Bratislava',
619 'Europe/Busingen',
620 'Europe/Copenhagen',
621 'Europe/Guernsey',
622 'Europe/Isle_of_Man',
623 'Europe/Jersey',
624 'Europe/Ljubljana',
625 'Europe/Luxembourg',
626 'Europe/Mariehamn',
627 'Europe/Monaco',
628 'Europe/Oslo',
629 'Europe/Podgorica',
630 'Europe/San_Marino',
631 'Europe/Sarajevo',
632 'Europe/Skopje',
633 'Europe/Stockholm',
634 'Europe/Vaduz',
635 'Europe/Vatican',
636 'Europe/Zagreb',
637 'GMT',
638 'Indian/Antananarivo',
639 'Indian/Christmas',
640 'Indian/Cocos',
641 'Indian/Comoro',
642 'Indian/Kerguelen',
643 'Indian/Mahe',
644 'Indian/Mayotte',
645 'Indian/Reunion',
646 'Pacific/Chuuk',
647 'Pacific/Funafuti',
648 'Pacific/Majuro',
649 'Pacific/Midway',
650 'Pacific/Pohnpei',
651 'Pacific/Saipan',
652 'Pacific/Wake',
653 'Pacific/Wallis',
654 'US/Alaska',
655 'US/Arizona',
656 'US/Central',
657 'US/Eastern',
658 'US/Hawaii',
659 'US/Mountain',
660 'US/Pacific',
661 'UTC'}
663all_timezones_set = zoneinfo.available_timezones() - {"Factory", "localtime"}
664common_timezones_set = (_read_timezones_from_zone_tab() | _extra_common_timezones_set) & all_timezones_set
666all_timezones = sorted(all_timezones_set)
667common_timezones = sorted(common_timezones_set)