From: Armin Ronacher Date: Sat, 6 Jul 2013 11:36:47 +0000 (+0200) Subject: Started support for local times in babel X-Git-Tag: 1.0~113 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3b99fab5b3d4d406e3c37bad66fdf14caaae9ce5;p=thirdparty%2Fbabel.git Started support for local times in babel --- diff --git a/babel/localtime/__init__.py b/babel/localtime/__init__.py new file mode 100644 index 00000000..e2a45d5f --- /dev/null +++ b/babel/localtime/__init__.py @@ -0,0 +1,60 @@ +import sys +import pytz +import time +from datetime import timedelta, datetime +from datetime import tzinfo +from threading import RLock + +if sys.platform == 'win32': + from babel.localtime._win32 import _get_localzone +else: + from babel.localtime._unix import _get_localzone + + +_cached_tz = None +_cache_lock = RLock() + +STDOFFSET = timedelta(seconds = -time.timezone) +if time.daylight: + DSTOFFSET = timedelta(seconds = -time.altzone) +else: + DSTOFFSET = STDOFFSET + +DSTDIFF = DSTOFFSET - STDOFFSET +ZERO = timedelta(0) + + +class _FallbackLocalTimezone(tzinfo): + + def utcoffset(self, dt): + if self._isdst(dt): + return DSTOFFSET + else: + return STDOFFSET + + def dst(self, dt): + if self._isdst(dt): + return DSTDIFF + else: + return ZERO + + def tzname(self, dt): + return time.tzname[self._isdst(dt)] + + def _isdst(self, dt): + tt = (dt.year, dt.month, dt.day, + dt.hour, dt.minute, dt.second, + dt.weekday(), 0, -1) + stamp = time.mktime(tt) + tt = time.localtime(stamp) + return tt.tm_isdst > 0 + +def get_localzone(): + """Returns the current underlying local timezone object. + Generally this function does not need to be used, it's a + better idea to use the :data:`LOCALTZ` singleton instead. + """ + return _get_localzone() + + +LOCALTZ = get_localzone() diff --git a/babel/localtime/_unix.py b/babel/localtime/_unix.py new file mode 100644 index 00000000..b002c18e --- /dev/null +++ b/babel/localtime/_unix.py @@ -0,0 +1,111 @@ +from __future__ import with_statement +import os +import re +import pytz + + +def _tz_from_env(tzenv): + if tzenv[0] == ':': + tzenv = tzenv[1:] + + # TZ specifies a file + if os.path.exists(tzenv): + with open(tzenv, 'rb') as tzfile: + return pytz.tzfile.build_tzinfo('local', tzfile) + + # TZ specifies a zoneinfo zone. + try: + tz = pytz.timezone(tzenv) + # That worked, so we return this: + return tz + except pytz.UnknownTimeZoneError: + raise pytz.UnknownTimeZoneError( + "tzlocal() does not support non-zoneinfo timezones like %s. \n" + "Please use a timezone in the form of Continent/City") + +def _get_localzone(_root='/'): + """Tries to find the local timezone configuration. + This method prefers finding the timezone name and passing that to pytz, + over passing in the localtime file, as in the later case the zoneinfo + name is unknown. + The parameter _root makes the function look for files like /etc/localtime + beneath the _root directory. This is primarily used by the tests. + In normal usage you call the function without parameters.""" + + tzenv = os.environ.get('TZ') + if tzenv: + return _tz_from_env(tzenv) + + # This is actually a pretty reliable way to test for the local time + # zone on operating systems like OS X. On OS X especially this is the + # only one that actually works. + try: + link_dst = os.readlink('/etc/localtime') + except OSError: + pass + else: + pos = link_dst.find('/zoneinfo/') + if pos >= 0: + zone_name = link_dst[pos + 10:] + try: + return pytz.timezone(zone_name) + except pytz.UnknownTimeZoneError: + pass + + # Now look for distribution specific configuration files + # that contain the timezone name. + tzpath = os.path.join(_root, 'etc/timezone') + if os.path.exists(tzpath): + with open(tzpath, 'rb') as tzfile: + data = tzfile.read() + + # Issue #3 was that /etc/timezone was a zoneinfo file. + # That's a misconfiguration, but we need to handle it gracefully: + if data[:5] != 'TZif2': + etctz = data.strip().decode() + # Get rid of host definitions and comments: + if ' ' in etctz: + etctz, dummy = etctz.split(' ', 1) + if '#' in etctz: + etctz, dummy = etctz.split('#', 1) + return pytz.timezone(etctz.replace(' ', '_')) + + # CentOS has a ZONE setting in /etc/sysconfig/clock, + # OpenSUSE has a TIMEZONE setting in /etc/sysconfig/clock and + # Gentoo has a TIMEZONE setting in /etc/conf.d/clock + # We look through these files for a timezone: + zone_re = re.compile('\s*ZONE\s*=\s*\"') + timezone_re = re.compile('\s*TIMEZONE\s*=\s*\"') + end_re = re.compile('\"') + + for filename in ('etc/sysconfig/clock', 'etc/conf.d/clock'): + tzpath = os.path.join(_root, filename) + if not os.path.exists(tzpath): + continue + with open(tzpath, 'rt') as tzfile: + data = tzfile.readlines() + + for line in data: + # Look for the ZONE= setting. + match = zone_re.match(line) + if match is None: + # No ZONE= setting. Look for the TIMEZONE= setting. + match = timezone_re.match(line) + if match is not None: + # Some setting existed + line = line[match.end():] + etctz = line[:end_re.search(line).start()] + + # We found a timezone + return pytz.timezone(etctz.replace(' ', '_')) + + # No explicit setting existed. Use localtime + for filename in ('etc/localtime', 'usr/local/etc/localtime'): + tzpath = os.path.join(_root, filename) + + if not os.path.exists(tzpath): + continue + with open(tzpath, 'rb') as tzfile: + return pytz.tzfile.build_tzinfo('local', tzfile) + + raise pytz.UnknownTimeZoneError('Can not find any timezone configuration') diff --git a/babel/localtime/_win32.py b/babel/localtime/_win32.py new file mode 100644 index 00000000..03f9e363 --- /dev/null +++ b/babel/localtime/_win32.py @@ -0,0 +1,79 @@ +try: + import _winreg as winreg +except ImportError: + import winreg + +from tzlocal.windows_tz import tz_names +import pytz + + +def valuestodict(key): + """Convert a registry key's values to a dictionary.""" + dict = {} + size = winreg.QueryInfoKey(key)[1] + for i in range(size): + data = winreg.EnumValue(key, i) + dict[data[0]] = data[1] + return dict + +def get_localzone_name(): + # Windows is special. It has unique time zone names (in several + # meanings of the word) available, but unfortunately, they can be + # translated to the language of the operating system, so we need to + # do a backwards lookup, by going through all time zones and see which + # one matches. + handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) + + TZLOCALKEYNAME = r'SYSTEM\CurrentControlSet\Control\TimeZoneInformation' + localtz = winreg.OpenKey(handle, TZLOCALKEYNAME) + keyvalues = valuestodict(localtz) + localtz.Close() + if 'TimeZoneKeyName' in keyvalues: + # Windows 7 (and Vista?) + + # For some reason this returns a string with loads of NUL bytes at + # least on some systems. I don't know if this is a bug somewhere, I + # just work around it. + tzkeyname = keyvalues['TimeZoneKeyName'].split('\x00', 1)[0] + else: + # Windows 2000 or XP + + # This is the localized name: + tzwin = keyvalues['StandardName'] + + # Open the list of timezones to look up the real name: + TZKEYNAME = r'SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones' + tzkey = winreg.OpenKey(handle, TZKEYNAME) + + # Now, match this value to Time Zone information + tzkeyname = None + for i in range(winreg.QueryInfoKey(tzkey)[0]): + subkey = winreg.EnumKey(tzkey, i) + sub = winreg.OpenKey(tzkey, subkey) + data = valuestodict(sub) + sub.Close() + if data['Std'] == tzwin: + tzkeyname = subkey + break + + tzkey.Close() + handle.Close() + + if tzkeyname is None: + raise LookupError('Can not find Windows timezone configuration') + + timezone = tz_names.get(tzkeyname) + if timezone is None: + # Nope, that didn't work. Try adding 'Standard Time', + # it seems to work a lot of times: + timezone = tz_names.get(tzkeyname + ' Standard Time') + + # Return what we have. + if timezone is None: + raise pytz.UnknownTimeZoneError('Can not find timezone ' + tzkeyname) + + return timezone + + +def _get_localzone(): + return pytz.timezone(get_localzone_name()) diff --git a/babel/util.py b/babel/util.py index fe202d77..d392d1ca 100644 --- a/babel/util.py +++ b/babel/util.py @@ -18,7 +18,6 @@ from datetime import timedelta, tzinfo import os import re import textwrap -import time from itertools import izip, imap missing = object() @@ -266,8 +265,6 @@ except AttributeError: rel_list = [os.path.pardir] * (len(start_list) - i) + path_list[i:] return os.path.join(*rel_list) -ZERO = timedelta(0) - class FixedOffsetTimezone(tzinfo): """Fixed offset in minutes east from UTC.""" @@ -295,46 +292,15 @@ class FixedOffsetTimezone(tzinfo): import pytz as _pytz +from babel import localtime +# Export the localtime functionality here because that's +# where it was in the past. UTC = _pytz.utc +LOCALTZ = localtime.LOCALTZ +get_localzone = localtime.get_localzone -STDOFFSET = timedelta(seconds = -time.timezone) -if time.daylight: - DSTOFFSET = timedelta(seconds = -time.altzone) -else: - DSTOFFSET = STDOFFSET - -DSTDIFF = DSTOFFSET - STDOFFSET - - -class LocalTimezone(tzinfo): - - def utcoffset(self, dt): - if self._isdst(dt): - return DSTOFFSET - else: - return STDOFFSET - - def dst(self, dt): - if self._isdst(dt): - return DSTDIFF - else: - return ZERO - - def tzname(self, dt): - return time.tzname[self._isdst(dt)] - - def _isdst(self, dt): - tt = (dt.year, dt.month, dt.day, - dt.hour, dt.minute, dt.second, - dt.weekday(), 0, -1) - stamp = time.mktime(tt) - tt = time.localtime(stamp) - return tt.tm_isdst > 0 - - -LOCALTZ = LocalTimezone() -"""`tzinfo` object for local time-zone. - -:type: `tzinfo` -""" +STDOFFSET = localtime.STDOFFSET +DSTOFFSET = localtime.DSTOFFSET +DSTDIFF = localtime.DSTDIFF +ZERO = localtime.ZERO diff --git a/scripts/import_cldr.py b/scripts/import_cldr.py index 60aeacea..1ebb06be 100755 --- a/scripts/import_cldr.py +++ b/scripts/import_cldr.py @@ -114,6 +114,7 @@ def main(): territory_zones = global_data.setdefault('territory_zones', {}) zone_aliases = global_data.setdefault('zone_aliases', {}) zone_territories = global_data.setdefault('zone_territories', {}) + win_mapping = global_data.setdefault('windows_zone_mapping', {}) # create auxiliary zone->territory map from the windows zones (we don't set # the 'zones_territories' map directly here, because there are some zones @@ -121,6 +122,8 @@ def main(): # 'bcp47' data _zone_territory_map = {} for map_zone in sup_windows_zones.findall('.//windowsZones/mapTimezones/mapZone'): + if map_zone.attrib.get('territory') == '001': + win_mapping[map_zone.attrib['other']] = map_zone.attrib['type'].split()[0] for tzid in map_zone.attrib['type'].split(): _zone_territory_map[tzid] = map_zone.attrib['territory']