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, calendar
25
from duplicity import globals
28
class TimeException(Exception):
31
_interval_conv_dict = {"s": 1, "m": 60, "h": 3600, "D": 86400,
32
"W": 7*86400, "M": 30*86400, "Y": 365*86400}
33
_integer_regexp = re.compile("^[0-9]+$")
34
_interval_regexp = re.compile("^([0-9]+)([smhDWMY])")
35
_genstr_date_regexp1 = re.compile("^(?P<year>[0-9]{4})[-/]"
36
"(?P<month>[0-9]{1,2})[-/]"
37
"(?P<day>[0-9]{1,2})$")
38
_genstr_date_regexp2 = re.compile("^(?P<month>[0-9]{1,2})[-/]"
39
"(?P<day>[0-9]{1,2})[-/]"
40
"(?P<year>[0-9]{4})$")
41
_genstr_date_regexp3 = re.compile("^(?P<year>[0-9]{4})"
43
"(?P<day>[0-9]{2})Z$")
44
curtime = curtimestr = None
45
prevtime = prevtimestr = None
47
bad_interval_string = _("""Bad interval string "%s"
49
Intervals are specified like 2Y (2 years) or 2h30m (2.5 hours). The
50
allowed special characters are s, m, h, D, W, M, and Y. See the man
51
page for more information.""")
53
bad_time_string = _("""Bad time string "%s"
55
The acceptible time strings are intervals (like "3D64s"), w3-datetime
56
strings, like "2002-04-26T04:22:01-07:00" (strings like
57
"2002-04-26T04:22:01" are also acceptable - duplicity will use the
58
current time zone), or ordinary dates like 2/4/1997 or 2001-04-23
59
(various combinations are acceptable, but the month always precedes
62
def setcurtime(time_in_secs = None):
63
"""Sets the current time in curtime and curtimestr"""
64
global curtime, curtimestr
65
t = time_in_secs or long(time.time())
66
assert type(t) in (types.LongType, types.IntType)
67
curtime, curtimestr = t, timetostring(t)
69
def setprevtime(time_in_secs):
70
"""Sets the previous time in prevtime and prevtimestr"""
71
global prevtime, prevtimestr
72
assert type(time_in_secs) in (types.LongType, types.IntType), prevtime
73
prevtime, prevtimestr = time_in_secs, timetostring(time_in_secs)
75
def timetostring(timeinseconds):
76
"""Return w3 or duplicity datetime compliant listing of timeinseconds"""
78
if globals.old_filenames:
79
# We need to know if DST applies to append the correct offset. So
80
# 1. Save the tuple returned by localtime.
81
# 2. Pass the DST flag into gettzd
82
lcltime = time.localtime(timeinseconds)
83
return time.strftime("%Y-%m-%dT%H" + globals.time_separator +
84
"%M" + globals.time_separator + "%S",
85
lcltime) + gettzd(lcltime[-1])
87
# DST never applies to UTC
88
lcltime = time.gmtime(timeinseconds)
89
return time.strftime("%Y%m%dT%H%M%SZ", lcltime)
91
def stringtotime(timestring):
92
"""Return time in seconds from w3 or duplicity timestring
94
If there is an error parsing the string, or it doesn't look
95
like a valid datetime string, return None.
98
date, daytime = timestring[:19].split("T")
99
if len(timestring) == 16:
100
# new format for filename time
101
year, month, day = map(int,
102
[date[0:4], date[4:6], date[6:8]])
103
hour, minute, second = map(int,
104
[daytime[0:2], daytime[2:4], daytime[4:6]])
106
# old format for filename time
107
year, month, day = map(int, date.split("-"))
108
hour, minute, second = map(int,
109
daytime.split(globals.time_separator))
110
assert 1900 < year < 2100, year
111
assert 1 <= month <= 12
112
assert 1 <= day <= 31
113
assert 0 <= hour <= 23
114
assert 0 <= minute <= 59
115
assert 0 <= second <= 61 # leap seconds
116
# We want to return the time in units of seconds since the
117
# epoch. Unfortunately the only functin that does this
118
# works in terms of the current timezone and we have a
119
# timezone offset in the string.
120
timetuple = (year, month, day, hour, minute, second, -1, -1, 0)
122
if len(timestring) == 16:
123
# as said in documentation, time.gmtime() and timegm() are each others' inverse.
124
# As far as UTC format is used in new file format,
125
# do not rely on system's python DST and tzdata settings
126
# and use functions that working with UTC
127
utc_in_secs = calendar.timegm(timetuple)
129
# mktime assumed that the tuple was a local time. Compensate
130
# by subtracting the value for the current timezone.
131
# We don't need to worry about DST here because we turned it
133
local_in_secs = time.mktime(timetuple)
134
utc_in_secs = local_in_secs - time.timezone
135
# Now apply the offset that we were given in the time string
136
# This gives the correct number of seconds from the epoch
137
# even when we're not in the same timezone that wrote the
139
if len(timestring) == 16:
140
return long(utc_in_secs)
142
return long(utc_in_secs + tzdtoseconds(timestring[19:]))
143
except (TypeError, ValueError, AssertionError):
146
def timetopretty(timeinseconds):
147
"""Return pretty version of time"""
148
return time.asctime(time.localtime(timeinseconds))
150
def stringtopretty(timestring):
151
"""Return pretty version of time given w3 time string"""
152
return timetopretty(stringtotime(timestring))
154
def inttopretty(seconds):
155
"""Convert num of seconds to readable string like "2 hours"."""
157
hours, seconds = divmod(seconds, 3600)
159
partlist.append("%d hours" % hours)
161
partlist.append("1 hour")
163
minutes, seconds = divmod(seconds, 60)
165
partlist.append("%d minutes" % minutes)
167
partlist.append("1 minute")
170
partlist.append("1 second")
171
elif not partlist or seconds > 1:
172
if isinstance(seconds, int) or isinstance(seconds, long):
173
partlist.append("%s seconds" % seconds)
175
partlist.append("%.2f seconds" % seconds)
176
return " ".join(partlist)
178
def intstringtoseconds(interval_string):
179
"""Convert a string expressing an interval (e.g. "4D2s") to seconds"""
181
raise TimeException(bad_interval_string % interval_string)
183
if len(interval_string) < 2:
187
while interval_string:
188
match = _interval_regexp.match(interval_string)
191
num, ext = int(match.group(1)), match.group(2)
192
if not ext in _interval_conv_dict or num < 0:
194
total += num*_interval_conv_dict[ext]
195
interval_string = interval_string[match.end(0):]
199
"""Return w3's timezone identification string.
201
Expresed as [+/-]hh:mm. For instance, PST is -08:00. Zone is
202
coincides with what localtime(), etc., use.
205
# time.daylight doesn't help us. It's a flag that indicates that we
206
# have a dst option for the current timezone. Compensate by allowing
207
# the caller to pass a flag to indicate that DST applies. This flag
208
# is in the same format as the last member of the tuple returned by
212
offset = -1 * time.altzone/60
214
offset = -1 * time.timezone/60
220
return "Z" # time is already in UTC
222
hours, minutes = map(abs, divmod(offset, 60))
223
assert 0 <= hours <= 23
224
assert 0 <= minutes <= 59
225
return "%s%02d%s%02d" % (prefix, hours, globals.time_separator, minutes)
227
def tzdtoseconds(tzd):
228
"""Given w3 compliant TZD, return how far ahead UTC is"""
231
assert len(tzd) == 6 # only accept forms like +08:00 for now
232
assert (tzd[0] == "-" or tzd[0] == "+") and \
233
tzd[3] == globals.time_separator
234
return -60 * (60 * int(tzd[:3]) + int(tzd[4:]))
236
def cmp(time1, time2):
237
"""Compare time1 and time2 and return -1, 0, or 1"""
238
if type(time1) is types.StringType:
239
time1 = stringtotime(time1)
240
assert time1 is not None
241
if type(time2) is types.StringType:
242
time2 = stringtotime(time2)
243
assert time2 is not None
252
def genstrtotime(timestr, override_curtime = None):
253
"""Convert a generic time string to a time in seconds"""
254
if override_curtime is None:
255
override_curtime = curtime
257
return override_curtime
260
raise TimeException(bad_time_string % timestr)
262
# Test for straight integer
263
if _integer_regexp.search(timestr):
266
# Test for w3-datetime format, possibly missing tzd
267
# This is an ugly hack. We need to know if DST applies when doing
268
# gettzd. However, we don't have the flag to pass. Assume that DST
269
# doesn't apply and pass 0. Getting a reasonable default from
270
# localtime() is a bad idea, since we transition to/from DST between
271
# calls to this method on the same run
273
t = stringtotime(timestr) or stringtotime(timestr+gettzd(0))
277
try: # test for an interval, like "2 days ago"
278
return override_curtime - intstringtoseconds(timestr)
279
except TimeException:
282
# Now check for dates like 2001/3/23
283
match = _genstr_date_regexp1.search(timestr) or \
284
_genstr_date_regexp2.search(timestr) or \
285
_genstr_date_regexp3.search(timestr)
288
timestr = "%s-%02d-%02dT00:00:00%s" % (match.group('year'),
289
int(match.group('month')),
290
int(match.group('day')),
292
t = stringtotime(timestr)