Edgewall Software

Ticket #4547: datefmt-r5696.diff

File datefmt-r5696.diff, 8.8 KB (added by cboos, 17 months ago)

Reworked patch, with docstrings and some tests

  • trac/util/tests/datefmt.py

     
    1717import datetime 
    1818import unittest 
    1919 
    20 from trac.util.datefmt import get_timezone 
     20from trac.util import datefmt 
    2121 
    2222try: 
    2323    import pytz 
     
    2626else: 
    2727    class PytzTestCase(unittest.TestCase): 
    2828        def test_pytz_conversion(self): 
    29             tz = get_timezone('GMT +3:00') 
     29            tz = datefmt.get_timezone('GMT +3:00') 
    3030            self.assertEqual(datetime.timedelta(hours=3), 
    3131                             tz.utcoffset(None)) 
    3232 
    3333        def test_posix_conversion(self): 
    34             tz = get_timezone('Etc/GMT-4') 
     34            tz = datefmt.get_timezone('Etc/GMT-4') 
    3535            self.assertEqual(datetime.timedelta(hours=4), 
    3636                             tz.utcoffset(None)) 
    3737            self.assertEqual('GMT +4:00', tz.zone) 
    3838 
    3939        def test_unicode_input(self): 
    40             tz = get_timezone(u'Etc/GMT-4') 
     40            tz = datefmt.get_timezone(u'Etc/GMT-4') 
    4141            self.assertEqual(datetime.timedelta(hours=4), 
    4242                             tz.utcoffset(None)) 
    4343            self.assertEqual('GMT +4:00', tz.zone) 
    4444 
     45class DateFormatTestCase(unittest.TestCase): 
     46 
     47    def test_to_datetime(self): 
     48        expected = datetime.datetime(1970,1,1,1,0,23,0,datefmt.localtz) 
     49        self.assertEqual(datefmt.to_datetime(23), expected) 
     50        self.assertEqual(datefmt.to_datetime(23L), expected) 
     51        self.assertEqual(datefmt.to_datetime(23.0), expected) 
     52 
     53    def test_to_datetime_tz(self): 
     54        tz = datefmt.timezone('GMT +1:00') 
     55        expected = datetime.datetime(1970,1,1,1,0,23,0,tz) 
     56        self.assertEqual(datefmt.to_datetime(23, tz), expected) 
     57        self.assertEqual(datefmt.to_datetime(23L, tz), expected) 
     58        self.assertEqual(datefmt.to_datetime(23.0, tz), expected) 
     59 
     60    def test_format_datetime(self): 
     61        t = datetime.datetime(1970,1,1,1,0,23,0,datefmt.utc) 
     62        expected = '1970-01-01T01:00:23Z+0000' 
     63        self.assertEqual(datefmt.format_datetime(t, '%Y-%m-%dT%H:%M:%SZ%z', 
     64                                                 datefmt.utc), expected) 
     65        self.assertEqual(datefmt.format_datetime(t, 'iso8601', 
     66                                                 datefmt.utc), expected) 
     67 
     68    def test_format_datetime(self): 
     69        t = datetime.datetime(1970,1,1,1,0,23,0,datefmt.utc) 
     70        expected = '1970-01-01T01:00:23Z+0000' 
     71        self.assertEqual(datefmt.format_datetime(t, '%Y-%m-%dT%H:%M:%SZ%z', 
     72                                                 datefmt.utc), expected) 
     73        self.assertEqual(datefmt.format_datetime(t, 'iso8601', 
     74                                                 datefmt.utc), expected) 
     75 
     76         
    4577def suite(): 
    4678    suite = unittest.TestSuite() 
    4779    if PytzTestCase: 
    4880        suite.addTest(unittest.makeSuite(PytzTestCase, 'test')) 
    4981    else: 
    5082        print "SKIP: utils/tests/datefmt.py (no pytz installed)" 
     83    suite.addTest(unittest.makeSuite(DateFormatTestCase)) 
    5184    return suite 
    5285 
    5386if __name__ == '__main__': 
  • trac/util/datefmt.py

     
    2828 
    2929# Date/time utilities 
    3030 
     31# -- conversion 
     32 
     33def to_datetime(t, tzinfo=None): 
     34    """Convert `t` into a `datetime` object, using the following rules: 
     35     
     36     - If `t` is already a `datetime` object, it is simply returned. 
     37     - If `t` is None, the current time will be used. 
     38     - If `t` is a number, it is interpreted as a timestamp. If no `tzinfo` 
     39       is given, the local timezone will be used for the conversion. 
     40 
     41    Any other input will trigger a `TypeError`. 
     42    """ 
     43    if t is None: 
     44        return datetime.now(localtz) 
     45    elif isinstance(t, datetime): 
     46        return t 
     47    elif isinstance(t, (int,long,float)): 
     48        return datetime.fromtimestamp(t, tzinfo or localtz) 
     49    raise TypeError('expecting datetime, int, long, float, or None; got %s' % 
     50                    type(t)) 
     51 
     52def to_timestamp(dt): 
     53    """Return the corresponding POSIX timestamp""" 
     54    if dt: 
     55        diff = dt - _epoc 
     56        return diff.days * 86400 + diff.seconds 
     57    else: 
     58        return 0 
     59 
     60 
     61# -- formatting 
     62 
    3163def pretty_timedelta(time1, time2=None, resolution=None): 
    32     """Calculate time delta (inaccurately, only for decorative purposes ;-) for 
    33     prettyprinting. If time1 is None, the current time is used.""" 
    34     if not time1: time1 = datetime.now(utc) 
    35     if not time2: time2 = datetime.now(utc) 
     64    """Calculate time delta between two `datetime` objects. 
     65    (the result is somewhat imprecise, only use for prettyprinting). 
     66 
     67    If either `time1` or `time2` is None, the current time will be used 
     68    instead. 
     69    """ 
     70    time1 = to_datetime(time1) 
     71    time2 = to_datetime(time2) 
    3672    if time1 > time2: 
    3773        time2, time1 = time1, time2 
    3874    units = ((3600 * 24 * 365, 'year',   'years'), 
     
    5389            r = int(round(r)) 
    5490            return '%d %s' % (r, r == 1 and unit or unit_plural) 
    5591    return '' 
     92     
     93def format_datetime(t=None, format='%x %X', tzinfo=None): 
     94    """Format the `datetime` object `t` into an `unicode` string 
    5695 
    57 def format_datetime(t=None, format='%x %X', tzinfo=None): 
    58     if not tzinfo: 
    59         tzinfo = localtz 
    60     if t is None: 
    61         t = datetime.now(utc) 
    62     if isinstance(t, (int,long)): 
    63         t = datetime.fromtimestamp(t, tzinfo) 
     96    If `t` is None, the current time will be used. 
     97     
     98    The formatting will be done using the given `format`, which consist 
     99    of conventional `strftime` keys. In addition the format can be 'iso8601' 
     100    to specify the international date format. 
     101 
     102    `tzinfo` will default to the local timezone if left to `None`. 
     103    """ 
     104    t = to_datetime(t, tzinfo).astimezone(tzinfo or localtz) 
    64105    if format.lower() == 'iso8601': 
    65106        format = '%Y-%m-%dT%H:%M:%SZ%z' 
    66     t = t.astimezone(tzinfo) 
    67107    text = t.strftime(format) 
    68108    encoding = locale.getpreferredencoding() or sys.getdefaultencoding() 
    69109    if sys.platform != 'win32': 
     
    72112    return unicode(text, encoding, 'replace') 
    73113 
    74114def format_date(t=None, format='%x', tzinfo=None): 
     115    """Convenience method for formatting the date part of a `datetime` object. 
     116    See `format_datetime` for more details. 
     117    """ 
    75118    if format == 'iso8601': 
    76119        format = '%Y-%m-%d' 
    77120    return format_datetime(t, format, tzinfo=tzinfo) 
    78121 
    79122def format_time(t=None, format='%X', tzinfo=None): 
     123    """Convenience method for formatting the time part of a `datetime` object. 
     124    See `format_datetime` for more details. 
     125    """ 
    80126    if format == 'iso8601': 
    81127        format = '%H:%M:%SZ%z' 
    82128    return format_datetime(t, format, tzinfo=tzinfo) 
    83129 
    84130def get_date_format_hint(): 
     131    """Present the default format used by `format_date` in a human readable 
     132    form. 
     133    This is a format that will be recognized by `parse_date` when reading a 
     134    date. 
     135    """ 
    85136    t = datetime(1999, 10, 29, tzinfo=utc) 
    86137    tmpl = format_date(t, tzinfo=utc) 
    87138    return tmpl.replace('1999', 'YYYY', 1).replace('99', 'YY', 1) \ 
    88139               .replace('10', 'MM', 1).replace('29', 'DD', 1) 
    89140 
    90141def get_datetime_format_hint(): 
     142    """Present the default format used by `format_datetime` in a human readable 
     143    form. 
     144    This is a format that will be recognized by `parse_date` when reading a 
     145    date. 
     146    """ 
    91147    t = datetime(1999, 10, 29, 23, 59, 58, tzinfo=utc) 
    92148    tmpl = format_datetime(t, tzinfo=utc) 
    93149    return tmpl.replace('1999', 'YYYY', 1).replace('99', 'YY', 1) \ 
     
    96152               .replace('59', 'mm', 1).replace('58', 'ss', 1) 
    97153 
    98154def http_date(t=None): 
    99     """Format t as a rfc822 timestamp""" 
    100     if t is None: 
    101         t = datetime.now(utc) 
    102     t = t.astimezone(utc) 
     155    """Format `datetime` object `t` as a rfc822 timestamp""" 
     156    t = to_datetime(t).astimezone(utc) 
    103157    weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] 
    104158    months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 
    105159              'Oct', 'Nov', 'Dec'] 
     
    107161        weekdays[t.weekday()], t.day, months[t.month - 1], t.year, 
    108162        t.hour, t.minute, t.second) 
    109163 
     164 
     165# -- parsing 
     166 
    110167_ISO_8601_RE = re.compile(r'(\d\d\d\d)(?:-?(\d\d)(?:-?(\d\d))?)?'   # date 
    111168                          r'(?:T(\d\d)(?::?(\d\d)(?::?(\d\d))?)?)?' # time 
    112169                          r'(Z(?:([-+])?(\d\d):?(\d\d)?)?)?$'       # timezone 
     
    157214                        'Invalid Date') 
    158215    return datetime(*(tm[0:6] + (0, tzinfo))) 
    159216 
    160 def to_timestamp(dt): 
    161     """Return the corresponding POSIX timestamp""" 
    162     if dt: 
    163         diff = dt - _epoc 
    164         return diff.days * 86400 + diff.seconds 
    165     else: 
    166         return 0 
    167217 
     218# -- timezone utilities 
    168219 
    169220class FixedOffset(tzinfo): 
    170221    """Fixed offset in minutes east from UTC."""