Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
tzinfo.py581 linesDownload Raw Back to pytz
1'''Base classes and helpers for building zone specific tzinfo classes'''2 3from datetime import datetime, timedelta, tzinfo4from bisect import bisect_right5try:6    set7except NameError:8    from sets import Set as set9 10import pytz11from pytz.exceptions import AmbiguousTimeError, NonExistentTimeError12 13__all__ = []14 15_timedelta_cache = {}16 17 18def memorized_timedelta(seconds):19    '''Create only one instance of each distinct timedelta'''20    try:21        return _timedelta_cache[seconds]22    except KeyError:23        delta = timedelta(seconds=seconds)24        _timedelta_cache[seconds] = delta25        return delta26 27 28_epoch = datetime(1970, 1, 1, 0, 0) # datetime.utcfromtimestamp(0)29_datetime_cache = {0: _epoch}30 31 32def memorized_datetime(seconds):33    '''Create only one instance of each distinct datetime'''34    try:35        return _datetime_cache[seconds]36    except KeyError:37        # NB. We can't just do datetime.fromtimestamp(seconds, tz=timezone.utc).replace(tzinfo=None)38        # as this fails with negative values under Windows (Bug #90096)39        dt = _epoch + timedelta(seconds=seconds)40        _datetime_cache[seconds] = dt41        return dt42 43 44_ttinfo_cache = {}45 46 47def memorized_ttinfo(*args):48    '''Create only one instance of each distinct tuple'''49    try:50        return _ttinfo_cache[args]51    except KeyError:52        ttinfo = (53            memorized_timedelta(args[0]),54            memorized_timedelta(args[1]),55            args[2]56        )57        _ttinfo_cache[args] = ttinfo58        return ttinfo59 60 61_notime = memorized_timedelta(0)62 63 64def _to_seconds(td):65    '''Convert a timedelta to seconds'''66    return td.seconds + td.days * 24 * 60 * 6067 68 69class BaseTzInfo(tzinfo):70    # Overridden in subclass71    _utcoffset = None72    _tzname = None73    zone = None74 75    def __str__(self):76        return self.zone77 78 79class StaticTzInfo(BaseTzInfo):80    '''A timezone that has a constant offset from UTC81 82    These timezones are rare, as most locations have changed their83    offset at some point in their history84    '''85    def fromutc(self, dt):86        '''See datetime.tzinfo.fromutc'''87        if dt.tzinfo is not None and dt.tzinfo is not self:88            raise ValueError('fromutc: dt.tzinfo is not self')89        return (dt + self._utcoffset).replace(tzinfo=self)90 91    def utcoffset(self, dt, is_dst=None):92        '''See datetime.tzinfo.utcoffset93 94        is_dst is ignored for StaticTzInfo, and exists only to95        retain compatibility with DstTzInfo.96        '''97        return self._utcoffset98 99    def dst(self, dt, is_dst=None):100        '''See datetime.tzinfo.dst101 102        is_dst is ignored for StaticTzInfo, and exists only to103        retain compatibility with DstTzInfo.104        '''105        return _notime106 107    def tzname(self, dt, is_dst=None):108        '''See datetime.tzinfo.tzname109 110        is_dst is ignored for StaticTzInfo, and exists only to111        retain compatibility with DstTzInfo.112        '''113        return self._tzname114 115    def localize(self, dt, is_dst=False):116        '''Convert naive time to local time'''117        if dt.tzinfo is not None:118            raise ValueError('Not naive datetime (tzinfo is already set)')119        return dt.replace(tzinfo=self)120 121    def normalize(self, dt, is_dst=False):122        '''Correct the timezone information on the given datetime.123 124        This is normally a no-op, as StaticTzInfo timezones never have125        ambiguous cases to correct:126 127        >>> from pytz import timezone128        >>> gmt = timezone('GMT')129        >>> isinstance(gmt, StaticTzInfo)130        True131        >>> dt = datetime(2011, 5, 8, 1, 2, 3, tzinfo=gmt)132        >>> gmt.normalize(dt) is dt133        True134 135        The supported method of converting between timezones is to use136        datetime.astimezone(). Currently normalize() also works:137 138        >>> la = timezone('America/Los_Angeles')139        >>> dt = la.localize(datetime(2011, 5, 7, 1, 2, 3))140        >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)'141        >>> gmt.normalize(dt).strftime(fmt)142        '2011-05-07 08:02:03 GMT (+0000)'143        '''144        if dt.tzinfo is self:145            return dt146        if dt.tzinfo is None:147            raise ValueError('Naive time - no tzinfo set')148        return dt.astimezone(self)149 150    def __repr__(self):151        return '<StaticTzInfo %r>' % (self.zone,)152 153    def __reduce__(self):154        # Special pickle to zone remains a singleton and to cope with155        # database changes.156        return pytz._p, (self.zone,)157 158 159class DstTzInfo(BaseTzInfo):160    '''A timezone that has a variable offset from UTC161 162    The offset might change if daylight saving time comes into effect,163    or at a point in history when the region decides to change their164    timezone definition.165    '''166    # Overridden in subclass167 168    # Sorted list of DST transition times, UTC169    _utc_transition_times = None170 171    # [(utcoffset, dstoffset, tzname)] corresponding to172    # _utc_transition_times entries173    _transition_info = None174 175    zone = None176 177    # Set in __init__178 179    _tzinfos = None180    _dst = None  # DST offset181 182    def __init__(self, _inf=None, _tzinfos=None):183        if _inf:184            self._tzinfos = _tzinfos185            self._utcoffset, self._dst, self._tzname = _inf186        else:187            _tzinfos = {}188            self._tzinfos = _tzinfos189            self._utcoffset, self._dst, self._tzname = (190                self._transition_info[0])191            _tzinfos[self._transition_info[0]] = self192            for inf in self._transition_info[1:]:193                if inf not in _tzinfos:194                    _tzinfos[inf] = self.__class__(inf, _tzinfos)195 196    def fromutc(self, dt):197        '''See datetime.tzinfo.fromutc'''198        if (dt.tzinfo is not None and199                getattr(dt.tzinfo, '_tzinfos', None) is not self._tzinfos):200            raise ValueError('fromutc: dt.tzinfo is not self')201        dt = dt.replace(tzinfo=None)202        idx = max(0, bisect_right(self._utc_transition_times, dt) - 1)203        inf = self._transition_info[idx]204        return (dt + inf[0]).replace(tzinfo=self._tzinfos[inf])205 206    def normalize(self, dt):207        '''Correct the timezone information on the given datetime208 209        If date arithmetic crosses DST boundaries, the tzinfo210        is not magically adjusted. This method normalizes the211        tzinfo to the correct one.212 213        To test, first we need to do some setup214 215        >>> from pytz import timezone216        >>> utc = timezone('UTC')217        >>> eastern = timezone('US/Eastern')218        >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)'219 220        We next create a datetime right on an end-of-DST transition point,221        the instant when the wallclocks are wound back one hour.222 223        >>> utc_dt = datetime(2002, 10, 27, 6, 0, 0, tzinfo=utc)224        >>> loc_dt = utc_dt.astimezone(eastern)225        >>> loc_dt.strftime(fmt)226        '2002-10-27 01:00:00 EST (-0500)'227 228        Now, if we subtract a few minutes from it, note that the timezone229        information has not changed.230 231        >>> before = loc_dt - timedelta(minutes=10)232        >>> before.strftime(fmt)233        '2002-10-27 00:50:00 EST (-0500)'234 235        But we can fix that by calling the normalize method236 237        >>> before = eastern.normalize(before)238        >>> before.strftime(fmt)239        '2002-10-27 01:50:00 EDT (-0400)'240 241        The supported method of converting between timezones is to use242        datetime.astimezone(). Currently, normalize() also works:243 244        >>> th = timezone('Asia/Bangkok')245        >>> am = timezone('Europe/Amsterdam')246        >>> dt = th.localize(datetime(2011, 5, 7, 1, 2, 3))247        >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)'248        >>> am.normalize(dt).strftime(fmt)249        '2011-05-06 20:02:03 CEST (+0200)'250        '''251        if dt.tzinfo is None:252            raise ValueError('Naive time - no tzinfo set')253 254        # Convert dt in localtime to UTC255        offset = dt.tzinfo._utcoffset256        dt = dt.replace(tzinfo=None)257        dt = dt - offset258        # convert it back, and return it259        return self.fromutc(dt)260 261    def localize(self, dt, is_dst=False):262        '''Convert naive time to local time.263 264        This method should be used to construct localtimes, rather265        than passing a tzinfo argument to a datetime constructor.266 267        is_dst is used to determine the correct timezone in the ambigous268        period at the end of daylight saving time.269 270        >>> from pytz import timezone271        >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)'272        >>> amdam = timezone('Europe/Amsterdam')273        >>> dt  = datetime(2004, 10, 31, 2, 0, 0)274        >>> loc_dt1 = amdam.localize(dt, is_dst=True)275        >>> loc_dt2 = amdam.localize(dt, is_dst=False)276        >>> loc_dt1.strftime(fmt)277        '2004-10-31 02:00:00 CEST (+0200)'278        >>> loc_dt2.strftime(fmt)279        '2004-10-31 02:00:00 CET (+0100)'280        >>> str(loc_dt2 - loc_dt1)281        '1:00:00'282 283        Use is_dst=None to raise an AmbiguousTimeError for ambiguous284        times at the end of daylight saving time285 286        >>> try:287        ...     loc_dt1 = amdam.localize(dt, is_dst=None)288        ... except AmbiguousTimeError:289        ...     print('Ambiguous')290        Ambiguous291 292        is_dst defaults to False293 294        >>> amdam.localize(dt) == amdam.localize(dt, False)295        True296 297        is_dst is also used to determine the correct timezone in the298        wallclock times jumped over at the start of daylight saving time.299 300        >>> pacific = timezone('US/Pacific')301        >>> dt = datetime(2008, 3, 9, 2, 0, 0)302        >>> ploc_dt1 = pacific.localize(dt, is_dst=True)303        >>> ploc_dt2 = pacific.localize(dt, is_dst=False)304        >>> ploc_dt1.strftime(fmt)305        '2008-03-09 02:00:00 PDT (-0700)'306        >>> ploc_dt2.strftime(fmt)307        '2008-03-09 02:00:00 PST (-0800)'308        >>> str(ploc_dt2 - ploc_dt1)309        '1:00:00'310 311        Use is_dst=None to raise a NonExistentTimeError for these skipped312        times.313 314        >>> try:315        ...     loc_dt1 = pacific.localize(dt, is_dst=None)316        ... except NonExistentTimeError:317        ...     print('Non-existent')318        Non-existent319        '''320        if dt.tzinfo is not None:321            raise ValueError('Not naive datetime (tzinfo is already set)')322 323        # Find the two best possibilities.324        possible_loc_dt = set()325        for delta in [timedelta(days=-1), timedelta(days=1)]:326            loc_dt = dt + delta327            idx = max(0, bisect_right(328                self._utc_transition_times, loc_dt) - 1)329            inf = self._transition_info[idx]330            tzinfo = self._tzinfos[inf]331            loc_dt = tzinfo.normalize(dt.replace(tzinfo=tzinfo))332            if loc_dt.replace(tzinfo=None) == dt:333                possible_loc_dt.add(loc_dt)334 335        if len(possible_loc_dt) == 1:336            return possible_loc_dt.pop()337 338        # If there are no possibly correct timezones, we are attempting339        # to convert a time that never happened - the time period jumped340        # during the start-of-DST transition period.341        if len(possible_loc_dt) == 0:342            # If we refuse to guess, raise an exception.343            if is_dst is None:344                raise NonExistentTimeError(dt)345 346            # If we are forcing the pre-DST side of the DST transition, we347            # obtain the correct timezone by winding the clock forward a few348            # hours.349            elif is_dst:350                return self.localize(351                    dt + timedelta(hours=6), is_dst=True) - timedelta(hours=6)352 353            # If we are forcing the post-DST side of the DST transition, we354            # obtain the correct timezone by winding the clock back.355            else:356                return self.localize(357                    dt - timedelta(hours=6),358                    is_dst=False) + timedelta(hours=6)359 360        # If we get this far, we have multiple possible timezones - this361        # is an ambiguous case occurring during the end-of-DST transition.362 363        # If told to be strict, raise an exception since we have an364        # ambiguous case365        if is_dst is None:366            raise AmbiguousTimeError(dt)367 368        # Filter out the possiblilities that don't match the requested369        # is_dst370        filtered_possible_loc_dt = [371            p for p in possible_loc_dt if bool(p.tzinfo._dst) == is_dst372        ]373 374        # Hopefully we only have one possibility left. Return it.375        if len(filtered_possible_loc_dt) == 1:376            return filtered_possible_loc_dt[0]377 378        if len(filtered_possible_loc_dt) == 0:379            filtered_possible_loc_dt = list(possible_loc_dt)380 381        # If we get this far, we have in a wierd timezone transition382        # where the clocks have been wound back but is_dst is the same383        # in both (eg. Europe/Warsaw 1915 when they switched to CET).384        # At this point, we just have to guess unless we allow more385        # hints to be passed in (such as the UTC offset or abbreviation),386        # but that is just getting silly.387        #388        # Choose the earliest (by UTC) applicable timezone if is_dst=True389        # Choose the latest (by UTC) applicable timezone if is_dst=False390        # i.e., behave like end-of-DST transition391        dates = {}  # utc -> local392        for local_dt in filtered_possible_loc_dt:393            utc_time = (394                local_dt.replace(tzinfo=None) - local_dt.tzinfo._utcoffset)395            assert utc_time not in dates396            dates[utc_time] = local_dt397        return dates[[min, max][not is_dst](dates)]398 399    def utcoffset(self, dt, is_dst=None):400        '''See datetime.tzinfo.utcoffset401 402        The is_dst parameter may be used to remove ambiguity during DST403        transitions.404 405        >>> from pytz import timezone406        >>> tz = timezone('America/St_Johns')407        >>> ambiguous = datetime(2009, 10, 31, 23, 30)408 409        >>> str(tz.utcoffset(ambiguous, is_dst=False))410        '-1 day, 20:30:00'411 412        >>> str(tz.utcoffset(ambiguous, is_dst=True))413        '-1 day, 21:30:00'414 415        >>> try:416        ...     tz.utcoffset(ambiguous)417        ... except AmbiguousTimeError:418        ...     print('Ambiguous')419        Ambiguous420 421        '''422        if dt is None:423            return None424        elif dt.tzinfo is not self:425            dt = self.localize(dt, is_dst)426            return dt.tzinfo._utcoffset427        else:428            return self._utcoffset429 430    def dst(self, dt, is_dst=None):431        '''See datetime.tzinfo.dst432 433        The is_dst parameter may be used to remove ambiguity during DST434        transitions.435 436        >>> from pytz import timezone437        >>> tz = timezone('America/St_Johns')438 439        >>> normal = datetime(2009, 9, 1)440 441        >>> str(tz.dst(normal))442        '1:00:00'443        >>> str(tz.dst(normal, is_dst=False))444        '1:00:00'445        >>> str(tz.dst(normal, is_dst=True))446        '1:00:00'447 448        >>> ambiguous = datetime(2009, 10, 31, 23, 30)449 450        >>> str(tz.dst(ambiguous, is_dst=False))451        '0:00:00'452        >>> str(tz.dst(ambiguous, is_dst=True))453        '1:00:00'454        >>> try:455        ...     tz.dst(ambiguous)456        ... except AmbiguousTimeError:457        ...     print('Ambiguous')458        Ambiguous459 460        '''461        if dt is None:462            return None463        elif dt.tzinfo is not self:464            dt = self.localize(dt, is_dst)465            return dt.tzinfo._dst466        else:467            return self._dst468 469    def tzname(self, dt, is_dst=None):470        '''See datetime.tzinfo.tzname471 472        The is_dst parameter may be used to remove ambiguity during DST473        transitions.474 475        >>> from pytz import timezone476        >>> tz = timezone('America/St_Johns')477 478        >>> normal = datetime(2009, 9, 1)479 480        >>> tz.tzname(normal)481        'NDT'482        >>> tz.tzname(normal, is_dst=False)483        'NDT'484        >>> tz.tzname(normal, is_dst=True)485        'NDT'486 487        >>> ambiguous = datetime(2009, 10, 31, 23, 30)488 489        >>> tz.tzname(ambiguous, is_dst=False)490        'NST'491        >>> tz.tzname(ambiguous, is_dst=True)492        'NDT'493        >>> try:494        ...     tz.tzname(ambiguous)495        ... except AmbiguousTimeError:496        ...     print('Ambiguous')497        Ambiguous498        '''499        if dt is None:500            return self.zone501        elif dt.tzinfo is not self:502            dt = self.localize(dt, is_dst)503            return dt.tzinfo._tzname504        else:505            return self._tzname506 507    def __repr__(self):508        if self._dst:509            dst = 'DST'510        else:511            dst = 'STD'512        if self._utcoffset > _notime:513            return '<DstTzInfo %r %s+%s %s>' % (514                self.zone, self._tzname, self._utcoffset, dst515            )516        else:517            return '<DstTzInfo %r %s%s %s>' % (518                self.zone, self._tzname, self._utcoffset, dst519            )520 521    def __reduce__(self):522        # Special pickle to zone remains a singleton and to cope with523        # database changes.524        return pytz._p, (525            self.zone,526            _to_seconds(self._utcoffset),527            _to_seconds(self._dst),528            self._tzname529        )530 531 532def unpickler(zone, utcoffset=None, dstoffset=None, tzname=None):533    """Factory function for unpickling pytz tzinfo instances.534 535    This is shared for both StaticTzInfo and DstTzInfo instances, because536    database changes could cause a zones implementation to switch between537    these two base classes and we can't break pickles on a pytz version538    upgrade.539    """540    # Raises a KeyError if zone no longer exists, which should never happen541    # and would be a bug.542    tz = pytz.timezone(zone)543 544    # A StaticTzInfo - just return it545    if utcoffset is None:546        return tz547 548    # This pickle was created from a DstTzInfo. We need to549    # determine which of the list of tzinfo instances for this zone550    # to use in order to restore the state of any datetime instances using551    # it correctly.552    utcoffset = memorized_timedelta(utcoffset)553    dstoffset = memorized_timedelta(dstoffset)554    try:555        return tz._tzinfos[(utcoffset, dstoffset, tzname)]556    except KeyError:557        # The particular state requested in this timezone no longer exists.558        # This indicates a corrupt pickle, or the timezone database has been559        # corrected violently enough to make this particular560        # (utcoffset,dstoffset) no longer exist in the zone, or the561        # abbreviation has been changed.562        pass563 564    # See if we can find an entry differing only by tzname. Abbreviations565    # get changed from the initial guess by the database maintainers to566    # match reality when this information is discovered.567    for localized_tz in tz._tzinfos.values():568        if (localized_tz._utcoffset == utcoffset and569                localized_tz._dst == dstoffset):570            return localized_tz571 572    # This (utcoffset, dstoffset) information has been removed from the573    # zone. Add it back. This might occur when the database maintainers have574    # corrected incorrect information. datetime instances using this575    # incorrect information will continue to do so, exactly as they were576    # before being pickled. This is purely an overly paranoid safety net - I577    # doubt this will ever been needed in real life.578    inf = (utcoffset, dstoffset, tzname)579    tz._tzinfos[inf] = tz.__class__(inf, tz._tzinfos)580    return tz._tzinfos[inf]581 
codekingpro/portable-devtools · Team Ai