mirror of
https://github.com/clinton-hall/nzbToMedia.git
synced 2025-08-21 13:53:15 -07:00
add jaraco to test sym links on Windows. Fixes #894
This commit is contained in:
parent
fd5ee775e0
commit
52cc753881
50 changed files with 3862 additions and 7 deletions
242
libs/jaraco/windows/timezone.py
Normal file
242
libs/jaraco/windows/timezone.py
Normal file
|
@ -0,0 +1,242 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import operator
|
||||
import ctypes
|
||||
import datetime
|
||||
from ctypes.wintypes import WORD, WCHAR, BOOL, LONG
|
||||
|
||||
from jaraco.windows.util import Extended
|
||||
from jaraco.collections import RangeMap
|
||||
|
||||
class AnyDict(object):
|
||||
"A dictionary that returns the same value regardless of key"
|
||||
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.value
|
||||
|
||||
class SYSTEMTIME(Extended, ctypes.Structure):
|
||||
_fields_ = [
|
||||
('year', WORD),
|
||||
('month', WORD),
|
||||
('day_of_week', WORD),
|
||||
('day', WORD),
|
||||
('hour', WORD),
|
||||
('minute', WORD),
|
||||
('second', WORD),
|
||||
('millisecond', WORD),
|
||||
]
|
||||
|
||||
class REG_TZI_FORMAT(Extended, ctypes.Structure):
|
||||
_fields_ = [
|
||||
('bias', LONG),
|
||||
('standard_bias', LONG),
|
||||
('daylight_bias', LONG),
|
||||
('standard_start', SYSTEMTIME),
|
||||
('daylight_start', SYSTEMTIME),
|
||||
]
|
||||
|
||||
class TIME_ZONE_INFORMATION(Extended, ctypes.Structure):
|
||||
_fields_ = [
|
||||
('bias', LONG),
|
||||
('standard_name', WCHAR*32),
|
||||
('standard_start', SYSTEMTIME),
|
||||
('standard_bias', LONG),
|
||||
('daylight_name', WCHAR*32),
|
||||
('daylight_start', SYSTEMTIME),
|
||||
('daylight_bias', LONG),
|
||||
]
|
||||
|
||||
class DYNAMIC_TIME_ZONE_INFORMATION(TIME_ZONE_INFORMATION):
|
||||
"""
|
||||
Because the structure of the DYNAMIC_TIME_ZONE_INFORMATION extends
|
||||
the structure of the TIME_ZONE_INFORMATION, this structure
|
||||
can be used as a drop-in replacement for calls where the
|
||||
structure is passed by reference.
|
||||
|
||||
For example,
|
||||
dynamic_tzi = DYNAMIC_TIME_ZONE_INFORMATION()
|
||||
ctypes.windll.kernel32.GetTimeZoneInformation(ctypes.byref(dynamic_tzi))
|
||||
|
||||
(although the key_name and dynamic_daylight_time_disabled flags will be
|
||||
set to the default (null)).
|
||||
|
||||
>>> isinstance(DYNAMIC_TIME_ZONE_INFORMATION(), TIME_ZONE_INFORMATION)
|
||||
True
|
||||
|
||||
|
||||
"""
|
||||
_fields_ = [
|
||||
# ctypes automatically includes the fields from the parent
|
||||
('key_name', WCHAR*128),
|
||||
('dynamic_daylight_time_disabled', BOOL),
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Allow initialization from args from both this class and
|
||||
its superclass. Default ctypes implementation seems to
|
||||
assume that this class is only initialized with its own
|
||||
_fields_ (for non-keyword-args)."""
|
||||
super_self = super(DYNAMIC_TIME_ZONE_INFORMATION, self)
|
||||
super_fields = super_self._fields_
|
||||
super_args = args[:len(super_fields)]
|
||||
self_args = args[len(super_fields):]
|
||||
# convert the super args to keyword args so they're also handled
|
||||
for field, arg in zip(super_fields, super_args):
|
||||
field_name, spec = field
|
||||
kwargs[field_name] = arg
|
||||
super(DYNAMIC_TIME_ZONE_INFORMATION, self).__init__(*self_args, **kwargs)
|
||||
|
||||
class Info(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 field_names(self):
|
||||
return map(operator.itemgetter(0), self._fields_)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Try to construct a timezone.Info from
|
||||
a) [DYNAMIC_]TIME_ZONE_INFORMATION args
|
||||
b) another Info
|
||||
c) a REG_TZI_FORMAT
|
||||
d) a byte structure
|
||||
"""
|
||||
funcs = (
|
||||
super(Info, self).__init__,
|
||||
self.__init_from_other,
|
||||
self.__init_from_reg_tzi,
|
||||
self.__init_from_bytes,
|
||||
)
|
||||
for func in funcs:
|
||||
try:
|
||||
func(*args, **kwargs)
|
||||
return
|
||||
except TypeError:
|
||||
pass
|
||||
raise TypeError("Invalid arguments for %s" % self.__class__)
|
||||
|
||||
def __init_from_bytes(self, bytes, **kwargs):
|
||||
reg_tzi = REG_TZI_FORMAT()
|
||||
# todo: use buffer API in Python 3
|
||||
buffer = buffer(bytes)
|
||||
ctypes.memmove(ctypes.addressof(reg_tzi), buffer, len(buffer))
|
||||
self.__init_from_reg_tzi(self, reg_tzi, **kwargs)
|
||||
|
||||
def __init_from_reg_tzi(self, reg_tzi, **kwargs):
|
||||
if not isinstance(reg_tzi, REG_TZI_FORMAT):
|
||||
raise TypeError("Not a REG_TZI_FORMAT")
|
||||
for field_name, type in reg_tzi._fields_:
|
||||
setattr(self, field_name, getattr(reg_tzi, field_name))
|
||||
for name, value in kwargs.items():
|
||||
setattr(self, name, value)
|
||||
|
||||
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(Info, 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(Info, self).__getattribute__(attr)
|
||||
make_minute_timedelta = lambda m: datetime.timedelta(minutes = m)
|
||||
if 'bias' in attr:
|
||||
value = make_minute_timedelta(value)
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def current(class_):
|
||||
"Windows Platform SDK GetTimeZoneInformation"
|
||||
tzi = class_()
|
||||
kernel32 = ctypes.windll.kernel32
|
||||
getter = kernel32.GetTimeZoneInformation
|
||||
getter = getattr(kernel32, 'GetDynamicTimeZoneInformation', getter)
|
||||
code = getter(ctypes.byref(tzi))
|
||||
return code, tzi
|
||||
|
||||
def set(self):
|
||||
kernel32 = ctypes.windll.kernel32
|
||||
setter = kernel32.SetTimeZoneInformation
|
||||
setter = getattr(kernel32, 'SetDynamicTimeZoneInformation', setter)
|
||||
return setter(ctypes.byref(self))
|
||||
|
||||
def copy(self):
|
||||
return self.__class__(self)
|
||||
|
||||
def locate_daylight_start(self, year):
|
||||
info = self.get_info_for_year(year)
|
||||
return self._locate_day(year, info.daylight_start)
|
||||
|
||||
def locate_standard_start(self, year):
|
||||
info = self.get_info_for_year(year)
|
||||
return self._locate_day(year, info.standard_start)
|
||||
|
||||
def get_info_for_year(self, year):
|
||||
return self.dynamic_info[year]
|
||||
|
||||
@property
|
||||
def dynamic_info(self):
|
||||
"Return a map that for a given year will return the correct Info"
|
||||
if self.key_name:
|
||||
dyn_key = self.get_key().subkey('Dynamic DST')
|
||||
del dyn_key['FirstEntry']
|
||||
del dyn_key['LastEntry']
|
||||
years = map(int, dyn_key.keys())
|
||||
values = map(Info, dyn_key.values())
|
||||
# create a range mapping that searches by descending year and matches
|
||||
# if the target year is greater or equal.
|
||||
return RangeMap(zip(years, values), RangeMap.descending, operator.ge)
|
||||
else:
|
||||
return AnyDict(self)
|
||||
|
||||
@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)
|
||||
>>> Info._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
|
Loading…
Add table
Add a link
Reference in a new issue