~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/timestamp.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2008-06-20 01:09:18 UTC
  • mfrom: (3505.1.1 ianc-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20080620010918-64z4xylh1ap5hgyf
Accept user names with @s in URLs (Neil Martinsen-Burrell)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
import calendar
 
18
import time
 
19
 
 
20
from bzrlib import osutils
 
21
 
 
22
 
 
23
def format_highres_date(t, offset=0):
 
24
    """Format a date, such that it includes higher precision in the
 
25
    seconds field.
 
26
 
 
27
    :param t:   The local time in fractional seconds since the epoch
 
28
    :type t: float
 
29
    :param offset:  The timezone offset in integer seconds
 
30
    :type offset: int
 
31
 
 
32
    Example: format_highres_date(time.time(), -time.timezone)
 
33
    this will return a date stamp for right now,
 
34
    formatted for the local timezone.
 
35
 
 
36
    >>> from bzrlib.osutils import format_date
 
37
    >>> format_date(1120153132.350850105, 0)
 
38
    'Thu 2005-06-30 17:38:52 +0000'
 
39
    >>> format_highres_date(1120153132.350850105, 0)
 
40
    'Thu 2005-06-30 17:38:52.350850105 +0000'
 
41
    >>> format_date(1120153132.350850105, -5*3600)
 
42
    'Thu 2005-06-30 12:38:52 -0500'
 
43
    >>> format_highres_date(1120153132.350850105, -5*3600)
 
44
    'Thu 2005-06-30 12:38:52.350850105 -0500'
 
45
    >>> format_highres_date(1120153132.350850105, 7200)
 
46
    'Thu 2005-06-30 19:38:52.350850105 +0200'
 
47
    >>> format_highres_date(1152428738.867522, 19800)
 
48
    'Sun 2006-07-09 12:35:38.867522001 +0530'
 
49
    """
 
50
    if not isinstance(t, float):
 
51
        raise ValueError(t)
 
52
 
 
53
    # This has to be formatted for "original" date, so that the
 
54
    # revision XML entry will be reproduced faithfully.
 
55
    if offset is None:
 
56
        offset = 0
 
57
    tt = time.gmtime(t + offset)
 
58
 
 
59
    return (time.strftime("%a %Y-%m-%d %H:%M:%S", tt)
 
60
            # Get the high-res seconds, but ignore the 0
 
61
            + ('%.9f' % (t - int(t)))[1:]
 
62
            + ' %+03d%02d' % (offset / 3600, (offset / 60) % 60))
 
63
 
 
64
 
 
65
def unpack_highres_date(date):
 
66
    """This takes the high-resolution date stamp, and
 
67
    converts it back into the tuple (timestamp, timezone)
 
68
    Where timestamp is in real UTC since epoch seconds, and timezone is an
 
69
    integer number of seconds offset.
 
70
 
 
71
    :param date: A date formated by format_highres_date
 
72
    :type date: string
 
73
 
 
74
    >>> import time, random
 
75
    >>> unpack_highres_date('Thu 2005-06-30 12:38:52.350850105 -0500')
 
76
    (1120153132.3508501, -18000)
 
77
    >>> unpack_highres_date('Thu 2005-06-30 17:38:52.350850105 +0000')
 
78
    (1120153132.3508501, 0)
 
79
    >>> unpack_highres_date('Thu 2005-06-30 19:38:52.350850105 +0200')
 
80
    (1120153132.3508501, 7200)
 
81
    >>> unpack_highres_date('Sun 2006-07-09 12:35:38.867522001 +0530')
 
82
    (1152428738.867522, 19800)
 
83
    >>> from bzrlib.osutils import local_time_offset
 
84
    >>> t = time.time()
 
85
    >>> o = local_time_offset()
 
86
    >>> t2, o2 = unpack_highres_date(format_highres_date(t, o))
 
87
    >>> t == t2
 
88
    True
 
89
    >>> o == o2
 
90
    True
 
91
    >>> t -= 24*3600*365*2 # Start 2 years ago
 
92
    >>> o = -12*3600
 
93
    >>> for count in xrange(500):
 
94
    ...   t += random.random()*24*3600*30
 
95
    ...   o = ((o/3600 + 13) % 25 - 12)*3600 # Add 1 wrap around from [-12, 12]
 
96
    ...   date = format_highres_date(t, o)
 
97
    ...   t2, o2 = unpack_highres_date(date)
 
98
    ...   if t != t2 or o != o2:
 
99
    ...      print 'Failed on date %r, %s,%s diff:%s' % (date, t, o, t2-t)
 
100
    ...      break
 
101
 
 
102
    """
 
103
    # Up until the first period is a datestamp that is generated
 
104
    # as normal from time.strftime, so use time.strptime to
 
105
    # parse it
 
106
    dot_loc = date.find('.')
 
107
    if dot_loc == -1:
 
108
        raise ValueError(
 
109
            'Date string does not contain high-precision seconds: %r' % date)
 
110
    base_time = time.strptime(date[:dot_loc], "%a %Y-%m-%d %H:%M:%S")
 
111
    fract_seconds, offset = date[dot_loc:].split()
 
112
    fract_seconds = float(fract_seconds)
 
113
 
 
114
    offset = int(offset)
 
115
 
 
116
    hours = int(offset / 100)
 
117
    minutes = (offset % 100)
 
118
    seconds_offset = (hours * 3600) + (minutes * 60)
 
119
 
 
120
    # time.mktime returns localtime, but calendar.timegm returns UTC time
 
121
    timestamp = calendar.timegm(base_time)
 
122
    timestamp -= seconds_offset
 
123
    # Add back in the fractional seconds
 
124
    timestamp += fract_seconds
 
125
    return (timestamp, seconds_offset)
 
126
 
 
127
 
 
128
def format_patch_date(secs, offset=0):
 
129
    """Format a POSIX timestamp and optional offset as a patch-style date.
 
130
 
 
131
    Inverse of parse_patch_date.
 
132
    """
 
133
    if offset % 60 != 0:
 
134
        raise ValueError(
 
135
        "can't represent timezone %s offset by fractional minutes" % offset)
 
136
    # so that we don't need to do calculations on pre-epoch times, 
 
137
    # which doesn't work with win32 python gmtime, we always
 
138
    # give the epoch in utc
 
139
    if secs == 0:
 
140
        offset = 0
 
141
    if secs + offset < 0:
 
142
        from warnings import warn
 
143
        warn("gmtime of negative time (%s, %s) may not work on Windows" %
 
144
                (secs, offset))
 
145
    return osutils.format_date(secs, offset=offset,
 
146
            date_fmt='%Y-%m-%d %H:%M:%S')
 
147
 
 
148
 
 
149
def parse_patch_date(date_str):
 
150
    """Parse a patch-style date into a POSIX timestamp and offset.
 
151
 
 
152
    Inverse of format_patch_date.
 
153
    """
 
154
    secs_str = date_str[:-6]
 
155
    offset_str = date_str[-5:]
 
156
    if len(offset_str) != 5:
 
157
        raise ValueError(
 
158
            "invalid timezone %r" % offset_str)
 
159
    offset_hours, offset_mins = offset_str[:3], offset_str[3:]
 
160
    offset = int(offset_hours) * 3600 + int(offset_mins) * 60
 
161
    tm_time = time.strptime(secs_str, '%Y-%m-%d %H:%M:%S')
 
162
    # adjust seconds according to offset before converting to POSIX
 
163
    # timestamp, to avoid edge problems
 
164
    tm_time = tm_time[:5] + (tm_time[5] - offset,) + tm_time[6:]
 
165
    secs = calendar.timegm(tm_time)
 
166
    return secs, offset