|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023 |
- # -*- coding: UTF-8 -*-
-
- """
- win32timezone:
- Module for handling datetime.tzinfo time zones using the windows
- registry for time zone information. The time zone names are dependent
- on the registry entries defined by the operating system.
-
- This module may be tested using the doctest module.
-
- Written by Jason R. Coombs (jaraco@jaraco.com).
- Copyright © 2003-2012.
- All Rights Reserved.
-
- This module is licenced for use in Mark Hammond's pywin32
- library under the same terms as the pywin32 library.
-
- To use this time zone module with the datetime module, simply pass
- the TimeZoneInfo object to the datetime constructor. For example,
-
- >>> import win32timezone, datetime
- >>> assert 'Mountain Standard Time' in win32timezone.TimeZoneInfo.get_sorted_time_zone_names()
- >>> MST = win32timezone.TimeZoneInfo('Mountain Standard Time')
- >>> now = datetime.datetime.now(MST)
-
- The now object is now a time-zone aware object, and daylight savings-
- aware methods may be called on it.
-
- >>> now.utcoffset() in (datetime.timedelta(-1, 61200), datetime.timedelta(-1, 64800))
- True
-
- (note that the result of utcoffset call will be different based on when now was
- generated, unless standard time is always used)
-
- >>> now = datetime.datetime.now(TimeZoneInfo('Mountain Standard Time', True))
- >>> now.utcoffset()
- datetime.timedelta(days=-1, seconds=61200)
-
- >>> aug2 = datetime.datetime(2003, 8, 2, tzinfo = MST)
- >>> tuple(aug2.utctimetuple())
- (2003, 8, 2, 6, 0, 0, 5, 214, 0)
- >>> nov2 = datetime.datetime(2003, 11, 25, tzinfo = MST)
- >>> tuple(nov2.utctimetuple())
- (2003, 11, 25, 7, 0, 0, 1, 329, 0)
-
- To convert from one timezone to another, just use the astimezone method.
-
- >>> aug2.isoformat()
- '2003-08-02T00:00:00-06:00'
- >>> aug2est = aug2.astimezone(win32timezone.TimeZoneInfo('Eastern Standard Time'))
- >>> aug2est.isoformat()
- '2003-08-02T02:00:00-04:00'
-
- calling the displayName member will return the display name as set in the
- registry.
-
- >>> est = win32timezone.TimeZoneInfo('Eastern Standard Time')
- >>> str(est.displayName)
- '(UTC-05:00) Eastern Time (US & Canada)'
-
- >>> gmt = win32timezone.TimeZoneInfo('GMT Standard Time', True)
- >>> str(gmt.displayName)
- '(UTC+00:00) Dublin, Edinburgh, Lisbon, London'
-
- To get the complete list of available time zone keys,
- >>> zones = win32timezone.TimeZoneInfo.get_all_time_zones()
-
- If you want to get them in an order that's sorted longitudinally
- >>> zones = win32timezone.TimeZoneInfo.get_sorted_time_zones()
-
- TimeZoneInfo now supports being pickled and comparison
- >>> import pickle
- >>> tz = win32timezone.TimeZoneInfo('China Standard Time')
- >>> tz == pickle.loads(pickle.dumps(tz))
- True
-
- It's possible to construct a TimeZoneInfo from a TimeZoneDescription
- including the currently-defined zone.
- >>> tz = win32timezone.TimeZoneInfo(TimeZoneDefinition.current())
- >>> tz == pickle.loads(pickle.dumps(tz))
- True
-
- >>> aest = win32timezone.TimeZoneInfo('AUS Eastern Standard Time')
- >>> est = win32timezone.TimeZoneInfo('E. Australia Standard Time')
- >>> dt = datetime.datetime(2006, 11, 11, 1, 0, 0, tzinfo = aest)
- >>> estdt = dt.astimezone(est)
- >>> estdt.strftime('%Y-%m-%d %H:%M:%S')
- '2006-11-11 00:00:00'
-
- >>> dt = datetime.datetime(2007, 1, 12, 1, 0, 0, tzinfo = aest)
- >>> estdt = dt.astimezone(est)
- >>> estdt.strftime('%Y-%m-%d %H:%M:%S')
- '2007-01-12 00:00:00'
-
- >>> dt = datetime.datetime(2007, 6, 13, 1, 0, 0, tzinfo = aest)
- >>> estdt = dt.astimezone(est)
- >>> estdt.strftime('%Y-%m-%d %H:%M:%S')
- '2007-06-13 01:00:00'
-
- Microsoft now has a patch for handling time zones in 2007 (see
- http://support.microsoft.com/gp/cp_dst)
-
- As a result, patched systems will give an incorrect result for
- dates prior to the designated year except for Vista and its
- successors, which have dynamic time zone support.
- >>> nov2_pre_change = datetime.datetime(2003, 11, 2, tzinfo = MST)
- >>> old_response = (2003, 11, 2, 7, 0, 0, 6, 306, 0)
- >>> incorrect_patch_response = (2003, 11, 2, 6, 0, 0, 6, 306, 0)
- >>> pre_response = nov2_pre_change.utctimetuple()
- >>> pre_response in (old_response, incorrect_patch_response)
- True
-
- Furthermore, unpatched systems pre-Vista will give an incorrect
- result for dates after 2007.
- >>> nov2_post_change = datetime.datetime(2007, 11, 2, tzinfo = MST)
- >>> incorrect_unpatched_response = (2007, 11, 2, 7, 0, 0, 4, 306, 0)
- >>> new_response = (2007, 11, 2, 6, 0, 0, 4, 306, 0)
- >>> post_response = nov2_post_change.utctimetuple()
- >>> post_response in (new_response, incorrect_unpatched_response)
- True
-
-
- There is a function you can call to get some capabilities of the time
- zone data.
- >>> caps = GetTZCapabilities()
- >>> isinstance(caps, dict)
- True
- >>> 'MissingTZPatch' in caps
- True
- >>> 'DynamicTZSupport' in caps
- True
-
- >>> both_dates_correct = (pre_response == old_response and post_response == new_response)
- >>> old_dates_wrong = (pre_response == incorrect_patch_response)
- >>> new_dates_wrong = (post_response == incorrect_unpatched_response)
-
- >>> caps['DynamicTZSupport'] == both_dates_correct
- True
-
- >>> (not caps['DynamicTZSupport'] and caps['MissingTZPatch']) == new_dates_wrong
- True
-
- >>> (not caps['DynamicTZSupport'] and not caps['MissingTZPatch']) == old_dates_wrong
- True
-
- This test helps ensure language support for unicode characters
- >>> x = TIME_ZONE_INFORMATION(0, u'français')
-
-
- Test conversion from one time zone to another at a DST boundary
- ===============================================================
-
- >>> tz_hi = TimeZoneInfo('Hawaiian Standard Time')
- >>> tz_pac = TimeZoneInfo('Pacific Standard Time')
- >>> time_before = datetime.datetime(2011, 11, 5, 15, 59, 59, tzinfo=tz_hi)
- >>> tz_hi.utcoffset(time_before)
- datetime.timedelta(days=-1, seconds=50400)
- >>> tz_hi.dst(time_before)
- datetime.timedelta(0)
-
- Hawaii doesn't need dynamic TZ info
- >>> getattr(tz_hi, 'dynamicInfo', None)
-
- Here's a time that gave some trouble as reported in #3523104
- because one minute later, the equivalent UTC time changes from DST
- in the U.S.
- >>> dt_hi = datetime.datetime(2011, 11, 5, 15, 59, 59, 0, tzinfo=tz_hi)
- >>> dt_hi.timetuple()
- time.struct_time(tm_year=2011, tm_mon=11, tm_mday=5, tm_hour=15, tm_min=59, tm_sec=59, tm_wday=5, tm_yday=309, tm_isdst=0)
- >>> dt_hi.utctimetuple()
- time.struct_time(tm_year=2011, tm_mon=11, tm_mday=6, tm_hour=1, tm_min=59, tm_sec=59, tm_wday=6, tm_yday=310, tm_isdst=0)
-
- Convert the time to pacific time.
- >>> dt_pac = dt_hi.astimezone(tz_pac)
- >>> dt_pac.timetuple()
- time.struct_time(tm_year=2011, tm_mon=11, tm_mday=5, tm_hour=18, tm_min=59, tm_sec=59, tm_wday=5, tm_yday=309, tm_isdst=1)
-
- Notice that the UTC time is almost 2am.
- >>> dt_pac.utctimetuple()
- time.struct_time(tm_year=2011, tm_mon=11, tm_mday=6, tm_hour=1, tm_min=59, tm_sec=59, tm_wday=6, tm_yday=310, tm_isdst=0)
-
- Now do the same tests one minute later in Hawaii.
- >>> time_after = datetime.datetime(2011, 11, 5, 16, 0, 0, 0, tzinfo=tz_hi)
- >>> tz_hi.utcoffset(time_after)
- datetime.timedelta(days=-1, seconds=50400)
- >>> tz_hi.dst(time_before)
- datetime.timedelta(0)
-
- >>> dt_hi = datetime.datetime(2011, 11, 5, 16, 0, 0, 0, tzinfo=tz_hi)
- >>> print(dt_hi.timetuple())
- time.struct_time(tm_year=2011, tm_mon=11, tm_mday=5, tm_hour=16, tm_min=0, tm_sec=0, tm_wday=5, tm_yday=309, tm_isdst=0)
- >>> print(dt_hi.utctimetuple())
- time.struct_time(tm_year=2011, tm_mon=11, tm_mday=6, tm_hour=2, tm_min=0, tm_sec=0, tm_wday=6, tm_yday=310, tm_isdst=0)
-
- According to the docs, this is what astimezone does.
- >>> utc = (dt_hi - dt_hi.utcoffset()).replace(tzinfo=tz_pac)
- >>> utc
- datetime.datetime(2011, 11, 6, 2, 0, tzinfo=TimeZoneInfo('Pacific Standard Time'))
- >>> tz_pac.fromutc(utc) == dt_hi.astimezone(tz_pac)
- True
- >>> tz_pac.fromutc(utc)
- datetime.datetime(2011, 11, 5, 19, 0, tzinfo=TimeZoneInfo('Pacific Standard Time'))
-
- Make sure the converted time is correct.
- >>> dt_pac = dt_hi.astimezone(tz_pac)
- >>> dt_pac.timetuple()
- time.struct_time(tm_year=2011, tm_mon=11, tm_mday=5, tm_hour=19, tm_min=0, tm_sec=0, tm_wday=5, tm_yday=309, tm_isdst=1)
- >>> dt_pac.utctimetuple()
- time.struct_time(tm_year=2011, tm_mon=11, tm_mday=6, tm_hour=2, tm_min=0, tm_sec=0, tm_wday=6, tm_yday=310, tm_isdst=0)
-
- Check some internal methods
- >>> tz_pac._getStandardBias(datetime.datetime(2011, 1, 1))
- datetime.timedelta(seconds=28800)
- >>> tz_pac._getDaylightBias(datetime.datetime(2011, 1, 1))
- datetime.timedelta(seconds=25200)
-
- Test the offsets
- >>> offset = tz_pac.utcoffset(datetime.datetime(2011, 11, 6, 2, 0))
- >>> offset == datetime.timedelta(hours=-8)
- True
- >>> dst_offset = tz_pac.dst(datetime.datetime(2011, 11, 6, 2, 0) + offset)
- >>> dst_offset == datetime.timedelta(hours=1)
- True
- >>> (offset + dst_offset) == datetime.timedelta(hours=-7)
- True
-
-
- Test offsets that occur right at the DST changeover
- >>> datetime.datetime.utcfromtimestamp(1320570000).replace(
- ... tzinfo=TimeZoneInfo.utc()).astimezone(tz_pac)
- datetime.datetime(2011, 11, 6, 1, 0, tzinfo=TimeZoneInfo('Pacific Standard Time'))
-
- """
- __author__ = "Jason R. Coombs <jaraco@jaraco.com>"
-
- import datetime
- import logging
- import operator
- import re
- import struct
- import winreg
- from itertools import count
-
- import win32api
-
- log = logging.getLogger(__file__)
-
-
- # A couple of objects for working with objects as if they were native C-type
- # structures.
- class _SimpleStruct(object):
- _fields_ = None # must be overridden by subclasses
-
- def __init__(self, *args, **kw):
- for i, (name, typ) in enumerate(self._fields_):
- def_arg = None
- if i < len(args):
- def_arg = args[i]
- if name in kw:
- def_arg = kw[name]
- if def_arg is not None:
- if not isinstance(def_arg, tuple):
- def_arg = (def_arg,)
- else:
- def_arg = ()
- if len(def_arg) == 1 and isinstance(def_arg[0], typ):
- # already an object of this type.
- # XXX - should copy.copy???
- def_val = def_arg[0]
- else:
- def_val = typ(*def_arg)
- setattr(self, name, def_val)
-
- def field_names(self):
- return [f[0] for f in self._fields_]
-
- def __eq__(self, other):
- if not hasattr(other, "_fields_"):
- return False
- if self._fields_ != other._fields_:
- return False
- for name, _ in self._fields_:
- if getattr(self, name) != getattr(other, name):
- return False
- return True
-
- def __ne__(self, other):
- return not self.__eq__(other)
-
-
- class SYSTEMTIME(_SimpleStruct):
- _fields_ = [
- ("year", int),
- ("month", int),
- ("day_of_week", int),
- ("day", int),
- ("hour", int),
- ("minute", int),
- ("second", int),
- ("millisecond", int),
- ]
-
-
- class TIME_ZONE_INFORMATION(_SimpleStruct):
- _fields_ = [
- ("bias", int),
- ("standard_name", str),
- ("standard_start", SYSTEMTIME),
- ("standard_bias", int),
- ("daylight_name", str),
- ("daylight_start", SYSTEMTIME),
- ("daylight_bias", int),
- ]
-
-
- class DYNAMIC_TIME_ZONE_INFORMATION(_SimpleStruct):
- _fields_ = TIME_ZONE_INFORMATION._fields_ + [
- ("key_name", str),
- ("dynamic_daylight_time_disabled", bool),
- ]
-
-
- class TimeZoneDefinition(DYNAMIC_TIME_ZONE_INFORMATION):
- """
- A time zone definition class based on the win32
- DYNAMIC_TIME_ZONE_INFORMATION structure.
-
- Describes a bias against UTC (bias), and two dates at which a separate
- additional bias applies (standard_bias and daylight_bias).
- """
-
- def __init__(self, *args, **kwargs):
- """
- Try to construct a TimeZoneDefinition from
- a) [DYNAMIC_]TIME_ZONE_INFORMATION args
- b) another TimeZoneDefinition
- c) a byte structure (using _from_bytes)
- """
- try:
- super(TimeZoneDefinition, self).__init__(*args, **kwargs)
- return
- except (TypeError, ValueError):
- pass
-
- try:
- self.__init_from_other(*args, **kwargs)
- return
- except TypeError:
- pass
-
- try:
- self.__init_from_bytes(*args, **kwargs)
- return
- except TypeError:
- pass
-
- raise TypeError("Invalid arguments for %s" % self.__class__)
-
- def __init_from_bytes(
- self,
- bytes,
- standard_name="",
- daylight_name="",
- key_name="",
- daylight_disabled=False,
- ):
- format = "3l8h8h"
- components = struct.unpack(format, bytes)
- bias, standard_bias, daylight_bias = components[:3]
- standard_start = SYSTEMTIME(*components[3:11])
- daylight_start = SYSTEMTIME(*components[11:19])
- super(TimeZoneDefinition, self).__init__(
- bias,
- standard_name,
- standard_start,
- standard_bias,
- daylight_name,
- daylight_start,
- daylight_bias,
- key_name,
- daylight_disabled,
- )
-
- def __init_from_other(self, other):
- if not isinstance(other, TIME_ZONE_INFORMATION):
- raise TypeError("Not a TIME_ZONE_INFORMATION")
- for name in other.field_names():
- # explicitly get the value from the underlying structure
- value = super(TimeZoneDefinition, other).__getattribute__(other, name)
- setattr(self, name, value)
- # consider instead of the loop above just copying the memory directly
- # size = max(ctypes.sizeof(DYNAMIC_TIME_ZONE_INFO), ctypes.sizeof(other))
- # ctypes.memmove(ctypes.addressof(self), other, size)
-
- def __getattribute__(self, attr):
- value = super(TimeZoneDefinition, self).__getattribute__(attr)
- if "bias" in attr:
- value = datetime.timedelta(minutes=value)
- return value
-
- @classmethod
- def current(class_):
- "Windows Platform SDK GetTimeZoneInformation"
- code, tzi = win32api.GetTimeZoneInformation(True)
- return code, class_(*tzi)
-
- def set(self):
- tzi = tuple(getattr(self, n) for n, t in self._fields_)
- win32api.SetTimeZoneInformation(tzi)
-
- def copy(self):
- # XXX - this is no longer a copy!
- return self.__class__(self)
-
- def locate_daylight_start(self, year):
- return self._locate_day(year, self.daylight_start)
-
- def locate_standard_start(self, year):
- return self._locate_day(year, self.standard_start)
-
- @staticmethod
- def _locate_day(year, cutoff):
- """
- Takes a SYSTEMTIME object, such as retrieved from a TIME_ZONE_INFORMATION
- structure or call to GetTimeZoneInformation and interprets it based on the given
- year to identify the actual day.
-
- This method is necessary because the SYSTEMTIME structure refers to a day by its
- day of the week and week of the month (e.g. 4th saturday in March).
-
- >>> SATURDAY = 6
- >>> MARCH = 3
- >>> st = SYSTEMTIME(2000, MARCH, SATURDAY, 4, 0, 0, 0, 0)
-
- # according to my calendar, the 4th Saturday in March in 2009 was the 28th
- >>> expected_date = datetime.datetime(2009, 3, 28)
- >>> TimeZoneDefinition._locate_day(2009, st) == expected_date
- True
- """
- # MS stores Sunday as 0, Python datetime stores Monday as zero
- target_weekday = (cutoff.day_of_week + 6) % 7
- # For SYSTEMTIMEs relating to time zone inforamtion, cutoff.day
- # is the week of the month
- week_of_month = cutoff.day
- # so the following is the first day of that week
- day = (week_of_month - 1) * 7 + 1
- result = datetime.datetime(
- year,
- cutoff.month,
- day,
- cutoff.hour,
- cutoff.minute,
- cutoff.second,
- cutoff.millisecond,
- )
- # now the result is the correct week, but not necessarily the correct day of the week
- days_to_go = (target_weekday - result.weekday()) % 7
- result += datetime.timedelta(days_to_go)
- # if we selected a day in the month following the target month,
- # move back a week or two.
- # This is necessary because Microsoft defines the fifth week in a month
- # to be the last week in a month and adding the time delta might have
- # pushed the result into the next month.
- while result.month == cutoff.month + 1:
- result -= datetime.timedelta(weeks=1)
- return result
-
-
- class TimeZoneInfo(datetime.tzinfo):
- """
- Main class for handling Windows time zones.
- Usage:
- TimeZoneInfo(<Time Zone Standard Name>, [<Fix Standard Time>])
-
- If <Fix Standard Time> evaluates to True, daylight savings time is
- calculated in the same way as standard time.
-
- >>> tzi = TimeZoneInfo('Pacific Standard Time')
- >>> march31 = datetime.datetime(2000,3,31)
-
- We know that time zone definitions haven't changed from 2007
- to 2012, so regardless of whether dynamic info is available,
- there should be consistent results for these years.
- >>> subsequent_years = [march31.replace(year=year)
- ... for year in range(2007, 2013)]
- >>> offsets = set(tzi.utcoffset(year) for year in subsequent_years)
- >>> len(offsets)
- 1
- """
-
- # this key works for WinNT+, but not for the Win95 line.
- tzRegKey = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones"
-
- def __init__(self, param=None, fix_standard_time=False):
- if isinstance(param, TimeZoneDefinition):
- self._LoadFromTZI(param)
- if isinstance(param, str):
- self.timeZoneName = param
- self._LoadInfoFromKey()
- self.fixedStandardTime = fix_standard_time
-
- def _FindTimeZoneKey(self):
- """Find the registry key for the time zone name (self.timeZoneName)."""
- # for multi-language compatability, match the time zone name in the
- # "Std" key of the time zone key.
- zoneNames = dict(self._get_indexed_time_zone_keys("Std"))
- # Also match the time zone key name itself, to be compatible with
- # English-based hard-coded time zones.
- timeZoneName = zoneNames.get(self.timeZoneName, self.timeZoneName)
- key = _RegKeyDict.open(winreg.HKEY_LOCAL_MACHINE, self.tzRegKey)
- try:
- result = key.subkey(timeZoneName)
- except Exception:
- raise ValueError("Timezone Name %s not found." % timeZoneName)
- return result
-
- def _LoadInfoFromKey(self):
- """Loads the information from an opened time zone registry key
- into relevant fields of this TZI object"""
- key = self._FindTimeZoneKey()
- self.displayName = key["Display"]
- self.standardName = key["Std"]
- self.daylightName = key["Dlt"]
- self.staticInfo = TimeZoneDefinition(key["TZI"])
- self._LoadDynamicInfoFromKey(key)
-
- def _LoadFromTZI(self, tzi):
- self.timeZoneName = tzi.standard_name
- self.displayName = "Unknown"
- self.standardName = tzi.standard_name
- self.daylightName = tzi.daylight_name
- self.staticInfo = tzi
-
- def _LoadDynamicInfoFromKey(self, key):
- """
- >>> tzi = TimeZoneInfo('Central Standard Time')
-
- Here's how the RangeMap is supposed to work:
- >>> m = RangeMap(zip([2006,2007], 'BC'),
- ... sort_params = dict(reverse=True),
- ... key_match_comparator=operator.ge)
- >>> m.get(2000, 'A')
- 'A'
- >>> m[2006]
- 'B'
- >>> m[2007]
- 'C'
- >>> m[2008]
- 'C'
-
- >>> m[RangeMap.last_item]
- 'B'
-
- >>> m.get(2008, m[RangeMap.last_item])
- 'C'
-
-
- Now test the dynamic info (but fallback to our simple RangeMap
- on systems that don't have dynamicInfo).
-
- >>> dinfo = getattr(tzi, 'dynamicInfo', m)
- >>> 2007 in dinfo
- True
- >>> 2008 in dinfo
- False
- >>> dinfo[2007] == dinfo[2008] == dinfo[2012]
- True
- """
- try:
- info = key.subkey("Dynamic DST")
- except WindowsError:
- return
- del info["FirstEntry"]
- del info["LastEntry"]
- years = map(int, list(info.keys()))
- values = map(TimeZoneDefinition, list(info.values()))
- # create a range mapping that searches by descending year and matches
- # if the target year is greater or equal.
- self.dynamicInfo = RangeMap(
- zip(years, values),
- sort_params=dict(reverse=True),
- key_match_comparator=operator.ge,
- )
-
- def __repr__(self):
- result = "%s(%s" % (self.__class__.__name__, repr(self.timeZoneName))
- if self.fixedStandardTime:
- result += ", True"
- result += ")"
- return result
-
- def __str__(self):
- return self.displayName
-
- def tzname(self, dt):
- winInfo = self.getWinInfo(dt)
- if self.dst(dt) == winInfo.daylight_bias:
- result = self.daylightName
- elif self.dst(dt) == winInfo.standard_bias:
- result = self.standardName
- return result
-
- def getWinInfo(self, targetYear):
- """
- Return the most relevant "info" for this time zone
- in the target year.
- """
- if not hasattr(self, "dynamicInfo") or not self.dynamicInfo:
- return self.staticInfo
- # Find the greatest year entry in self.dynamicInfo which is for
- # a year greater than or equal to our targetYear. If not found,
- # default to the earliest year.
- return self.dynamicInfo.get(targetYear, self.dynamicInfo[RangeMap.last_item])
-
- def _getStandardBias(self, dt):
- winInfo = self.getWinInfo(dt.year)
- return winInfo.bias + winInfo.standard_bias
-
- def _getDaylightBias(self, dt):
- winInfo = self.getWinInfo(dt.year)
- return winInfo.bias + winInfo.daylight_bias
-
- def utcoffset(self, dt):
- "Calculates the utcoffset according to the datetime.tzinfo spec"
- if dt is None:
- return
- winInfo = self.getWinInfo(dt.year)
- return -winInfo.bias + self.dst(dt)
-
- def dst(self, dt):
- """
- Calculate the daylight savings offset according to the
- datetime.tzinfo spec.
- """
- if dt is None:
- return
- winInfo = self.getWinInfo(dt.year)
- if not self.fixedStandardTime and self._inDaylightSavings(dt):
- result = winInfo.daylight_bias
- else:
- result = winInfo.standard_bias
- return -result
-
- def _inDaylightSavings(self, dt):
- dt = dt.replace(tzinfo=None)
- winInfo = self.getWinInfo(dt.year)
- try:
- dstStart = self.GetDSTStartTime(dt.year)
- dstEnd = self.GetDSTEndTime(dt.year)
-
- # at the end of DST, when clocks are moved back, there's a period
- # of daylight_bias where it's ambiguous whether we're in DST or
- # not.
- dstEndAdj = dstEnd + winInfo.daylight_bias
-
- # the same thing could theoretically happen at the start of DST
- # if there's a standard_bias (which I suspect is always 0).
- dstStartAdj = dstStart + winInfo.standard_bias
-
- if dstStart < dstEnd:
- in_dst = dstStartAdj <= dt < dstEndAdj
- else:
- # in the southern hemisphere, daylight savings time
- # typically ends before it begins in a given year.
- in_dst = not (dstEndAdj < dt <= dstStartAdj)
- except ValueError:
- # there was an error parsing the time zone, which is normal when a
- # start and end time are not specified.
- in_dst = False
-
- return in_dst
-
- def GetDSTStartTime(self, year):
- "Given a year, determines the time when daylight savings time starts"
- return self.getWinInfo(year).locate_daylight_start(year)
-
- def GetDSTEndTime(self, year):
- "Given a year, determines the time when daylight savings ends."
- return self.getWinInfo(year).locate_standard_start(year)
-
- def __cmp__(self, other):
- return cmp(self.__dict__, other.__dict__)
-
- def __eq__(self, other):
- return self.__dict__ == other.__dict__
-
- def __ne__(self, other):
- return self.__dict__ != other.__dict__
-
- @classmethod
- def local(class_):
- """Returns the local time zone as defined by the operating system in the
- registry.
- >>> localTZ = TimeZoneInfo.local()
- >>> now_local = datetime.datetime.now(localTZ)
- >>> now_UTC = datetime.datetime.utcnow()
- >>> (now_UTC - now_local) < datetime.timedelta(seconds = 5)
- Traceback (most recent call last):
- ...
- TypeError: can't subtract offset-naive and offset-aware datetimes
-
- >>> now_UTC = now_UTC.replace(tzinfo = TimeZoneInfo('GMT Standard Time', True))
-
- Now one can compare the results of the two offset aware values
- >>> (now_UTC - now_local) < datetime.timedelta(seconds = 5)
- True
- """
- code, info = TimeZoneDefinition.current()
- # code is 0 if daylight savings is disabled or not defined
- # code is 1 or 2 if daylight savings is enabled, 2 if currently active
- fix_standard_time = not code
- # note that although the given information is sufficient
- # to construct a WinTZI object, it's
- # not sufficient to represent the time zone in which
- # the current user is operating due
- # to dynamic time zones.
- return class_(info, fix_standard_time)
-
- @classmethod
- def utc(class_):
- """Returns a time-zone representing UTC.
-
- Same as TimeZoneInfo('GMT Standard Time', True) but caches the result
- for performance.
-
- >>> isinstance(TimeZoneInfo.utc(), TimeZoneInfo)
- True
- """
- if "_tzutc" not in class_.__dict__:
- setattr(class_, "_tzutc", class_("GMT Standard Time", True))
- return class_._tzutc
-
- # helper methods for accessing the timezone info from the registry
- @staticmethod
- def _get_time_zone_key(subkey=None):
- "Return the registry key that stores time zone details"
- key = _RegKeyDict.open(winreg.HKEY_LOCAL_MACHINE, TimeZoneInfo.tzRegKey)
- if subkey:
- key = key.subkey(subkey)
- return key
-
- @staticmethod
- def _get_time_zone_key_names():
- "Returns the names of the (registry keys of the) time zones"
- return TimeZoneInfo._get_time_zone_key().subkeys()
-
- @staticmethod
- def _get_indexed_time_zone_keys(index_key="Index"):
- """
- Get the names of the registry keys indexed by a value in that key,
- ignoring any keys for which that value is empty or missing.
- """
- key_names = list(TimeZoneInfo._get_time_zone_key_names())
-
- def get_index_value(key_name):
- key = TimeZoneInfo._get_time_zone_key(key_name)
- return key.get(index_key)
-
- values = map(get_index_value, key_names)
-
- return (
- (value, key_name) for value, key_name in zip(values, key_names) if value
- )
-
- @staticmethod
- def get_sorted_time_zone_names():
- """
- Return a list of time zone names that can
- be used to initialize TimeZoneInfo instances.
- """
- tzs = TimeZoneInfo.get_sorted_time_zones()
- return [tz.standardName for tz in tzs]
-
- @staticmethod
- def get_all_time_zones():
- return [TimeZoneInfo(n) for n in TimeZoneInfo._get_time_zone_key_names()]
-
- @staticmethod
- def get_sorted_time_zones(key=None):
- """
- Return the time zones sorted by some key.
- key must be a function that takes a TimeZoneInfo object and returns
- a value suitable for sorting on.
- The key defaults to the bias (descending), as is done in Windows
- (see http://blogs.msdn.com/michkap/archive/2006/12/22/1350684.aspx)
- """
- key = key or (lambda tzi: -tzi.staticInfo.bias)
- zones = TimeZoneInfo.get_all_time_zones()
- zones.sort(key=key)
- return zones
-
-
- class _RegKeyDict(dict):
- def __init__(self, key):
- dict.__init__(self)
- self.key = key
- self.__load_values()
-
- @classmethod
- def open(cls, *args, **kargs):
- return _RegKeyDict(winreg.OpenKeyEx(*args, **kargs))
-
- def subkey(self, name):
- return _RegKeyDict(winreg.OpenKeyEx(self.key, name))
-
- def __load_values(self):
- pairs = [(n, v) for (n, v, t) in self._enumerate_reg_values(self.key)]
- self.update(pairs)
-
- def subkeys(self):
- return self._enumerate_reg_keys(self.key)
-
- @staticmethod
- def _enumerate_reg_values(key):
- return _RegKeyDict._enumerate_reg(key, winreg.EnumValue)
-
- @staticmethod
- def _enumerate_reg_keys(key):
- return _RegKeyDict._enumerate_reg(key, winreg.EnumKey)
-
- @staticmethod
- def _enumerate_reg(key, func):
- "Enumerates an open registry key as an iterable generator"
- try:
- for index in count():
- yield func(key, index)
- except WindowsError:
- pass
-
-
- def utcnow():
- """
- Return the UTC time now with timezone awareness as enabled
- by this module
- >>> now = utcnow()
- """
- now = datetime.datetime.utcnow()
- now = now.replace(tzinfo=TimeZoneInfo.utc())
- return now
-
-
- def now():
- """
- Return the local time now with timezone awareness as enabled
- by this module
- >>> now_local = now()
- """
- return datetime.datetime.now(TimeZoneInfo.local())
-
-
- def GetTZCapabilities():
- """
- Run a few known tests to determine the capabilities of
- the time zone database on this machine.
- Note Dynamic Time Zone support is not available on any
- platform at this time; this
- is a limitation of this library, not the platform."""
- tzi = TimeZoneInfo("Mountain Standard Time")
- MissingTZPatch = datetime.datetime(2007, 11, 2, tzinfo=tzi).utctimetuple() != (
- 2007,
- 11,
- 2,
- 6,
- 0,
- 0,
- 4,
- 306,
- 0,
- )
- DynamicTZSupport = not MissingTZPatch and datetime.datetime(
- 2003, 11, 2, tzinfo=tzi
- ).utctimetuple() == (2003, 11, 2, 7, 0, 0, 6, 306, 0)
- del tzi
- return locals()
-
-
- class DLLHandleCache(object):
- def __init__(self):
- self.__cache = {}
-
- def __getitem__(self, filename):
- key = filename.lower()
- return self.__cache.setdefault(key, win32api.LoadLibrary(key))
-
-
- DLLCache = DLLHandleCache()
-
-
- def resolveMUITimeZone(spec):
- """Resolve a multilingual user interface resource for the time zone name
- >>> #some pre-amble for the doc-tests to be py2k and py3k aware)
- >>> try: unicode and None
- ... except NameError: unicode=str
- ...
- >>> import sys
- >>> result = resolveMUITimeZone('@tzres.dll,-110')
- >>> expectedResultType = [type(None),unicode][sys.getwindowsversion() >= (6,)]
- >>> type(result) is expectedResultType
- True
-
- spec should be of the format @path,-stringID[;comment]
- see http://msdn2.microsoft.com/en-us/library/ms725481.aspx for details
- """
- pattern = re.compile(r"@(?P<dllname>.*),-(?P<index>\d+)(?:;(?P<comment>.*))?")
- matcher = pattern.match(spec)
- assert matcher, "Could not parse MUI spec"
-
- try:
- handle = DLLCache[matcher.groupdict()["dllname"]]
- result = win32api.LoadString(handle, int(matcher.groupdict()["index"]))
- except win32api.error:
- result = None
- return result
-
-
- # from jaraco.util.dictlib 5.3.1
- class RangeMap(dict):
- """
- A dictionary-like object that uses the keys as bounds for a range.
- Inclusion of the value for that range is determined by the
- key_match_comparator, which defaults to less-than-or-equal.
- A value is returned for a key if it is the first key that matches in
- the sorted list of keys.
-
- One may supply keyword parameters to be passed to the sort function used
- to sort keys (i.e. cmp [python 2 only], keys, reverse) as sort_params.
-
- Let's create a map that maps 1-3 -> 'a', 4-6 -> 'b'
- >>> r = RangeMap({3: 'a', 6: 'b'}) # boy, that was easy
- >>> r[1], r[2], r[3], r[4], r[5], r[6]
- ('a', 'a', 'a', 'b', 'b', 'b')
-
- Even float values should work so long as the comparison operator
- supports it.
- >>> r[4.5]
- 'b'
-
- But you'll notice that the way rangemap is defined, it must be open-ended on one side.
- >>> r[0]
- 'a'
- >>> r[-1]
- 'a'
-
- One can close the open-end of the RangeMap by using undefined_value
- >>> r = RangeMap({0: RangeMap.undefined_value, 3: 'a', 6: 'b'})
- >>> r[0]
- Traceback (most recent call last):
- ...
- KeyError: 0
-
- One can get the first or last elements in the range by using RangeMap.Item
- >>> last_item = RangeMap.Item(-1)
- >>> r[last_item]
- 'b'
-
- .last_item is a shortcut for Item(-1)
- >>> r[RangeMap.last_item]
- 'b'
-
- Sometimes it's useful to find the bounds for a RangeMap
- >>> r.bounds()
- (0, 6)
-
- RangeMap supports .get(key, default)
- >>> r.get(0, 'not found')
- 'not found'
-
- >>> r.get(7, 'not found')
- 'not found'
-
- """
-
- def __init__(self, source, sort_params={}, key_match_comparator=operator.le):
- dict.__init__(self, source)
- self.sort_params = sort_params
- self.match = key_match_comparator
-
- def __getitem__(self, item):
- sorted_keys = sorted(list(self.keys()), **self.sort_params)
- if isinstance(item, RangeMap.Item):
- result = self.__getitem__(sorted_keys[item])
- else:
- key = self._find_first_match_(sorted_keys, item)
- result = dict.__getitem__(self, key)
- if result is RangeMap.undefined_value:
- raise KeyError(key)
- return result
-
- def get(self, key, default=None):
- """
- Return the value for key if key is in the dictionary, else default.
- If default is not given, it defaults to None, so that this method
- never raises a KeyError.
- """
- try:
- return self[key]
- except KeyError:
- return default
-
- def _find_first_match_(self, keys, item):
- def is_match(k):
- return self.match(item, k)
-
- matches = list(filter(is_match, keys))
- if matches:
- return matches[0]
- raise KeyError(item)
-
- def bounds(self):
- sorted_keys = sorted(list(self.keys()), **self.sort_params)
- return (
- sorted_keys[RangeMap.first_item],
- sorted_keys[RangeMap.last_item],
- )
-
- # some special values for the RangeMap
- undefined_value = type(str("RangeValueUndefined"), (object,), {})()
-
- class Item(int):
- pass
-
- first_item = Item(0)
- last_item = Item(-1)
|