Path: blob/master/ invest-robot-contest_TinkoffBotTwitch-main/venv/lib/python3.8/site-packages/pytz/tzinfo.py
7789 views
'''Base classes and helpers for building zone specific tzinfo classes'''12from datetime import datetime, timedelta, tzinfo3from bisect import bisect_right4try:5set6except NameError:7from sets import Set as set89import pytz10from pytz.exceptions import AmbiguousTimeError, NonExistentTimeError1112__all__ = []1314_timedelta_cache = {}151617def memorized_timedelta(seconds):18'''Create only one instance of each distinct timedelta'''19try:20return _timedelta_cache[seconds]21except KeyError:22delta = timedelta(seconds=seconds)23_timedelta_cache[seconds] = delta24return delta2526_epoch = datetime.utcfromtimestamp(0)27_datetime_cache = {0: _epoch}282930def memorized_datetime(seconds):31'''Create only one instance of each distinct datetime'''32try:33return _datetime_cache[seconds]34except KeyError:35# NB. We can't just do datetime.utcfromtimestamp(seconds) as this36# fails with negative values under Windows (Bug #90096)37dt = _epoch + timedelta(seconds=seconds)38_datetime_cache[seconds] = dt39return dt4041_ttinfo_cache = {}424344def memorized_ttinfo(*args):45'''Create only one instance of each distinct tuple'''46try:47return _ttinfo_cache[args]48except KeyError:49ttinfo = (50memorized_timedelta(args[0]),51memorized_timedelta(args[1]),52args[2]53)54_ttinfo_cache[args] = ttinfo55return ttinfo5657_notime = memorized_timedelta(0)585960def _to_seconds(td):61'''Convert a timedelta to seconds'''62return td.seconds + td.days * 24 * 60 * 60636465class BaseTzInfo(tzinfo):66# Overridden in subclass67_utcoffset = None68_tzname = None69zone = None7071def __str__(self):72return self.zone737475class StaticTzInfo(BaseTzInfo):76'''A timezone that has a constant offset from UTC7778These timezones are rare, as most locations have changed their79offset at some point in their history80'''81def fromutc(self, dt):82'''See datetime.tzinfo.fromutc'''83if dt.tzinfo is not None and dt.tzinfo is not self:84raise ValueError('fromutc: dt.tzinfo is not self')85return (dt + self._utcoffset).replace(tzinfo=self)8687def utcoffset(self, dt, is_dst=None):88'''See datetime.tzinfo.utcoffset8990is_dst is ignored for StaticTzInfo, and exists only to91retain compatibility with DstTzInfo.92'''93return self._utcoffset9495def dst(self, dt, is_dst=None):96'''See datetime.tzinfo.dst9798is_dst is ignored for StaticTzInfo, and exists only to99retain compatibility with DstTzInfo.100'''101return _notime102103def tzname(self, dt, is_dst=None):104'''See datetime.tzinfo.tzname105106is_dst is ignored for StaticTzInfo, and exists only to107retain compatibility with DstTzInfo.108'''109return self._tzname110111def localize(self, dt, is_dst=False):112'''Convert naive time to local time'''113if dt.tzinfo is not None:114raise ValueError('Not naive datetime (tzinfo is already set)')115return dt.replace(tzinfo=self)116117def normalize(self, dt, is_dst=False):118'''Correct the timezone information on the given datetime.119120This is normally a no-op, as StaticTzInfo timezones never have121ambiguous cases to correct:122123>>> from pytz import timezone124>>> gmt = timezone('GMT')125>>> isinstance(gmt, StaticTzInfo)126True127>>> dt = datetime(2011, 5, 8, 1, 2, 3, tzinfo=gmt)128>>> gmt.normalize(dt) is dt129True130131The supported method of converting between timezones is to use132datetime.astimezone(). Currently normalize() also works:133134>>> la = timezone('America/Los_Angeles')135>>> dt = la.localize(datetime(2011, 5, 7, 1, 2, 3))136>>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)'137>>> gmt.normalize(dt).strftime(fmt)138'2011-05-07 08:02:03 GMT (+0000)'139'''140if dt.tzinfo is self:141return dt142if dt.tzinfo is None:143raise ValueError('Naive time - no tzinfo set')144return dt.astimezone(self)145146def __repr__(self):147return '<StaticTzInfo %r>' % (self.zone,)148149def __reduce__(self):150# Special pickle to zone remains a singleton and to cope with151# database changes.152return pytz._p, (self.zone,)153154155class DstTzInfo(BaseTzInfo):156'''A timezone that has a variable offset from UTC157158The offset might change if daylight saving time comes into effect,159or at a point in history when the region decides to change their160timezone definition.161'''162# Overridden in subclass163164# Sorted list of DST transition times, UTC165_utc_transition_times = None166167# [(utcoffset, dstoffset, tzname)] corresponding to168# _utc_transition_times entries169_transition_info = None170171zone = None172173# Set in __init__174175_tzinfos = None176_dst = None # DST offset177178def __init__(self, _inf=None, _tzinfos=None):179if _inf:180self._tzinfos = _tzinfos181self._utcoffset, self._dst, self._tzname = _inf182else:183_tzinfos = {}184self._tzinfos = _tzinfos185self._utcoffset, self._dst, self._tzname = (186self._transition_info[0])187_tzinfos[self._transition_info[0]] = self188for inf in self._transition_info[1:]:189if inf not in _tzinfos:190_tzinfos[inf] = self.__class__(inf, _tzinfos)191192def fromutc(self, dt):193'''See datetime.tzinfo.fromutc'''194if (dt.tzinfo is not None and195getattr(dt.tzinfo, '_tzinfos', None) is not self._tzinfos):196raise ValueError('fromutc: dt.tzinfo is not self')197dt = dt.replace(tzinfo=None)198idx = max(0, bisect_right(self._utc_transition_times, dt) - 1)199inf = self._transition_info[idx]200return (dt + inf[0]).replace(tzinfo=self._tzinfos[inf])201202def normalize(self, dt):203'''Correct the timezone information on the given datetime204205If date arithmetic crosses DST boundaries, the tzinfo206is not magically adjusted. This method normalizes the207tzinfo to the correct one.208209To test, first we need to do some setup210211>>> from pytz import timezone212>>> utc = timezone('UTC')213>>> eastern = timezone('US/Eastern')214>>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)'215216We next create a datetime right on an end-of-DST transition point,217the instant when the wallclocks are wound back one hour.218219>>> utc_dt = datetime(2002, 10, 27, 6, 0, 0, tzinfo=utc)220>>> loc_dt = utc_dt.astimezone(eastern)221>>> loc_dt.strftime(fmt)222'2002-10-27 01:00:00 EST (-0500)'223224Now, if we subtract a few minutes from it, note that the timezone225information has not changed.226227>>> before = loc_dt - timedelta(minutes=10)228>>> before.strftime(fmt)229'2002-10-27 00:50:00 EST (-0500)'230231But we can fix that by calling the normalize method232233>>> before = eastern.normalize(before)234>>> before.strftime(fmt)235'2002-10-27 01:50:00 EDT (-0400)'236237The supported method of converting between timezones is to use238datetime.astimezone(). Currently, normalize() also works:239240>>> th = timezone('Asia/Bangkok')241>>> am = timezone('Europe/Amsterdam')242>>> dt = th.localize(datetime(2011, 5, 7, 1, 2, 3))243>>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)'244>>> am.normalize(dt).strftime(fmt)245'2011-05-06 20:02:03 CEST (+0200)'246'''247if dt.tzinfo is None:248raise ValueError('Naive time - no tzinfo set')249250# Convert dt in localtime to UTC251offset = dt.tzinfo._utcoffset252dt = dt.replace(tzinfo=None)253dt = dt - offset254# convert it back, and return it255return self.fromutc(dt)256257def localize(self, dt, is_dst=False):258'''Convert naive time to local time.259260This method should be used to construct localtimes, rather261than passing a tzinfo argument to a datetime constructor.262263is_dst is used to determine the correct timezone in the ambigous264period at the end of daylight saving time.265266>>> from pytz import timezone267>>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)'268>>> amdam = timezone('Europe/Amsterdam')269>>> dt = datetime(2004, 10, 31, 2, 0, 0)270>>> loc_dt1 = amdam.localize(dt, is_dst=True)271>>> loc_dt2 = amdam.localize(dt, is_dst=False)272>>> loc_dt1.strftime(fmt)273'2004-10-31 02:00:00 CEST (+0200)'274>>> loc_dt2.strftime(fmt)275'2004-10-31 02:00:00 CET (+0100)'276>>> str(loc_dt2 - loc_dt1)277'1:00:00'278279Use is_dst=None to raise an AmbiguousTimeError for ambiguous280times at the end of daylight saving time281282>>> try:283... loc_dt1 = amdam.localize(dt, is_dst=None)284... except AmbiguousTimeError:285... print('Ambiguous')286Ambiguous287288is_dst defaults to False289290>>> amdam.localize(dt) == amdam.localize(dt, False)291True292293is_dst is also used to determine the correct timezone in the294wallclock times jumped over at the start of daylight saving time.295296>>> pacific = timezone('US/Pacific')297>>> dt = datetime(2008, 3, 9, 2, 0, 0)298>>> ploc_dt1 = pacific.localize(dt, is_dst=True)299>>> ploc_dt2 = pacific.localize(dt, is_dst=False)300>>> ploc_dt1.strftime(fmt)301'2008-03-09 02:00:00 PDT (-0700)'302>>> ploc_dt2.strftime(fmt)303'2008-03-09 02:00:00 PST (-0800)'304>>> str(ploc_dt2 - ploc_dt1)305'1:00:00'306307Use is_dst=None to raise a NonExistentTimeError for these skipped308times.309310>>> try:311... loc_dt1 = pacific.localize(dt, is_dst=None)312... except NonExistentTimeError:313... print('Non-existent')314Non-existent315'''316if dt.tzinfo is not None:317raise ValueError('Not naive datetime (tzinfo is already set)')318319# Find the two best possibilities.320possible_loc_dt = set()321for delta in [timedelta(days=-1), timedelta(days=1)]:322loc_dt = dt + delta323idx = max(0, bisect_right(324self._utc_transition_times, loc_dt) - 1)325inf = self._transition_info[idx]326tzinfo = self._tzinfos[inf]327loc_dt = tzinfo.normalize(dt.replace(tzinfo=tzinfo))328if loc_dt.replace(tzinfo=None) == dt:329possible_loc_dt.add(loc_dt)330331if len(possible_loc_dt) == 1:332return possible_loc_dt.pop()333334# If there are no possibly correct timezones, we are attempting335# to convert a time that never happened - the time period jumped336# during the start-of-DST transition period.337if len(possible_loc_dt) == 0:338# If we refuse to guess, raise an exception.339if is_dst is None:340raise NonExistentTimeError(dt)341342# If we are forcing the pre-DST side of the DST transition, we343# obtain the correct timezone by winding the clock forward a few344# hours.345elif is_dst:346return self.localize(347dt + timedelta(hours=6), is_dst=True) - timedelta(hours=6)348349# If we are forcing the post-DST side of the DST transition, we350# obtain the correct timezone by winding the clock back.351else:352return self.localize(353dt - timedelta(hours=6),354is_dst=False) + timedelta(hours=6)355356# If we get this far, we have multiple possible timezones - this357# is an ambiguous case occuring during the end-of-DST transition.358359# If told to be strict, raise an exception since we have an360# ambiguous case361if is_dst is None:362raise AmbiguousTimeError(dt)363364# Filter out the possiblilities that don't match the requested365# is_dst366filtered_possible_loc_dt = [367p for p in possible_loc_dt if bool(p.tzinfo._dst) == is_dst368]369370# Hopefully we only have one possibility left. Return it.371if len(filtered_possible_loc_dt) == 1:372return filtered_possible_loc_dt[0]373374if len(filtered_possible_loc_dt) == 0:375filtered_possible_loc_dt = list(possible_loc_dt)376377# If we get this far, we have in a wierd timezone transition378# where the clocks have been wound back but is_dst is the same379# in both (eg. Europe/Warsaw 1915 when they switched to CET).380# At this point, we just have to guess unless we allow more381# hints to be passed in (such as the UTC offset or abbreviation),382# but that is just getting silly.383#384# Choose the earliest (by UTC) applicable timezone if is_dst=True385# Choose the latest (by UTC) applicable timezone if is_dst=False386# i.e., behave like end-of-DST transition387dates = {} # utc -> local388for local_dt in filtered_possible_loc_dt:389utc_time = (390local_dt.replace(tzinfo=None) - local_dt.tzinfo._utcoffset)391assert utc_time not in dates392dates[utc_time] = local_dt393return dates[[min, max][not is_dst](dates)]394395def utcoffset(self, dt, is_dst=None):396'''See datetime.tzinfo.utcoffset397398The is_dst parameter may be used to remove ambiguity during DST399transitions.400401>>> from pytz import timezone402>>> tz = timezone('America/St_Johns')403>>> ambiguous = datetime(2009, 10, 31, 23, 30)404405>>> str(tz.utcoffset(ambiguous, is_dst=False))406'-1 day, 20:30:00'407408>>> str(tz.utcoffset(ambiguous, is_dst=True))409'-1 day, 21:30:00'410411>>> try:412... tz.utcoffset(ambiguous)413... except AmbiguousTimeError:414... print('Ambiguous')415Ambiguous416417'''418if dt is None:419return None420elif dt.tzinfo is not self:421dt = self.localize(dt, is_dst)422return dt.tzinfo._utcoffset423else:424return self._utcoffset425426def dst(self, dt, is_dst=None):427'''See datetime.tzinfo.dst428429The is_dst parameter may be used to remove ambiguity during DST430transitions.431432>>> from pytz import timezone433>>> tz = timezone('America/St_Johns')434435>>> normal = datetime(2009, 9, 1)436437>>> str(tz.dst(normal))438'1:00:00'439>>> str(tz.dst(normal, is_dst=False))440'1:00:00'441>>> str(tz.dst(normal, is_dst=True))442'1:00:00'443444>>> ambiguous = datetime(2009, 10, 31, 23, 30)445446>>> str(tz.dst(ambiguous, is_dst=False))447'0:00:00'448>>> str(tz.dst(ambiguous, is_dst=True))449'1:00:00'450>>> try:451... tz.dst(ambiguous)452... except AmbiguousTimeError:453... print('Ambiguous')454Ambiguous455456'''457if dt is None:458return None459elif dt.tzinfo is not self:460dt = self.localize(dt, is_dst)461return dt.tzinfo._dst462else:463return self._dst464465def tzname(self, dt, is_dst=None):466'''See datetime.tzinfo.tzname467468The is_dst parameter may be used to remove ambiguity during DST469transitions.470471>>> from pytz import timezone472>>> tz = timezone('America/St_Johns')473474>>> normal = datetime(2009, 9, 1)475476>>> tz.tzname(normal)477'NDT'478>>> tz.tzname(normal, is_dst=False)479'NDT'480>>> tz.tzname(normal, is_dst=True)481'NDT'482483>>> ambiguous = datetime(2009, 10, 31, 23, 30)484485>>> tz.tzname(ambiguous, is_dst=False)486'NST'487>>> tz.tzname(ambiguous, is_dst=True)488'NDT'489>>> try:490... tz.tzname(ambiguous)491... except AmbiguousTimeError:492... print('Ambiguous')493Ambiguous494'''495if dt is None:496return self.zone497elif dt.tzinfo is not self:498dt = self.localize(dt, is_dst)499return dt.tzinfo._tzname500else:501return self._tzname502503def __repr__(self):504if self._dst:505dst = 'DST'506else:507dst = 'STD'508if self._utcoffset > _notime:509return '<DstTzInfo %r %s+%s %s>' % (510self.zone, self._tzname, self._utcoffset, dst511)512else:513return '<DstTzInfo %r %s%s %s>' % (514self.zone, self._tzname, self._utcoffset, dst515)516517def __reduce__(self):518# Special pickle to zone remains a singleton and to cope with519# database changes.520return pytz._p, (521self.zone,522_to_seconds(self._utcoffset),523_to_seconds(self._dst),524self._tzname525)526527528def unpickler(zone, utcoffset=None, dstoffset=None, tzname=None):529"""Factory function for unpickling pytz tzinfo instances.530531This is shared for both StaticTzInfo and DstTzInfo instances, because532database changes could cause a zones implementation to switch between533these two base classes and we can't break pickles on a pytz version534upgrade.535"""536# Raises a KeyError if zone no longer exists, which should never happen537# and would be a bug.538tz = pytz.timezone(zone)539540# A StaticTzInfo - just return it541if utcoffset is None:542return tz543544# This pickle was created from a DstTzInfo. We need to545# determine which of the list of tzinfo instances for this zone546# to use in order to restore the state of any datetime instances using547# it correctly.548utcoffset = memorized_timedelta(utcoffset)549dstoffset = memorized_timedelta(dstoffset)550try:551return tz._tzinfos[(utcoffset, dstoffset, tzname)]552except KeyError:553# The particular state requested in this timezone no longer exists.554# This indicates a corrupt pickle, or the timezone database has been555# corrected violently enough to make this particular556# (utcoffset,dstoffset) no longer exist in the zone, or the557# abbreviation has been changed.558pass559560# See if we can find an entry differing only by tzname. Abbreviations561# get changed from the initial guess by the database maintainers to562# match reality when this information is discovered.563for localized_tz in tz._tzinfos.values():564if (localized_tz._utcoffset == utcoffset and565localized_tz._dst == dstoffset):566return localized_tz567568# This (utcoffset, dstoffset) information has been removed from the569# zone. Add it back. This might occur when the database maintainers have570# corrected incorrect information. datetime instances using this571# incorrect information will continue to do so, exactly as they were572# before being pickled. This is purely an overly paranoid safety net - I573# doubt this will ever been needed in real life.574inf = (utcoffset, dstoffset, tzname)575tz._tzinfos[inf] = tz.__class__(inf, tz._tzinfos)576return tz._tzinfos[inf]577578579