~bzr-pqm/bzr/bzr.dev

1551.12.28 by Aaron Bentley
Move bundle timestamp code to timestamp
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
2425.6.2 by Martin Pool
Make timestamps use existing format_date; document that function more
20
from bzrlib import osutils
21
1551.12.29 by Aaron Bentley
Copy and extend patch date formatting code, add patch-date parsing
22
1551.12.28 by Aaron Bentley
Move bundle timestamp code to timestamp
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
    assert isinstance(t, float)
51
52
    # This has to be formatted for "original" date, so that the
53
    # revision XML entry will be reproduced faithfully.
54
    if offset is None:
55
        offset = 0
56
    tt = time.gmtime(t + offset)
57
58
    return (time.strftime("%a %Y-%m-%d %H:%M:%S", tt)
59
            # Get the high-res seconds, but ignore the 0
60
            + ('%.9f' % (t - int(t)))[1:]
61
            + ' %+03d%02d' % (offset / 3600, (offset / 60) % 60))
62
63
64
def unpack_highres_date(date):
65
    """This takes the high-resolution date stamp, and
66
    converts it back into the tuple (timestamp, timezone)
67
    Where timestamp is in real UTC since epoch seconds, and timezone is an
68
    integer number of seconds offset.
69
70
    :param date: A date formated by format_highres_date
71
    :type date: string
72
73
    >>> import time, random
74
    >>> unpack_highres_date('Thu 2005-06-30 12:38:52.350850105 -0500')
75
    (1120153132.3508501, -18000)
76
    >>> unpack_highres_date('Thu 2005-06-30 17:38:52.350850105 +0000')
77
    (1120153132.3508501, 0)
78
    >>> unpack_highres_date('Thu 2005-06-30 19:38:52.350850105 +0200')
79
    (1120153132.3508501, 7200)
80
    >>> unpack_highres_date('Sun 2006-07-09 12:35:38.867522001 +0530')
81
    (1152428738.867522, 19800)
82
    >>> from bzrlib.osutils import local_time_offset
83
    >>> t = time.time()
84
    >>> o = local_time_offset()
85
    >>> t2, o2 = unpack_highres_date(format_highres_date(t, o))
86
    >>> t == t2
87
    True
88
    >>> o == o2
89
    True
90
    >>> t -= 24*3600*365*2 # Start 2 years ago
91
    >>> o = -12*3600
92
    >>> for count in xrange(500):
93
    ...   t += random.random()*24*3600*30
94
    ...   o = ((o/3600 + 13) % 25 - 12)*3600 # Add 1 wrap around from [-12, 12]
95
    ...   date = format_highres_date(t, o)
96
    ...   t2, o2 = unpack_highres_date(date)
97
    ...   if t != t2 or o != o2:
98
    ...      print 'Failed on date %r, %s,%s diff:%s' % (date, t, o, t2-t)
99
    ...      break
100
101
    """
102
    # Up until the first period is a datestamp that is generated
103
    # as normal from time.strftime, so use time.strptime to
104
    # parse it
105
    dot_loc = date.find('.')
106
    if dot_loc == -1:
107
        raise ValueError(
108
            'Date string does not contain high-precision seconds: %r' % date)
109
    base_time = time.strptime(date[:dot_loc], "%a %Y-%m-%d %H:%M:%S")
110
    fract_seconds, offset = date[dot_loc:].split()
111
    fract_seconds = float(fract_seconds)
112
113
    offset = int(offset)
114
115
    hours = int(offset / 100)
116
    minutes = (offset % 100)
117
    seconds_offset = (hours * 3600) + (minutes * 60)
118
119
    # time.mktime returns localtime, but calendar.timegm returns UTC time
120
    timestamp = calendar.timegm(base_time)
121
    timestamp -= seconds_offset
122
    # Add back in the fractional seconds
123
    timestamp += fract_seconds
124
    return (timestamp, seconds_offset)
1551.12.29 by Aaron Bentley
Copy and extend patch date formatting code, add patch-date parsing
125
126
127
def format_patch_date(secs, offset=0):
128
    """Format a POSIX timestamp and optional offset as a patch-style date.
129
130
    Inverse of parse_patch_date.
131
    """
2425.6.1 by Martin Pool
Fix formatting of timezones in bundles and merge directives.
132
    assert offset % 60 == 0, \
133
        "can't represent timezone %s offset by fractional minutes" % offset
134
    # so that we don't need to do calculations on pre-epoch times, 
135
    # which doesn't work with win32 python gmtime, we always
136
    # give the epoch in utc
137
    if secs == 0:
138
        offset = 0
139
    if secs + offset < 0:
140
        from warnings import warn
141
        warn("gmtime of negative time (%s, %s) may not work on Windows" %
142
                (secs, offset))
2425.6.2 by Martin Pool
Make timestamps use existing format_date; document that function more
143
    return osutils.format_date(secs, offset=offset,
144
            date_fmt='%Y-%m-%d %H:%M:%S')
1551.12.29 by Aaron Bentley
Copy and extend patch date formatting code, add patch-date parsing
145
146
147
def parse_patch_date(date_str):
148
    """Parse a patch-style date into a POSIX timestamp and offset.
149
150
    Inverse of format_patch_date.
151
    """
152
    secs_str = date_str[:-6]
2425.6.1 by Martin Pool
Fix formatting of timezones in bundles and merge directives.
153
    offset_str = date_str[-5:]
154
    assert len(offset_str) == 5, \
155
            "invalid timezone %r" % offset_str
156
    offset_hours, offset_mins = offset_str[:3], offset_str[3:]
157
    offset = int(offset_hours) * 3600 + int(offset_mins) * 60
1551.12.29 by Aaron Bentley
Copy and extend patch date formatting code, add patch-date parsing
158
    tm_time = time.strptime(secs_str, '%Y-%m-%d %H:%M:%S')
159
    # adjust seconds according to offset before converting to POSIX
160
    # timestamp, to avoid edge problems
161
    tm_time = tm_time[:5] + (tm_time[5] - offset,) + tm_time[6:]
162
    secs = calendar.timegm(tm_time)
163
    return secs, offset