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