]> git.ipfire.org Git - thirdparty/babel.git/commitdiff
More work on timezones.
authorChristopher Lenz <cmlenz@gmail.com>
Mon, 4 Jun 2007 10:51:38 +0000 (10:51 +0000)
committerChristopher Lenz <cmlenz@gmail.com>
Mon, 4 Jun 2007 10:51:38 +0000 (10:51 +0000)
babel/dates.py
babel/tests/dates.py
babel/util.py
doc/formatting.txt

index e3e62e2a0f078084388cfd44a598772c22a910d9..4f3629bf2996f8383cd93e9f14f747cb5dd0caa2 100644 (file)
@@ -21,10 +21,10 @@ following environment variables, in that order:
  * ``LANG``
 """
 
-from datetime import date, datetime, time
+from datetime import date, datetime, time, timedelta, tzinfo
 
 from babel.core import Locale
-from babel.util import default_locale
+from babel.util import default_locale, UTC
 
 __all__ = ['format_date', 'format_datetime', 'format_time', 'parse_date',
            'parse_datetime', 'parse_time']
@@ -157,7 +157,7 @@ def format_date(date, format='medium', locale=LC_TIME):
     If you don't want to use the locale default formats, you can specify a
     custom date pattern:
     
-    >>> format_time(d, "EEE, MMM d, ''yy", locale='en')
+    >>> format_date(d, "EEE, MMM d, ''yy", locale='en')
     u"Sun, Apr 1, '07"
     
     :param date: the ``date`` or ``datetime`` object
@@ -179,12 +179,13 @@ def format_date(date, format='medium', locale=LC_TIME):
     pattern = parse_pattern(format)
     return parse_pattern(format).apply(date, locale)
 
-def format_datetime(datetime, format='medium', locale=LC_TIME):
+def format_datetime(datetime, format='medium', tzinfo=UTC, locale=LC_TIME):
     """Returns a date formatted according to the given pattern.
     
     :param datetime: the ``date`` object
     :param format: one of "full", "long", "medium", or "short", or a custom
                    date/time pattern
+    :param tzinfo: the timezone to apply to the time for display
     :param locale: a `Locale` object or a locale identifier
     :rtype: `unicode`
     """
@@ -194,7 +195,7 @@ def format_datetime(datetime, format='medium', locale=LC_TIME):
     pattern = parse_pattern(format)
     return parse_pattern(format).apply(datetime, locale)
 
-def format_time(time, format='medium', locale=LC_TIME):
+def format_time(time, format='medium', tzinfo=UTC, locale=LC_TIME):
     """Returns a time formatted according to the given pattern.
     
     >>> t = time(15, 30)
@@ -209,9 +210,18 @@ def format_time(time, format='medium', locale=LC_TIME):
     >>> format_time(t, "hh 'o''clock' a", locale='en')
     u"03 o'clock PM"
     
+    For any pattern requiring the display of the time-zone, the third-party
+    ``pytz`` package is needed to explicitly specify the time-zone:
+    
+    >>> from pytz import timezone
+    >>> cet = timezone('Europe/Berlin')
+    >>> format_time(t, format='full', tzinfo=cet, locale='de_DE')
+    u'15:30 Uhr MEZ'
+    
     :param time: the ``time`` or ``datetime`` object
     :param format: one of "full", "long", "medium", or "short", or a custom
                    date/time pattern
+    :param tzinfo: the time-zone to apply to the time for display
     :param locale: a `Locale` object or a locale identifier
     :rtype: `unicode`
     
@@ -224,6 +234,8 @@ def format_time(time, format='medium', locale=LC_TIME):
         time = datetime.fromtimestamp(time).time()
     elif isinstance(time, datetime):
         time = time.time()
+    if time.tzinfo is None:
+        time = time.replace(tzinfo=tzinfo)
     locale = Locale.parse(locale)
     if format in ('full', 'long', 'medium', 'short'):
         format = get_time_format(format, locale=locale)
@@ -263,6 +275,8 @@ class DateTimeFormat(object):
 
     def __init__(self, value, locale):
         assert isinstance(value, (date, datetime, time))
+        if isinstance(value, (datetime, time)) and value.tzinfo is None:
+            value = value.replace(tzinfo=UTC)
         self.value = value
         self.locale = Locale.parse(locale)
 
@@ -296,6 +310,8 @@ class DateTimeFormat(object):
             return self.format(self.value.minute, num)
         elif char == 's':
             return self.format(self.value.second, num)
+        elif char in ('z', 'Z', 'v'):
+            return self.format_timezone(char, num)
         else:
             raise KeyError('Unsupported date/time field %r' % char)
 
@@ -336,6 +352,31 @@ class DateTimeFormat(object):
         period = {0: 'am', 1: 'pm'}[int(self.value.hour > 12)]
         return get_period_names(locale=self.locale)[period]
 
+    def format_timezone(self, char, num):
+        if char == 'z':
+            zone = self.value.tzinfo.zone
+            if num < 4:
+                return self.locale.time_zones[zone]['short'][
+                    self.value.dst() and 'daylight' or 'standard'
+                ]
+            else:
+                return self.locale.time_zones[zone]['long'][
+                    self.value.dst() and 'daylight' or 'standard'
+                ]
+
+        elif char == 'Z':
+            offset = self.value.utcoffset()
+            hours, seconds = divmod(offset.seconds, 3600)
+            minutes = seconds // 60
+            sign = '+'
+            if offset.seconds < 0:
+                sign = '-'
+            pattern = {3: '%s%02d%02d', 4: 'GMT %s%02d:%02d'}[max(3, num)]
+            return pattern % (sign, hours, minutes)
+
+        elif char == 'v':
+            raise NotImplementedError
+
     def format(self, value, length):
         return ('%%0%dd' % length) % value
 
index 47616a6496b9b33dd4a02d7880f18175d0d28992..c1aaf684e0ff184391cc79692afb45cd252834f4 100644 (file)
@@ -15,6 +15,8 @@ from datetime import date, datetime, time
 import doctest
 import unittest
 
+from pytz import timezone
+
 from babel import dates
 
 
@@ -54,6 +56,19 @@ class DateTimeFormatTestCase(unittest.TestCase):
         fmt = dates.DateTimeFormat(d, locale='dv_MV')
         self.assertEqual('4', fmt['c']) # friday is first day of week
 
+    def test_timezone_rfc822(self):
+        tz = timezone('Europe/Berlin')
+        t = time(15, 30, tzinfo=tz)
+        fmt = dates.DateTimeFormat(t, locale='de_DE')
+        self.assertEqual('+0100', fmt['Z'])
+
+    def test_timezone_gmt(self):
+        tz = timezone('Europe/Berlin')
+        t = time(15, 30, tzinfo=tz)
+        fmt = dates.DateTimeFormat(t, locale='de_DE')
+        self.assertEqual('GMT +01:00', fmt['ZZZZ'])
+
+
 
 class FormatDateTestCase(unittest.TestCase):
 
index 8869d1cf7415f232bf1a71188ace853baffb541a..f38bc364c5e9193512330a982de876aa86bcfa10 100644 (file)
 
 """Various utility classes and functions."""
 
+from datetime import tzinfo
 import os
 import re
 
-__all__ = ['default_locale', 'extended_glob', 'LazyProxy']
+__all__ = ['default_locale', 'extended_glob', 'relpath', 'LazyProxy', 'UTC']
 __docformat__ = 'restructuredtext en'
 
 def default_locale(kind=None):
@@ -75,6 +76,7 @@ def extended_glob(pattern, dirname=''):
             if regex.match(filepath):
                 yield filepath
 
+
 class LazyProxy(object):
     """Class for proxy objects that delegate to a specified function to evaluate
     the actual object.
@@ -209,6 +211,11 @@ try:
     relpath = os.path.relpath
 except AttributeError:
     def relpath(path, start='.'):
+        """Compute the relative path to one path from another.
+        
+        :return: the relative path
+        :rtype: `basestring`
+        """
         start_list = os.path.abspath(start).split(os.sep)
         path_list = os.path.abspath(path).split(os.sep)
 
@@ -217,3 +224,32 @@ except AttributeError:
 
         rel_list = [os.path.pardir] * (len(start_list) - i) + path_list[i:]
         return os.path.join(*rel_list)
+
+try:
+    from pytz import UTC
+except ImportError:
+    ZERO = timedelta(0)
+
+    class UTC(tzinfo):
+        """Simple `tzinfo` implementation for UTC."""
+
+        def __repr__(self):
+            return '<UTC>'
+
+        def __str__(self):
+            return 'UTC'
+
+        def utcoffset(self, dt):
+            return ZERO
+
+        def tzname(self, dt):
+            return 'UTC'
+
+        def dst(self, dt):
+            return ZERO
+
+    UTC = UTC()
+    """`tzinfo` object for UTC (Universal Time).
+    
+    :type: `tzinfo`
+    """
index af45b32052c17e2ce4024e82e1375c3b7bd93c0d..61535b06bcfb76519c3489dec527b9104b7a27bd 100644 (file)
@@ -192,6 +192,35 @@ Time Fields
   +----------+--------+--------------------------------------------------------+
 
 
+Time-zone Support
+-----------------
+
+Many of the verbose default time formats include the time-zone, but the
+time-zone is not by default available for the Python ``datetime`` and ``time``
+objects. The standard library includes only the abstract ``tzinfo`` class,
+which you need appropriate implementations for to actually use in your
+application. Babel includes a ``tzinfo`` implementation for UTC (Universal
+Time). For actual time-zones, it is strongly recommended that you use the
+third-party package `pytz`_, which includes the definitions of practically all
+of the time-zones used on the world, as well as important functions for
+reliably converting from UTC to local time, and vice versa::
+
+    >>> from datetime import time
+    >>> t = time(15, 30)
+    
+    >>> from pytz import timezone
+    >>> cet = timezone('Europe/Berlin')
+    >>> format_time(t, 'H:mm Z', tzinfo=cet, locale='de_DE')
+    u'15:30 +0100'
+
+The recommended approach to deal with different time-zones in a Python
+application is to always use UTC internally, and only convert from/to the users
+time-zone when accepting user input and displaying date/time data, respectively.
+
+ .. _`pytz`: http://pytz.sourceforge.net/
+
+
+
 Parsing Dates
 -------------