1
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
3
# Copyright 2002 Ben Escoto <ben@emerose.org>
4
# Copyright 2007 Kenneth Loafman <kenneth@loafman.com>
6
# This file is part of duplicity.
8
# Duplicity is free software; you can redistribute it and/or modify it
9
# under the terms of the GNU General Public License as published by the
10
# Free Software Foundation; either version 2 of the License, or (at your
11
# option) any later version.
13
# Duplicity is distributed in the hope that it will be useful, but
14
# WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16
# General Public License for more details.
18
# You should have received a copy of the GNU General Public License
19
# along with duplicity; if not, write to the Free Software Foundation,
20
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22
"""Provide time related exceptions and functions"""
24
import time, types, re
26
from duplicity import globals
29
class TimeException(Exception):
32
_interval_conv_dict = {"s": 1, "m": 60, "h": 3600, "D": 86400,
33
"W": 7*86400, "M": 30*86400, "Y": 365*86400}
34
_integer_regexp = re.compile("^[0-9]+$")
35
_interval_regexp = re.compile("^([0-9]+)([smhDWMY])")
36
_genstr_date_regexp1 = re.compile("^(?P<year>[0-9]{4})[-/]"
37
"(?P<month>[0-9]{1,2})[-/]"
38
"(?P<day>[0-9]{1,2})$")
39
_genstr_date_regexp2 = re.compile("^(?P<month>[0-9]{1,2})[-/]"
40
"(?P<day>[0-9]{1,2})[-/]"
41
"(?P<year>[0-9]{4})$")
42
_genstr_date_regexp3 = re.compile("^(?P<year>[0-9]{4})"
44
"(?P<day>[0-9]{2})Z$")
45
curtime = curtimestr = None
46
prevtime = prevtimestr = None
48
bad_interval_string = _("""Bad interval string "%s"
50
Intervals are specified like 2Y (2 years) or 2h30m (2.5 hours). The
51
allowed special characters are s, m, h, D, W, M, and Y. See the man
52
page for more information.""")
54
bad_time_string = _("""Bad time string "%s"
56
The acceptible time strings are intervals (like "3D64s"), w3-datetime
57
strings, like "2002-04-26T04:22:01-07:00" (strings like
58
"2002-04-26T04:22:01" are also acceptable - duplicity will use the
59
current time zone), or ordinary dates like 2/4/1997 or 2001-04-23
60
(various combinations are acceptable, but the month always precedes
63
def setcurtime(time_in_secs = None):
64
"""Sets the current time in curtime and curtimestr"""
65
global curtime, curtimestr
66
t = time_in_secs or long(time.time())
67
assert type(t) in (types.LongType, types.IntType)
68
curtime, curtimestr = t, timetostring(t)
70
def setprevtime(time_in_secs):
71
"""Sets the previous time in prevtime and prevtimestr"""
72
global prevtime, prevtimestr
73
assert type(time_in_secs) in (types.LongType, types.IntType), prevtime
74
prevtime, prevtimestr = time_in_secs, timetostring(time_in_secs)
76
def timetostring(timeinseconds):
77
"""Return w3 or duplicity datetime compliant listing of timeinseconds"""
79
if globals.old_filenames:
80
# We need to know if DST applies to append the correct offset. So
81
# 1. Save the tuple returned by localtime.
82
# 2. Pass the DST flag into gettzd
83
lcltime = time.localtime(timeinseconds)
84
return time.strftime("%Y-%m-%dT%H" + globals.time_separator +
85
"%M" + globals.time_separator + "%S",
86
lcltime) + gettzd(lcltime[-1])
88
# DST never applies to UTC
89
lcltime = time.gmtime(timeinseconds)
90
return time.strftime("%Y%m%dT%H%M%SZ", lcltime)
92
def stringtotime(timestring):
93
"""Return time in seconds from w3 or duplicity timestring
95
If there is an error parsing the string, or it doesn't look
96
like a valid datetime string, return None.
99
date, daytime = timestring[:19].split("T")
100
if len(timestring) == 16:
101
# new format for filename time
102
year, month, day = map(int,
103
[date[0:4], date[4:6], date[6:8]])
104
hour, minute, second = map(int,
105
[daytime[0:2], daytime[2:4], daytime[4:6]])
107
# old format for filename time
108
year, month, day = map(int, date.split("-"))
109
hour, minute, second = map(int,
110
daytime.split(globals.time_separator))
111
assert 1900 < year < 2100, year
112
assert 1 <= month <= 12
113
assert 1 <= day <= 31
114
assert 0 <= hour <= 23
115
assert 0 <= minute <= 59
116
assert 0 <= second <= 61 # leap seconds
117
# We want to return the time in units of seconds since the
118
# epoch. Unfortunately the only functin that does this
119
# works in terms of the current timezone and we have a
120
# timezone offset in the string.
121
timetuple = (year, month, day, hour, minute, second, -1, -1, 0)
122
local_in_secs = time.mktime(timetuple)
123
# mktime assumed that the tuple was a local time. Compensate
124
# by subtracting the value for the current timezone.
125
# We don't need to worry about DST here because we turned it
127
utc_in_secs = local_in_secs - time.timezone
128
# Now apply the offset that we were given in the time string
129
# This gives the correct number of seconds from the epoch
130
# even when we're not in the same timezone that wrote the
132
if len(timestring) == 16:
133
return long(utc_in_secs)
135
return long(utc_in_secs + tzdtoseconds(timestring[19:]))
136
except (TypeError, ValueError, AssertionError):
139
def timetopretty(timeinseconds):
140
"""Return pretty version of time"""
141
return time.asctime(time.localtime(timeinseconds))
143
def stringtopretty(timestring):
144
"""Return pretty version of time given w3 time string"""
145
return timetopretty(stringtotime(timestring))
147
def inttopretty(seconds):
148
"""Convert num of seconds to readable string like "2 hours"."""
150
hours, seconds = divmod(seconds, 3600)
152
partlist.append("%d hours" % hours)
154
partlist.append("1 hour")
156
minutes, seconds = divmod(seconds, 60)
158
partlist.append("%d minutes" % minutes)
160
partlist.append("1 minute")
163
partlist.append("1 second")
164
elif not partlist or seconds > 1:
165
if isinstance(seconds, int) or isinstance(seconds, long):
166
partlist.append("%s seconds" % seconds)
168
partlist.append("%.2f seconds" % seconds)
169
return " ".join(partlist)
171
def intstringtoseconds(interval_string):
172
"""Convert a string expressing an interval (e.g. "4D2s") to seconds"""
174
raise TimeException(bad_interval_string % interval_string)
176
if len(interval_string) < 2:
180
while interval_string:
181
match = _interval_regexp.match(interval_string)
184
num, ext = int(match.group(1)), match.group(2)
185
if not ext in _interval_conv_dict or num < 0:
187
total += num*_interval_conv_dict[ext]
188
interval_string = interval_string[match.end(0):]
192
"""Return w3's timezone identification string.
194
Expresed as [+/-]hh:mm. For instance, PST is -08:00. Zone is
195
coincides with what localtime(), etc., use.
198
# time.daylight doesn't help us. It's a flag that indicates that we
199
# have a dst option for the current timezone. Compensate by allowing
200
# the caller to pass a flag to indicate that DST applies. This flag
201
# is in the same format as the last member of the tuple returned by
205
offset = -1 * time.altzone/60
207
offset = -1 * time.timezone/60
213
return "Z" # time is already in UTC
215
hours, minutes = map(abs, divmod(offset, 60))
216
assert 0 <= hours <= 23
217
assert 0 <= minutes <= 59
218
return "%s%02d%s%02d" % (prefix, hours, globals.time_separator, minutes)
220
def tzdtoseconds(tzd):
221
"""Given w3 compliant TZD, return how far ahead UTC is"""
224
assert len(tzd) == 6 # only accept forms like +08:00 for now
225
assert (tzd[0] == "-" or tzd[0] == "+") and \
226
tzd[3] == globals.time_separator
227
return -60 * (60 * int(tzd[:3]) + int(tzd[4:]))
229
def cmp(time1, time2):
230
"""Compare time1 and time2 and return -1, 0, or 1"""
231
if type(time1) is types.StringType:
232
time1 = stringtotime(time1)
233
assert time1 is not None
234
if type(time2) is types.StringType:
235
time2 = stringtotime(time2)
236
assert time2 is not None
245
def genstrtotime(timestr, override_curtime = None):
246
"""Convert a generic time string to a time in seconds"""
247
if override_curtime is None:
248
override_curtime = curtime
250
return override_curtime
253
raise TimeException(bad_time_string % timestr)
255
# Test for straight integer
256
if _integer_regexp.search(timestr):
259
# Test for w3-datetime format, possibly missing tzd
260
# This is an ugly hack. We need to know if DST applies when doing
261
# gettzd. However, we don't have the flag to pass. Assume that DST
262
# doesn't apply and pass 0. Getting a reasonable default from
263
# localtime() is a bad idea, since we transition to/from DST between
264
# calls to this method on the same run
266
t = stringtotime(timestr) or stringtotime(timestr+gettzd(0))
270
try: # test for an interval, like "2 days ago"
271
return override_curtime - intstringtoseconds(timestr)
272
except TimeException:
275
# Now check for dates like 2001/3/23
276
match = _genstr_date_regexp1.search(timestr) or \
277
_genstr_date_regexp2.search(timestr) or \
278
_genstr_date_regexp3.search(timestr)
281
timestr = "%s-%02d-%02dT00:00:00%s" % (match.group('year'),
282
int(match.group('month')),
283
int(match.group('day')),
285
t = stringtotime(timestr)