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