jpayne@69: '''Base classes and helpers for building zone specific tzinfo classes''' jpayne@69: jpayne@69: from datetime import datetime, timedelta, tzinfo jpayne@69: from bisect import bisect_right jpayne@69: try: jpayne@69: set jpayne@69: except NameError: jpayne@69: from sets import Set as set jpayne@69: jpayne@69: import pytz jpayne@69: from pytz.exceptions import AmbiguousTimeError, NonExistentTimeError jpayne@69: jpayne@69: __all__ = [] jpayne@69: jpayne@69: _timedelta_cache = {} jpayne@69: jpayne@69: jpayne@69: def memorized_timedelta(seconds): jpayne@69: '''Create only one instance of each distinct timedelta''' jpayne@69: try: jpayne@69: return _timedelta_cache[seconds] jpayne@69: except KeyError: jpayne@69: delta = timedelta(seconds=seconds) jpayne@69: _timedelta_cache[seconds] = delta jpayne@69: return delta jpayne@69: jpayne@69: jpayne@69: _epoch = datetime(1970, 1, 1, 0, 0) # datetime.utcfromtimestamp(0) jpayne@69: _datetime_cache = {0: _epoch} jpayne@69: jpayne@69: jpayne@69: def memorized_datetime(seconds): jpayne@69: '''Create only one instance of each distinct datetime''' jpayne@69: try: jpayne@69: return _datetime_cache[seconds] jpayne@69: except KeyError: jpayne@69: # NB. We can't just do datetime.fromtimestamp(seconds, tz=timezone.utc).replace(tzinfo=None) jpayne@69: # as this fails with negative values under Windows (Bug #90096) jpayne@69: dt = _epoch + timedelta(seconds=seconds) jpayne@69: _datetime_cache[seconds] = dt jpayne@69: return dt jpayne@69: jpayne@69: jpayne@69: _ttinfo_cache = {} jpayne@69: jpayne@69: jpayne@69: def memorized_ttinfo(*args): jpayne@69: '''Create only one instance of each distinct tuple''' jpayne@69: try: jpayne@69: return _ttinfo_cache[args] jpayne@69: except KeyError: jpayne@69: ttinfo = ( jpayne@69: memorized_timedelta(args[0]), jpayne@69: memorized_timedelta(args[1]), jpayne@69: args[2] jpayne@69: ) jpayne@69: _ttinfo_cache[args] = ttinfo jpayne@69: return ttinfo jpayne@69: jpayne@69: jpayne@69: _notime = memorized_timedelta(0) jpayne@69: jpayne@69: jpayne@69: def _to_seconds(td): jpayne@69: '''Convert a timedelta to seconds''' jpayne@69: return td.seconds + td.days * 24 * 60 * 60 jpayne@69: jpayne@69: jpayne@69: class BaseTzInfo(tzinfo): jpayne@69: # Overridden in subclass jpayne@69: _utcoffset = None jpayne@69: _tzname = None jpayne@69: zone = None jpayne@69: jpayne@69: def __str__(self): jpayne@69: return self.zone jpayne@69: jpayne@69: jpayne@69: class StaticTzInfo(BaseTzInfo): jpayne@69: '''A timezone that has a constant offset from UTC jpayne@69: jpayne@69: These timezones are rare, as most locations have changed their jpayne@69: offset at some point in their history jpayne@69: ''' jpayne@69: def fromutc(self, dt): jpayne@69: '''See datetime.tzinfo.fromutc''' jpayne@69: if dt.tzinfo is not None and dt.tzinfo is not self: jpayne@69: raise ValueError('fromutc: dt.tzinfo is not self') jpayne@69: return (dt + self._utcoffset).replace(tzinfo=self) jpayne@69: jpayne@69: def utcoffset(self, dt, is_dst=None): jpayne@69: '''See datetime.tzinfo.utcoffset jpayne@69: jpayne@69: is_dst is ignored for StaticTzInfo, and exists only to jpayne@69: retain compatibility with DstTzInfo. jpayne@69: ''' jpayne@69: return self._utcoffset jpayne@69: jpayne@69: def dst(self, dt, is_dst=None): jpayne@69: '''See datetime.tzinfo.dst jpayne@69: jpayne@69: is_dst is ignored for StaticTzInfo, and exists only to jpayne@69: retain compatibility with DstTzInfo. jpayne@69: ''' jpayne@69: return _notime jpayne@69: jpayne@69: def tzname(self, dt, is_dst=None): jpayne@69: '''See datetime.tzinfo.tzname jpayne@69: jpayne@69: is_dst is ignored for StaticTzInfo, and exists only to jpayne@69: retain compatibility with DstTzInfo. jpayne@69: ''' jpayne@69: return self._tzname jpayne@69: jpayne@69: def localize(self, dt, is_dst=False): jpayne@69: '''Convert naive time to local time''' jpayne@69: if dt.tzinfo is not None: jpayne@69: raise ValueError('Not naive datetime (tzinfo is already set)') jpayne@69: return dt.replace(tzinfo=self) jpayne@69: jpayne@69: def normalize(self, dt, is_dst=False): jpayne@69: '''Correct the timezone information on the given datetime. jpayne@69: jpayne@69: This is normally a no-op, as StaticTzInfo timezones never have jpayne@69: ambiguous cases to correct: jpayne@69: jpayne@69: >>> from pytz import timezone jpayne@69: >>> gmt = timezone('GMT') jpayne@69: >>> isinstance(gmt, StaticTzInfo) jpayne@69: True jpayne@69: >>> dt = datetime(2011, 5, 8, 1, 2, 3, tzinfo=gmt) jpayne@69: >>> gmt.normalize(dt) is dt jpayne@69: True jpayne@69: jpayne@69: The supported method of converting between timezones is to use jpayne@69: datetime.astimezone(). Currently normalize() also works: jpayne@69: jpayne@69: >>> la = timezone('America/Los_Angeles') jpayne@69: >>> dt = la.localize(datetime(2011, 5, 7, 1, 2, 3)) jpayne@69: >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)' jpayne@69: >>> gmt.normalize(dt).strftime(fmt) jpayne@69: '2011-05-07 08:02:03 GMT (+0000)' jpayne@69: ''' jpayne@69: if dt.tzinfo is self: jpayne@69: return dt jpayne@69: if dt.tzinfo is None: jpayne@69: raise ValueError('Naive time - no tzinfo set') jpayne@69: return dt.astimezone(self) jpayne@69: jpayne@69: def __repr__(self): jpayne@69: return '' % (self.zone,) jpayne@69: jpayne@69: def __reduce__(self): jpayne@69: # Special pickle to zone remains a singleton and to cope with jpayne@69: # database changes. jpayne@69: return pytz._p, (self.zone,) jpayne@69: jpayne@69: jpayne@69: class DstTzInfo(BaseTzInfo): jpayne@69: '''A timezone that has a variable offset from UTC jpayne@69: jpayne@69: The offset might change if daylight saving time comes into effect, jpayne@69: or at a point in history when the region decides to change their jpayne@69: timezone definition. jpayne@69: ''' jpayne@69: # Overridden in subclass jpayne@69: jpayne@69: # Sorted list of DST transition times, UTC jpayne@69: _utc_transition_times = None jpayne@69: jpayne@69: # [(utcoffset, dstoffset, tzname)] corresponding to jpayne@69: # _utc_transition_times entries jpayne@69: _transition_info = None jpayne@69: jpayne@69: zone = None jpayne@69: jpayne@69: # Set in __init__ jpayne@69: jpayne@69: _tzinfos = None jpayne@69: _dst = None # DST offset jpayne@69: jpayne@69: def __init__(self, _inf=None, _tzinfos=None): jpayne@69: if _inf: jpayne@69: self._tzinfos = _tzinfos jpayne@69: self._utcoffset, self._dst, self._tzname = _inf jpayne@69: else: jpayne@69: _tzinfos = {} jpayne@69: self._tzinfos = _tzinfos jpayne@69: self._utcoffset, self._dst, self._tzname = ( jpayne@69: self._transition_info[0]) jpayne@69: _tzinfos[self._transition_info[0]] = self jpayne@69: for inf in self._transition_info[1:]: jpayne@69: if inf not in _tzinfos: jpayne@69: _tzinfos[inf] = self.__class__(inf, _tzinfos) jpayne@69: jpayne@69: def fromutc(self, dt): jpayne@69: '''See datetime.tzinfo.fromutc''' jpayne@69: if (dt.tzinfo is not None and jpayne@69: getattr(dt.tzinfo, '_tzinfos', None) is not self._tzinfos): jpayne@69: raise ValueError('fromutc: dt.tzinfo is not self') jpayne@69: dt = dt.replace(tzinfo=None) jpayne@69: idx = max(0, bisect_right(self._utc_transition_times, dt) - 1) jpayne@69: inf = self._transition_info[idx] jpayne@69: return (dt + inf[0]).replace(tzinfo=self._tzinfos[inf]) jpayne@69: jpayne@69: def normalize(self, dt): jpayne@69: '''Correct the timezone information on the given datetime jpayne@69: jpayne@69: If date arithmetic crosses DST boundaries, the tzinfo jpayne@69: is not magically adjusted. This method normalizes the jpayne@69: tzinfo to the correct one. jpayne@69: jpayne@69: To test, first we need to do some setup jpayne@69: jpayne@69: >>> from pytz import timezone jpayne@69: >>> utc = timezone('UTC') jpayne@69: >>> eastern = timezone('US/Eastern') jpayne@69: >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)' jpayne@69: jpayne@69: We next create a datetime right on an end-of-DST transition point, jpayne@69: the instant when the wallclocks are wound back one hour. jpayne@69: jpayne@69: >>> utc_dt = datetime(2002, 10, 27, 6, 0, 0, tzinfo=utc) jpayne@69: >>> loc_dt = utc_dt.astimezone(eastern) jpayne@69: >>> loc_dt.strftime(fmt) jpayne@69: '2002-10-27 01:00:00 EST (-0500)' jpayne@69: jpayne@69: Now, if we subtract a few minutes from it, note that the timezone jpayne@69: information has not changed. jpayne@69: jpayne@69: >>> before = loc_dt - timedelta(minutes=10) jpayne@69: >>> before.strftime(fmt) jpayne@69: '2002-10-27 00:50:00 EST (-0500)' jpayne@69: jpayne@69: But we can fix that by calling the normalize method jpayne@69: jpayne@69: >>> before = eastern.normalize(before) jpayne@69: >>> before.strftime(fmt) jpayne@69: '2002-10-27 01:50:00 EDT (-0400)' jpayne@69: jpayne@69: The supported method of converting between timezones is to use jpayne@69: datetime.astimezone(). Currently, normalize() also works: jpayne@69: jpayne@69: >>> th = timezone('Asia/Bangkok') jpayne@69: >>> am = timezone('Europe/Amsterdam') jpayne@69: >>> dt = th.localize(datetime(2011, 5, 7, 1, 2, 3)) jpayne@69: >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)' jpayne@69: >>> am.normalize(dt).strftime(fmt) jpayne@69: '2011-05-06 20:02:03 CEST (+0200)' jpayne@69: ''' jpayne@69: if dt.tzinfo is None: jpayne@69: raise ValueError('Naive time - no tzinfo set') jpayne@69: jpayne@69: # Convert dt in localtime to UTC jpayne@69: offset = dt.tzinfo._utcoffset jpayne@69: dt = dt.replace(tzinfo=None) jpayne@69: dt = dt - offset jpayne@69: # convert it back, and return it jpayne@69: return self.fromutc(dt) jpayne@69: jpayne@69: def localize(self, dt, is_dst=False): jpayne@69: '''Convert naive time to local time. jpayne@69: jpayne@69: This method should be used to construct localtimes, rather jpayne@69: than passing a tzinfo argument to a datetime constructor. jpayne@69: jpayne@69: is_dst is used to determine the correct timezone in the ambigous jpayne@69: period at the end of daylight saving time. jpayne@69: jpayne@69: >>> from pytz import timezone jpayne@69: >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)' jpayne@69: >>> amdam = timezone('Europe/Amsterdam') jpayne@69: >>> dt = datetime(2004, 10, 31, 2, 0, 0) jpayne@69: >>> loc_dt1 = amdam.localize(dt, is_dst=True) jpayne@69: >>> loc_dt2 = amdam.localize(dt, is_dst=False) jpayne@69: >>> loc_dt1.strftime(fmt) jpayne@69: '2004-10-31 02:00:00 CEST (+0200)' jpayne@69: >>> loc_dt2.strftime(fmt) jpayne@69: '2004-10-31 02:00:00 CET (+0100)' jpayne@69: >>> str(loc_dt2 - loc_dt1) jpayne@69: '1:00:00' jpayne@69: jpayne@69: Use is_dst=None to raise an AmbiguousTimeError for ambiguous jpayne@69: times at the end of daylight saving time jpayne@69: jpayne@69: >>> try: jpayne@69: ... loc_dt1 = amdam.localize(dt, is_dst=None) jpayne@69: ... except AmbiguousTimeError: jpayne@69: ... print('Ambiguous') jpayne@69: Ambiguous jpayne@69: jpayne@69: is_dst defaults to False jpayne@69: jpayne@69: >>> amdam.localize(dt) == amdam.localize(dt, False) jpayne@69: True jpayne@69: jpayne@69: is_dst is also used to determine the correct timezone in the jpayne@69: wallclock times jumped over at the start of daylight saving time. jpayne@69: jpayne@69: >>> pacific = timezone('US/Pacific') jpayne@69: >>> dt = datetime(2008, 3, 9, 2, 0, 0) jpayne@69: >>> ploc_dt1 = pacific.localize(dt, is_dst=True) jpayne@69: >>> ploc_dt2 = pacific.localize(dt, is_dst=False) jpayne@69: >>> ploc_dt1.strftime(fmt) jpayne@69: '2008-03-09 02:00:00 PDT (-0700)' jpayne@69: >>> ploc_dt2.strftime(fmt) jpayne@69: '2008-03-09 02:00:00 PST (-0800)' jpayne@69: >>> str(ploc_dt2 - ploc_dt1) jpayne@69: '1:00:00' jpayne@69: jpayne@69: Use is_dst=None to raise a NonExistentTimeError for these skipped jpayne@69: times. jpayne@69: jpayne@69: >>> try: jpayne@69: ... loc_dt1 = pacific.localize(dt, is_dst=None) jpayne@69: ... except NonExistentTimeError: jpayne@69: ... print('Non-existent') jpayne@69: Non-existent jpayne@69: ''' jpayne@69: if dt.tzinfo is not None: jpayne@69: raise ValueError('Not naive datetime (tzinfo is already set)') jpayne@69: jpayne@69: # Find the two best possibilities. jpayne@69: possible_loc_dt = set() jpayne@69: for delta in [timedelta(days=-1), timedelta(days=1)]: jpayne@69: loc_dt = dt + delta jpayne@69: idx = max(0, bisect_right( jpayne@69: self._utc_transition_times, loc_dt) - 1) jpayne@69: inf = self._transition_info[idx] jpayne@69: tzinfo = self._tzinfos[inf] jpayne@69: loc_dt = tzinfo.normalize(dt.replace(tzinfo=tzinfo)) jpayne@69: if loc_dt.replace(tzinfo=None) == dt: jpayne@69: possible_loc_dt.add(loc_dt) jpayne@69: jpayne@69: if len(possible_loc_dt) == 1: jpayne@69: return possible_loc_dt.pop() jpayne@69: jpayne@69: # If there are no possibly correct timezones, we are attempting jpayne@69: # to convert a time that never happened - the time period jumped jpayne@69: # during the start-of-DST transition period. jpayne@69: if len(possible_loc_dt) == 0: jpayne@69: # If we refuse to guess, raise an exception. jpayne@69: if is_dst is None: jpayne@69: raise NonExistentTimeError(dt) jpayne@69: jpayne@69: # If we are forcing the pre-DST side of the DST transition, we jpayne@69: # obtain the correct timezone by winding the clock forward a few jpayne@69: # hours. jpayne@69: elif is_dst: jpayne@69: return self.localize( jpayne@69: dt + timedelta(hours=6), is_dst=True) - timedelta(hours=6) jpayne@69: jpayne@69: # If we are forcing the post-DST side of the DST transition, we jpayne@69: # obtain the correct timezone by winding the clock back. jpayne@69: else: jpayne@69: return self.localize( jpayne@69: dt - timedelta(hours=6), jpayne@69: is_dst=False) + timedelta(hours=6) jpayne@69: jpayne@69: # If we get this far, we have multiple possible timezones - this jpayne@69: # is an ambiguous case occurring during the end-of-DST transition. jpayne@69: jpayne@69: # If told to be strict, raise an exception since we have an jpayne@69: # ambiguous case jpayne@69: if is_dst is None: jpayne@69: raise AmbiguousTimeError(dt) jpayne@69: jpayne@69: # Filter out the possiblilities that don't match the requested jpayne@69: # is_dst jpayne@69: filtered_possible_loc_dt = [ jpayne@69: p for p in possible_loc_dt if bool(p.tzinfo._dst) == is_dst jpayne@69: ] jpayne@69: jpayne@69: # Hopefully we only have one possibility left. Return it. jpayne@69: if len(filtered_possible_loc_dt) == 1: jpayne@69: return filtered_possible_loc_dt[0] jpayne@69: jpayne@69: if len(filtered_possible_loc_dt) == 0: jpayne@69: filtered_possible_loc_dt = list(possible_loc_dt) jpayne@69: jpayne@69: # If we get this far, we have in a wierd timezone transition jpayne@69: # where the clocks have been wound back but is_dst is the same jpayne@69: # in both (eg. Europe/Warsaw 1915 when they switched to CET). jpayne@69: # At this point, we just have to guess unless we allow more jpayne@69: # hints to be passed in (such as the UTC offset or abbreviation), jpayne@69: # but that is just getting silly. jpayne@69: # jpayne@69: # Choose the earliest (by UTC) applicable timezone if is_dst=True jpayne@69: # Choose the latest (by UTC) applicable timezone if is_dst=False jpayne@69: # i.e., behave like end-of-DST transition jpayne@69: dates = {} # utc -> local jpayne@69: for local_dt in filtered_possible_loc_dt: jpayne@69: utc_time = ( jpayne@69: local_dt.replace(tzinfo=None) - local_dt.tzinfo._utcoffset) jpayne@69: assert utc_time not in dates jpayne@69: dates[utc_time] = local_dt jpayne@69: return dates[[min, max][not is_dst](dates)] jpayne@69: jpayne@69: def utcoffset(self, dt, is_dst=None): jpayne@69: '''See datetime.tzinfo.utcoffset jpayne@69: jpayne@69: The is_dst parameter may be used to remove ambiguity during DST jpayne@69: transitions. jpayne@69: jpayne@69: >>> from pytz import timezone jpayne@69: >>> tz = timezone('America/St_Johns') jpayne@69: >>> ambiguous = datetime(2009, 10, 31, 23, 30) jpayne@69: jpayne@69: >>> str(tz.utcoffset(ambiguous, is_dst=False)) jpayne@69: '-1 day, 20:30:00' jpayne@69: jpayne@69: >>> str(tz.utcoffset(ambiguous, is_dst=True)) jpayne@69: '-1 day, 21:30:00' jpayne@69: jpayne@69: >>> try: jpayne@69: ... tz.utcoffset(ambiguous) jpayne@69: ... except AmbiguousTimeError: jpayne@69: ... print('Ambiguous') jpayne@69: Ambiguous jpayne@69: jpayne@69: ''' jpayne@69: if dt is None: jpayne@69: return None jpayne@69: elif dt.tzinfo is not self: jpayne@69: dt = self.localize(dt, is_dst) jpayne@69: return dt.tzinfo._utcoffset jpayne@69: else: jpayne@69: return self._utcoffset jpayne@69: jpayne@69: def dst(self, dt, is_dst=None): jpayne@69: '''See datetime.tzinfo.dst jpayne@69: jpayne@69: The is_dst parameter may be used to remove ambiguity during DST jpayne@69: transitions. jpayne@69: jpayne@69: >>> from pytz import timezone jpayne@69: >>> tz = timezone('America/St_Johns') jpayne@69: jpayne@69: >>> normal = datetime(2009, 9, 1) jpayne@69: jpayne@69: >>> str(tz.dst(normal)) jpayne@69: '1:00:00' jpayne@69: >>> str(tz.dst(normal, is_dst=False)) jpayne@69: '1:00:00' jpayne@69: >>> str(tz.dst(normal, is_dst=True)) jpayne@69: '1:00:00' jpayne@69: jpayne@69: >>> ambiguous = datetime(2009, 10, 31, 23, 30) jpayne@69: jpayne@69: >>> str(tz.dst(ambiguous, is_dst=False)) jpayne@69: '0:00:00' jpayne@69: >>> str(tz.dst(ambiguous, is_dst=True)) jpayne@69: '1:00:00' jpayne@69: >>> try: jpayne@69: ... tz.dst(ambiguous) jpayne@69: ... except AmbiguousTimeError: jpayne@69: ... print('Ambiguous') jpayne@69: Ambiguous jpayne@69: jpayne@69: ''' jpayne@69: if dt is None: jpayne@69: return None jpayne@69: elif dt.tzinfo is not self: jpayne@69: dt = self.localize(dt, is_dst) jpayne@69: return dt.tzinfo._dst jpayne@69: else: jpayne@69: return self._dst jpayne@69: jpayne@69: def tzname(self, dt, is_dst=None): jpayne@69: '''See datetime.tzinfo.tzname jpayne@69: jpayne@69: The is_dst parameter may be used to remove ambiguity during DST jpayne@69: transitions. jpayne@69: jpayne@69: >>> from pytz import timezone jpayne@69: >>> tz = timezone('America/St_Johns') jpayne@69: jpayne@69: >>> normal = datetime(2009, 9, 1) jpayne@69: jpayne@69: >>> tz.tzname(normal) jpayne@69: 'NDT' jpayne@69: >>> tz.tzname(normal, is_dst=False) jpayne@69: 'NDT' jpayne@69: >>> tz.tzname(normal, is_dst=True) jpayne@69: 'NDT' jpayne@69: jpayne@69: >>> ambiguous = datetime(2009, 10, 31, 23, 30) jpayne@69: jpayne@69: >>> tz.tzname(ambiguous, is_dst=False) jpayne@69: 'NST' jpayne@69: >>> tz.tzname(ambiguous, is_dst=True) jpayne@69: 'NDT' jpayne@69: >>> try: jpayne@69: ... tz.tzname(ambiguous) jpayne@69: ... except AmbiguousTimeError: jpayne@69: ... print('Ambiguous') jpayne@69: Ambiguous jpayne@69: ''' jpayne@69: if dt is None: jpayne@69: return self.zone jpayne@69: elif dt.tzinfo is not self: jpayne@69: dt = self.localize(dt, is_dst) jpayne@69: return dt.tzinfo._tzname jpayne@69: else: jpayne@69: return self._tzname jpayne@69: jpayne@69: def __repr__(self): jpayne@69: if self._dst: jpayne@69: dst = 'DST' jpayne@69: else: jpayne@69: dst = 'STD' jpayne@69: if self._utcoffset > _notime: jpayne@69: return '' % ( jpayne@69: self.zone, self._tzname, self._utcoffset, dst jpayne@69: ) jpayne@69: else: jpayne@69: return '' % ( jpayne@69: self.zone, self._tzname, self._utcoffset, dst jpayne@69: ) jpayne@69: jpayne@69: def __reduce__(self): jpayne@69: # Special pickle to zone remains a singleton and to cope with jpayne@69: # database changes. jpayne@69: return pytz._p, ( jpayne@69: self.zone, jpayne@69: _to_seconds(self._utcoffset), jpayne@69: _to_seconds(self._dst), jpayne@69: self._tzname jpayne@69: ) jpayne@69: jpayne@69: jpayne@69: def unpickler(zone, utcoffset=None, dstoffset=None, tzname=None): jpayne@69: """Factory function for unpickling pytz tzinfo instances. jpayne@69: jpayne@69: This is shared for both StaticTzInfo and DstTzInfo instances, because jpayne@69: database changes could cause a zones implementation to switch between jpayne@69: these two base classes and we can't break pickles on a pytz version jpayne@69: upgrade. jpayne@69: """ jpayne@69: # Raises a KeyError if zone no longer exists, which should never happen jpayne@69: # and would be a bug. jpayne@69: tz = pytz.timezone(zone) jpayne@69: jpayne@69: # A StaticTzInfo - just return it jpayne@69: if utcoffset is None: jpayne@69: return tz jpayne@69: jpayne@69: # This pickle was created from a DstTzInfo. We need to jpayne@69: # determine which of the list of tzinfo instances for this zone jpayne@69: # to use in order to restore the state of any datetime instances using jpayne@69: # it correctly. jpayne@69: utcoffset = memorized_timedelta(utcoffset) jpayne@69: dstoffset = memorized_timedelta(dstoffset) jpayne@69: try: jpayne@69: return tz._tzinfos[(utcoffset, dstoffset, tzname)] jpayne@69: except KeyError: jpayne@69: # The particular state requested in this timezone no longer exists. jpayne@69: # This indicates a corrupt pickle, or the timezone database has been jpayne@69: # corrected violently enough to make this particular jpayne@69: # (utcoffset,dstoffset) no longer exist in the zone, or the jpayne@69: # abbreviation has been changed. jpayne@69: pass jpayne@69: jpayne@69: # See if we can find an entry differing only by tzname. Abbreviations jpayne@69: # get changed from the initial guess by the database maintainers to jpayne@69: # match reality when this information is discovered. jpayne@69: for localized_tz in tz._tzinfos.values(): jpayne@69: if (localized_tz._utcoffset == utcoffset and jpayne@69: localized_tz._dst == dstoffset): jpayne@69: return localized_tz jpayne@69: jpayne@69: # This (utcoffset, dstoffset) information has been removed from the jpayne@69: # zone. Add it back. This might occur when the database maintainers have jpayne@69: # corrected incorrect information. datetime instances using this jpayne@69: # incorrect information will continue to do so, exactly as they were jpayne@69: # before being pickled. This is purely an overly paranoid safety net - I jpayne@69: # doubt this will ever been needed in real life. jpayne@69: inf = (utcoffset, dstoffset, tzname) jpayne@69: tz._tzinfos[inf] = tz.__class__(inf, tz._tzinfos) jpayne@69: return tz._tzinfos[inf]