1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
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.
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.
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
21
def format_highres_date(t, offset=0):
22
"""Format a date, such that it includes higher precision in the
25
:param t: The local time in fractional seconds since the epoch
27
:param offset: The timezone offset in integer seconds
30
Example: format_highres_date(time.time(), -time.timezone)
31
this will return a date stamp for right now,
32
formatted for the local timezone.
34
>>> from bzrlib.osutils import format_date
35
>>> format_date(1120153132.350850105, 0)
36
'Thu 2005-06-30 17:38:52 +0000'
37
>>> format_highres_date(1120153132.350850105, 0)
38
'Thu 2005-06-30 17:38:52.350850105 +0000'
39
>>> format_date(1120153132.350850105, -5*3600)
40
'Thu 2005-06-30 12:38:52 -0500'
41
>>> format_highres_date(1120153132.350850105, -5*3600)
42
'Thu 2005-06-30 12:38:52.350850105 -0500'
43
>>> format_highres_date(1120153132.350850105, 7200)
44
'Thu 2005-06-30 19:38:52.350850105 +0200'
45
>>> format_highres_date(1152428738.867522, 19800)
46
'Sun 2006-07-09 12:35:38.867522001 +0530'
48
assert isinstance(t, float)
50
# This has to be formatted for "original" date, so that the
51
# revision XML entry will be reproduced faithfully.
54
tt = time.gmtime(t + offset)
56
return (time.strftime("%a %Y-%m-%d %H:%M:%S", tt)
57
# Get the high-res seconds, but ignore the 0
58
+ ('%.9f' % (t - int(t)))[1:]
59
+ ' %+03d%02d' % (offset / 3600, (offset / 60) % 60))
62
def unpack_highres_date(date):
63
"""This takes the high-resolution date stamp, and
64
converts it back into the tuple (timestamp, timezone)
65
Where timestamp is in real UTC since epoch seconds, and timezone is an
66
integer number of seconds offset.
68
:param date: A date formated by format_highres_date
71
>>> import time, random
72
>>> unpack_highres_date('Thu 2005-06-30 12:38:52.350850105 -0500')
73
(1120153132.3508501, -18000)
74
>>> unpack_highres_date('Thu 2005-06-30 17:38:52.350850105 +0000')
75
(1120153132.3508501, 0)
76
>>> unpack_highres_date('Thu 2005-06-30 19:38:52.350850105 +0200')
77
(1120153132.3508501, 7200)
78
>>> unpack_highres_date('Sun 2006-07-09 12:35:38.867522001 +0530')
79
(1152428738.867522, 19800)
80
>>> from bzrlib.osutils import local_time_offset
82
>>> o = local_time_offset()
83
>>> t2, o2 = unpack_highres_date(format_highres_date(t, o))
88
>>> t -= 24*3600*365*2 # Start 2 years ago
90
>>> for count in xrange(500):
91
... t += random.random()*24*3600*30
92
... o = ((o/3600 + 13) % 25 - 12)*3600 # Add 1 wrap around from [-12, 12]
93
... date = format_highres_date(t, o)
94
... t2, o2 = unpack_highres_date(date)
95
... if t != t2 or o != o2:
96
... print 'Failed on date %r, %s,%s diff:%s' % (date, t, o, t2-t)
100
# Up until the first period is a datestamp that is generated
101
# as normal from time.strftime, so use time.strptime to
103
dot_loc = date.find('.')
106
'Date string does not contain high-precision seconds: %r' % date)
107
base_time = time.strptime(date[:dot_loc], "%a %Y-%m-%d %H:%M:%S")
108
fract_seconds, offset = date[dot_loc:].split()
109
fract_seconds = float(fract_seconds)
113
hours = int(offset / 100)
114
minutes = (offset % 100)
115
seconds_offset = (hours * 3600) + (minutes * 60)
117
# time.mktime returns localtime, but calendar.timegm returns UTC time
118
timestamp = calendar.timegm(base_time)
119
timestamp -= seconds_offset
120
# Add back in the fractional seconds
121
timestamp += fract_seconds
122
return (timestamp, seconds_offset)
125
def format_patch_date(secs, offset=0):
126
"""Format a POSIX timestamp and optional offset as a patch-style date.
128
Inverse of parse_patch_date.
130
assert offset % 36 == 0
131
tm = time.gmtime(secs+offset)
132
time_str = time.strftime('%Y-%m-%d %H:%M:%S', tm)
133
return '%s %+05d' % (time_str, offset/36)
136
def parse_patch_date(date_str):
137
"""Parse a patch-style date into a POSIX timestamp and offset.
139
Inverse of format_patch_date.
141
secs_str = date_str[:-6]
142
offset_str = date_str[-6:]
143
offset = int(offset_str) * 36
144
tm_time = time.strptime(secs_str, '%Y-%m-%d %H:%M:%S')
145
# adjust seconds according to offset before converting to POSIX
146
# timestamp, to avoid edge problems
147
tm_time = tm_time[:5] + (tm_time[5] - offset,) + tm_time[6:]
148
secs = calendar.timegm(tm_time)