diff babel/dates.py @ 233:bc22f5aef216

Upgraded to CLDR 1.5 and improved timezone formatting.
author cmlenz
date Wed, 01 Aug 2007 12:32:20 +0000
parents 898b9450f274
children ef9f9fec0252
line wrap: on
line diff
--- a/babel/dates.py
+++ b/babel/dates.py
@@ -24,11 +24,11 @@
 from datetime import date, datetime, time, timedelta, tzinfo
 import re
 
-from babel.core import default_locale, Locale
+from babel.core import default_locale, get_global, Locale
 from babel.util import UTC
 
-__all__ = ['format_date', 'format_datetime', 'format_time', 'parse_date',
-           'parse_datetime', 'parse_time']
+__all__ = ['format_date', 'format_datetime', 'format_time',
+           'get_timezone_name', 'parse_date', 'parse_datetime', 'parse_time']
 __docformat__ = 'restructuredtext en'
 
 LC_TIME = default_locale('LC_TIME')
@@ -110,7 +110,7 @@
     >>> get_era_names('abbreviated', locale='de_DE')[1]
     u'n. Chr.'
     
-    :param width: the width to use, either "wide" or "abbreviated"
+    :param width: the width to use, either "wide", "abbreviated", or "narrow"
     :param locale: the `Locale` object, or a locale string
     :return: the dictionary of era names
     :rtype: `dict`
@@ -159,7 +159,7 @@
     >>> get_time_format(locale='en_US')
     <DateTimePattern u'h:mm:ss a'>
     >>> get_time_format('full', locale='de_DE')
-    <DateTimePattern u"H:mm' Uhr 'z">
+    <DateTimePattern u'HH:mm:ss v'>
     
     :param format: the format to use, one of "full", "long", "medium", or
                    "short"
@@ -169,6 +169,249 @@
     """
     return Locale.parse(locale).time_formats[format]
 
+def get_timezone_gmt(datetime=None, width='long', locale=LC_TIME):
+    """Return the timezone associated with the given `datetime` object formatted
+    as string indicating the offset from GMT.
+    
+    >>> dt = datetime(2007, 4, 1, 15, 30)
+    >>> get_timezone_gmt(dt)
+    u'GMT+00:00'
+    
+    >>> from pytz import timezone
+    >>> tz = timezone('America/Los_Angeles')
+    >>> dt = datetime(2007, 4, 1, 15, 30, tzinfo=tz)
+    >>> get_timezone_gmt(dt)
+    u'GMT-08:00'
+    >>> get_timezone_gmt(dt, 'short')
+    u'-0800'
+    
+    The long format depends on the locale, for example in France a different
+    string is used for GMT:
+    
+    >>> get_timezone_gmt(dt, 'long', locale='fr_FR')
+    u'HMG-08:00'
+    
+    :param dt: the ``datetime`` object; if `None`, the current date and time are
+               used
+    :param width: either "long" or "short"
+    :param locale: the `Locale` object, or a locale string
+    :return: the GMT offset representation of the timezone
+    :rtype: `unicode`
+    :since: version 0.9
+    """
+    if datetime is None:
+        datetime = datetime_.now()
+    elif isinstance(datetime, (int, long)):
+        datetime = datetime_.fromtimestamp(datetime).time()
+    if datetime.tzinfo is None:
+        datetime = datetime.replace(tzinfo=UTC)
+    locale = Locale.parse(locale)
+
+    offset = datetime.utcoffset()
+    seconds = offset.days * 24 * 60 * 60 + offset.seconds
+    hours, seconds = divmod(seconds, 3600)
+    if width == 'short':
+        pattern = u'%+03d%02d'
+    else:
+        pattern = locale.zone_formats['gmt'] % '%+03d:%02d'
+    return pattern % (hours, seconds // 60)
+
+def get_timezone_location(dt_or_tzinfo=None, locale=LC_TIME):
+    """Return a representation of the given timezone using "location format".
+    
+    The result depends on both the local display name of the country and the
+    city assocaited with the time zone:
+    
+    >>> from pytz import timezone
+    >>> tz = timezone('America/St_Johns')
+    >>> get_timezone_location(tz, locale='de_DE')
+    u"Kanada (St. John's)"
+    >>> tz = timezone('America/Mexico_City')
+    >>> get_timezone_location(tz, locale='de_DE')
+    u'Mexiko (Mexiko-Stadt)'
+    
+    If the timezone is associated with a country that uses only a single
+    timezone, just the localized country name is returned:
+    
+    >>> tz = timezone('Europe/Berlin')
+    >>> get_timezone_name(tz, locale='de_DE')
+    u'Deutschland'
+    
+    :param dt_or_tzinfo: the ``datetime`` or ``tzinfo`` object that determines
+                         the timezone; if `None`, the current date and time in
+                         UTC is assumed
+    :param locale: the `Locale` object, or a locale string
+    :return: the localized timezone name using location format
+    :rtype: `unicode`
+    :since: version 0.9
+    """
+    if dt_or_tzinfo is None or isinstance(dt_or_tzinfo, (int, long)):
+        dt = None
+        tzinfo = UTC
+    elif isinstance(dt_or_tzinfo, (datetime, time)):
+        dt = dt_or_tzinfo
+        if dt.tzinfo is not None:
+            tzinfo = dt.tzinfo
+        else:
+            tzinfo = UTC
+    else:
+        dt = None
+        tzinfo = dt_or_tzinfo
+    locale = Locale.parse(locale)
+
+    if hasattr(tzinfo, 'zone'):
+        zone = tzinfo.zone
+    else:
+        zone = tzinfo.tzname(dt or datetime.utcnow())
+
+    # Get the canonical time-zone code
+    zone = get_global('zone_aliases').get(zone, zone)
+
+    metainfo = {}
+    info = locale.time_zones.get(zone, {})
+    if 'use_metazone' in info:
+        metainfo = locale.meta_zones.get(info['use_metazone'], {})
+
+    # Otherwise, if there is only one timezone for the country, return the
+    # localized country name
+    region_format = locale.zone_formats['region']
+    territory = get_global('zone_territories').get(zone)
+    territory_name = locale.territories[territory]
+    if territory and len(get_global('territory_zones')[territory]) == 1:
+        return region_format % (territory_name)
+
+    # Otherwise, include the city in the output
+    fallback_format = locale.zone_formats['fallback']
+    if 'city' in info:
+        city_name = info['city']
+    elif 'city' in metainfo:
+        city_name = metainfo['city']
+    elif '/' in zone:
+        city_name = zone.split('/', 1)[1].replace('_', ' ')
+    else:
+        city_name = zone.replace('_', ' ')
+
+    return region_format % (fallback_format % {
+        '0': city_name,
+        '1': territory_name
+    })
+
+def get_timezone_name(dt_or_tzinfo=None, width='long', uncommon=False,
+                      locale=LC_TIME):
+    r"""Return the localized display name for the given timezone. The timezone
+    may be specified using a ``datetime`` or `tzinfo` object.
+    
+    >>> from pytz import timezone
+    >>> dt = time(15, 30, tzinfo=timezone('America/Los_Angeles'))
+    >>> get_timezone_name(dt, locale='en_US')
+    u'Pacific Standard Time'
+    >>> get_timezone_name(dt, width='short', locale='en_US')
+    u'PST'
+    
+    If this function gets passed only a `tzinfo` object and no concrete
+    `datetime`,  the returned display name is indenpendent of daylight savings
+    time. This can be used for example for selecting timezones, or to set the
+    time of events that recur across DST changes:
+    
+    >>> tz = timezone('America/Los_Angeles')
+    >>> get_timezone_name(tz, locale='en_US')
+    u'Pacific Time'
+    >>> get_timezone_name(tz, 'short', locale='en_US')
+    u'PT'
+    
+    If no localized display name for the timezone is available, and the timezone
+    is associated with a country that uses only a single timezone, the name of
+    that country is returned, formatted according to the locale:
+    
+    >>> tz = timezone('Europe/Berlin')
+    >>> get_timezone_name(tz, locale='de_DE')
+    u'Deutschland'
+    >>> get_timezone_name(tz, locale='pt_BR')
+    u'Hor\xe1rio Alemanha'
+    
+    On the other hand, if the country uses multiple timezones, the city is also
+    included in the representation:
+    
+    >>> tz = timezone('America/St_Johns')
+    >>> get_timezone_name(tz, locale='de_DE')
+    u"Kanada (St. John's)"
+    
+    The `uncommon` parameter can be set to `True` to enable the use of timezone
+    representations that are not commonly used by the requested locale. For
+    example, while in frensh the central europian timezone is usually
+    abbreviated as "HEC", in Canadian frensh, this abbreviation is not in common
+    use, so a generic name would be chosen by default:
+    
+    >>> tz = timezone('Europe/Paris')
+    >>> get_timezone_name(tz, 'short', locale='fr_CA')
+    u'France'
+    >>> get_timezone_name(tz, 'short', uncommon=True, locale='fr_CA')
+    u'HEC'
+    
+    :param dt_or_tzinfo: the ``datetime`` or ``tzinfo`` object that determines
+                         the timezone; if a ``tzinfo`` object is used, the
+                         resulting display name will be generic, i.e.
+                         independent of daylight savings time; if `None`, the
+                         current date in UTC is assumed
+    :param width: either "long" or "short"
+    :param uncommon: whether even uncommon timezone abbreviations should be used
+    :param locale: the `Locale` object, or a locale string
+    :return: the timezone display name
+    :rtype: `unicode`
+    :since: version 0.9
+    :see:  `LDML Appendix J: Time Zone Display Names
+            <http://www.unicode.org/reports/tr35/#Time_Zone_Fallback>`_
+    """
+    if dt_or_tzinfo is None or isinstance(dt_or_tzinfo, (int, long)):
+        dt = None
+        tzinfo = UTC
+    elif isinstance(dt_or_tzinfo, (datetime, time)):
+        dt = dt_or_tzinfo
+        if dt.tzinfo is not None:
+            tzinfo = dt.tzinfo
+        else:
+            tzinfo = UTC
+    else:
+        dt = None
+        tzinfo = dt_or_tzinfo
+    locale = Locale.parse(locale)
+
+    if hasattr(tzinfo, 'zone'):
+        zone = tzinfo.zone
+    else:
+        zone = tzinfo.tzname(dt or datetime.utcnow())
+
+    # Get the canonical time-zone code
+    zone = get_global('zone_aliases').get(zone, zone)
+
+    metainfo = {}
+    info = locale.time_zones.get(zone, {})
+    # Try explicitly translated zone names first
+    if width in info:
+        if dt is None:
+            field = 'generic'
+        else:
+            field = tzinfo.dst(dt) and 'daylight' or 'standard'
+        if field in info[width]:
+            return info[width][field]
+
+    if 'use_metazone' in info:
+        metainfo = locale.meta_zones.get(info['use_metazone'], {})
+        if width in metainfo and (uncommon or metainfo.get('common')):
+            if dt is None:
+                field = 'generic'
+            else:
+                field = tzinfo.dst(dt) and 'daylight' or 'standard'
+            if field in metainfo[width]:
+                return metainfo[width][field]
+
+    # If we have a concrete datetime, we assume that the result can't be
+    # independent of daylight savings time, so we return the GMT offset
+    if dt is not None:
+        return get_timezone_gmt(time, width=width, locale=locale)
+
+    return get_timezone_location(dt_or_tzinfo, locale=locale)
+
 def format_date(date=None, format='medium', locale=LC_TIME):
     """Return a date formatted according to the given pattern.
     
@@ -219,9 +462,9 @@
     ``pytz`` package is needed to explicitly specify the time-zone:
     
     >>> from pytz import timezone
-    >>> format_datetime(dt, 'full', tzinfo=timezone('Europe/Berlin'),
-    ...                 locale='de_DE')
-    u'Sonntag, 1. April 2007 17:30 Uhr MESZ'
+    >>> format_datetime(dt, 'full', tzinfo=timezone('Europe/Paris'),
+    ...                 locale='fr_FR')
+    u'dimanche 1 avril 2007 17:30:00 HEC'
     >>> format_datetime(dt, "yyyy.MM.dd G 'at' HH:mm:ss zzz",
     ...                 tzinfo=timezone('US/Eastern'), locale='en')
     u'2007.04.01 AD at 11:30:00 EDT'
@@ -276,9 +519,9 @@
     
     >>> from pytz import timezone
     >>> t = time(15, 30)
-    >>> format_time(t, format='full', tzinfo=timezone('Europe/Berlin'),
-    ...             locale='de_DE')
-    u'17:30 Uhr MESZ'
+    >>> format_time(t, format='full', tzinfo=timezone('Europe/Paris'),
+    ...             locale='fr_FR')
+    u'17:30:00 HEC'
     >>> format_time(t, "hh 'o''clock' a, zzzz", tzinfo=timezone('US/Eastern'),
     ...             locale='en')
     u"11 o'clock AM, Eastern Daylight Time"
@@ -474,7 +717,7 @@
             return self.format_frac_seconds(num)
         elif char == 'A':
             return self.format_milliseconds_in_day(num)
-        elif char in ('z', 'Z', 'v'):
+        elif char in ('z', 'Z', 'v', 'V'):
             return self.format_timezone(char, num)
         else:
             raise KeyError('Unsupported date/time field %r' % char)
@@ -538,37 +781,19 @@
         return self.format(msecs, num)
 
     def format_timezone(self, char, num):
-        if char in ('z', 'v'):
-            if hasattr(self.value.tzinfo, 'zone'):
-                zone = self.value.tzinfo.zone
-            else:
-                zone = self.value.tzinfo.tzname(self.value)
-
-            # Get the canonical time-zone code
-            zone = self.locale.zone_aliases.get(zone, zone)
-
-            # Try explicitly translated zone names first
-            display = self.locale.time_zones.get(zone)
-            if display:
-                if 'long' in display:
-                    width = {3: 'short', 4: 'long'}[max(3, num)]
-                    if char == 'v':
-                        dst = 'generic'
-                    else:
-                        dst = self.value.dst() and 'daylight' or 'standard'
-                    return display[width][dst]
-                elif 'city' in display:
-                    return display['city']
-
-            else:
-                return zone.split('/', 1)[1]
-
+        width = {3: 'short', 4: 'long'}[max(3, num)]
+        if char == 'z':
+            return get_timezone_name(self.value, width, locale=self.locale)
         elif char == 'Z':
-            offset = self.value.utcoffset()
-            seconds = offset.days * 24 * 60 * 60 + offset.seconds
-            hours, seconds = divmod(seconds, 3600)
-            pattern = {3: '%+03d%02d', 4: 'GMT %+03d:%02d'}[max(3, num)]
-            return pattern % (hours, seconds // 60)
+            return get_timezone_gmt(self.value, width)
+        elif char == 'v':
+            return get_timezone_name(self.value.tzinfo, width,
+                                     locale=self.locale)
+        elif char == 'V':
+            if num == 1:
+                return get_timezone_name(self.value.tzinfo, width,
+                                         uncommon=True, locale=self.locale)
+            return get_timezone_location(self.value.tzinfo, locale=self.locale)
 
     def format(self, value, length):
         return ('%%0%dd' % length) % value
@@ -586,7 +811,7 @@
     'h': [1, 2], 'H': [1, 2], 'K': [1, 2], 'k': [1, 2],             # hour
     'm': [1, 2],                                                    # minute
     's': [1, 2], 'S': None, 'A': None,                              # second
-    'z': [1, 2, 3, 4], 'Z': [1, 2, 3, 4], 'v': [1, 4]               # zone
+    'z': [1, 2, 3, 4], 'Z': [1, 2, 3, 4], 'v': [1, 4], 'V': [1, 4]  # zone
 }
 
 def parse_pattern(pattern):
Copyright (C) 2012-2017 Edgewall Software