1
# Copyright (C) 2007, 2008, 2009, 2011 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
from __future__ import absolute_import
23
from bzrlib import osutils
26
def format_highres_date(t, offset=0):
27
"""Format a date, such that it includes higher precision in the
30
:param t: The local time in fractional seconds since the epoch
32
:param offset: The timezone offset in integer seconds
35
Example: format_highres_date(time.time(), -time.timezone)
36
this will return a date stamp for right now,
37
formatted for the local timezone.
39
>>> from bzrlib.osutils import format_date
40
>>> format_date(1120153132.350850105, 0)
41
'Thu 2005-06-30 17:38:52 +0000'
42
>>> format_highres_date(1120153132.350850105, 0)
43
'Thu 2005-06-30 17:38:52.350850105 +0000'
44
>>> format_date(1120153132.350850105, -5*3600)
45
'Thu 2005-06-30 12:38:52 -0500'
46
>>> format_highres_date(1120153132.350850105, -5*3600)
47
'Thu 2005-06-30 12:38:52.350850105 -0500'
48
>>> format_highres_date(1120153132.350850105, 7200)
49
'Thu 2005-06-30 19:38:52.350850105 +0200'
50
>>> format_highres_date(1152428738.867522, 19800)
51
'Sun 2006-07-09 12:35:38.867522001 +0530'
53
if not isinstance(t, float):
56
# This has to be formatted for "original" date, so that the
57
# revision XML entry will be reproduced faithfully.
60
tt = time.gmtime(t + offset)
62
return (osutils.weekdays[tt[6]] +
63
time.strftime(" %Y-%m-%d %H:%M:%S", tt)
64
# Get the high-res seconds, but ignore the 0
65
+ ('%.9f' % (t - int(t)))[1:]
66
+ ' %+03d%02d' % (offset / 3600, (offset / 60) % 60))
69
def unpack_highres_date(date):
70
"""This takes the high-resolution date stamp, and
71
converts it back into the tuple (timestamp, timezone)
72
Where timestamp is in real UTC since epoch seconds, and timezone is an
73
integer number of seconds offset.
75
:param date: A date formated by format_highres_date
79
# Weekday parsing is locale sensitive, so drop the weekday
80
space_loc = date.find(' ')
81
if space_loc == -1 or date[:space_loc] not in osutils.weekdays:
83
'Date string does not contain a day of week: %r' % date)
84
# Up until the first period is a datestamp that is generated
85
# as normal from time.strftime, so use time.strptime to
87
dot_loc = date.find('.')
90
'Date string does not contain high-precision seconds: %r' % date)
91
base_time = time.strptime(date[space_loc:dot_loc], " %Y-%m-%d %H:%M:%S")
92
fract_seconds, offset = date[dot_loc:].split()
93
fract_seconds = float(fract_seconds)
97
hours = int(offset / 100)
98
minutes = (offset % 100)
99
seconds_offset = (hours * 3600) + (minutes * 60)
101
# time.mktime returns localtime, but calendar.timegm returns UTC time
102
timestamp = calendar.timegm(base_time)
103
timestamp -= seconds_offset
104
# Add back in the fractional seconds
105
timestamp += fract_seconds
106
return (timestamp, seconds_offset)
109
def format_patch_date(secs, offset=0):
110
"""Format a POSIX timestamp and optional offset as a patch-style date.
112
Inverse of parse_patch_date.
116
"can't represent timezone %s offset by fractional minutes" % offset)
117
# so that we don't need to do calculations on pre-epoch times,
118
# which doesn't work with win32 python gmtime, we always
119
# give the epoch in utc
122
if secs + offset < 0:
123
from warnings import warn
124
warn("gmtime of negative time (%s, %s) may not work on Windows" %
126
return osutils.format_date(secs, offset=offset,
127
date_fmt='%Y-%m-%d %H:%M:%S')
130
# Format for patch dates: %Y-%m-%d %H:%M:%S [+-]%H%M
131
# Groups: 1 = %Y-%m-%d %H:%M:%S; 2 = [+-]%H; 3 = %M
132
RE_PATCHDATE = re.compile("(\d+-\d+-\d+\s+\d+:\d+:\d+)\s*([+-]\d\d)(\d\d)$")
133
RE_PATCHDATE_NOOFFSET = re.compile("\d+-\d+-\d+\s+\d+:\d+:\d+$")
135
def parse_patch_date(date_str):
136
"""Parse a patch-style date into a POSIX timestamp and offset.
138
Inverse of format_patch_date.
140
match = RE_PATCHDATE.match(date_str)
142
if RE_PATCHDATE_NOOFFSET.match(date_str) is not None:
143
raise ValueError("time data %r is missing a timezone offset"
146
raise ValueError("time data %r does not match format " % date_str
147
+ "'%Y-%m-%d %H:%M:%S %z'")
148
secs_str = match.group(1)
149
offset_hours, offset_mins = int(match.group(2)), int(match.group(3))
150
if abs(offset_hours) >= 24 or offset_mins >= 60:
151
raise ValueError("invalid timezone %r" %
152
(match.group(2) + match.group(3)))
153
offset = offset_hours * 3600 + offset_mins * 60
154
tm_time = time.strptime(secs_str, '%Y-%m-%d %H:%M:%S')
155
# adjust seconds according to offset before converting to POSIX
156
# timestamp, to avoid edge problems
157
tm_time = tm_time[:5] + (tm_time[5] - offset,) + tm_time[6:]
158
secs = calendar.timegm(tm_time)