]> git.ipfire.org Git - thirdparty/babel.git/commitdiff
Started support for local times in babel
authorArmin Ronacher <armin.ronacher@active-4.com>
Sat, 6 Jul 2013 11:36:47 +0000 (13:36 +0200)
committerArmin Ronacher <armin.ronacher@active-4.com>
Sat, 6 Jul 2013 11:36:47 +0000 (13:36 +0200)
babel/localtime/__init__.py [new file with mode: 0644]
babel/localtime/_unix.py [new file with mode: 0644]
babel/localtime/_win32.py [new file with mode: 0644]
babel/util.py
scripts/import_cldr.py

diff --git a/babel/localtime/__init__.py b/babel/localtime/__init__.py
new file mode 100644 (file)
index 0000000..e2a45d5
--- /dev/null
@@ -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 (file)
index 0000000..b002c18
--- /dev/null
@@ -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 (file)
index 0000000..03f9e36
--- /dev/null
@@ -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())
index fe202d777d9a52a4ca6978e2d59b08887a63bf89..d392d1ca02d21e3c7eab7434babeb61521846b94 100644 (file)
@@ -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
index 60aeaceade698c2d534388e853627d7e3e737ef0..1ebb06bebd49104ab74a0082bfdbf233099f421b 100755 (executable)
@@ -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']