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