1
# Copyright 2002 Ben Escoto
3
# This file is part of duplicity.
5
# duplicity is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation, Inc., 675 Mass Ave, Cambridge MA
8
# 02139, USA; either version 2 of the License, or (at your option) any
9
# later version; incorporated herein by reference.
11
"""Provide time related exceptions and functions"""
13
import time, types, re
17
class TimeException(Exception): pass
19
_interval_conv_dict = {"s": 1, "m": 60, "h": 3600, "D": 86400,
20
"W": 7*86400, "M": 30*86400, "Y": 365*86400}
21
_integer_regexp = re.compile("^[0-9]+$")
22
_interval_regexp = re.compile("^([0-9]+)([smhDWMY])")
23
_genstr_date_regexp1 = re.compile("^(?P<year>[0-9]{4})[-/]"
24
"(?P<month>[0-9]{1,2})[-/](?P<day>[0-9]{1,2})$")
25
_genstr_date_regexp2 = re.compile("^(?P<month>[0-9]{1,2})[-/]"
26
"(?P<day>[0-9]{1,2})[-/](?P<year>[0-9]{4})$")
27
curtime = curtimestr = None
28
prevtime = prevtimestr = None
29
been_awake_since = None # stores last time sleep() was run
31
def setcurtime(time_in_secs = None):
32
"""Sets the current time in curtime and curtimestr"""
33
global curtime, curtimestr
34
t = time_in_secs or time.time()
35
curtime, curtimestr = t, timetostring(t)
37
def setprevtime(time_in_secs):
38
"""Sets the previous time in prevtime and prevtimestr"""
39
global prevtime, prevtimestr
40
prevtime, prevtimestr = time_in_secs, timetostring(time_in_secs)
42
def timetostring(timeinseconds):
43
"""Return w3 datetime compliant listing of timeinseconds"""
44
return time.strftime("%Y-%m-%dT%H" + globals.time_separator +
45
"%M" + globals.time_separator + "%S",
46
time.localtime(timeinseconds)) + gettzd()
48
def stringtotime(timestring):
49
"""Return time in seconds from w3 timestring
51
If there is an error parsing the string, or it doesn't look
52
like a w3 datetime string, return None.
56
date, daytime = timestring[:19].split("T")
57
year, month, day = map(int, date.split("-"))
58
hour, minute, second = map(int,
59
daytime.split(globals.time_separator))
60
assert 1900 < year < 2100, year
61
assert 1 <= month <= 12
63
assert 0 <= hour <= 23
64
assert 0 <= minute <= 59
65
assert 0 <= second <= 61 # leap seconds
66
timetuple = (year, month, day, hour, minute, second, -1, -1, -1)
68
utc_in_secs = time.mktime(timetuple) - time.altzone
69
else: utc_in_secs = time.mktime(timetuple) - time.timezone
71
return long(utc_in_secs) + tzdtoseconds(timestring[19:])
72
except (TypeError, ValueError, AssertionError): return None
75
def timetopretty(timeinseconds):
76
"""Return pretty version of time"""
77
return time.asctime(time.localtime(timeinseconds))
79
def stringtopretty(timestring):
80
"""Return pretty version of time given w3 time string"""
81
return timetopretty(stringtotime(timestring))
83
def inttopretty(seconds):
84
"""Convert num of seconds to readable string like "2 hours"."""
86
hours, seconds = divmod(seconds, 3600)
87
if hours > 1: partlist.append("%d hours" % hours)
88
elif hours == 1: partlist.append("1 hour")
90
minutes, seconds = divmod(seconds, 60)
91
if minutes > 1: partlist.append("%d minutes" % minutes)
92
elif minutes == 1: partlist.append("1 minute")
94
if seconds == 1: partlist.append("1 second")
95
elif not partlist or seconds > 1:
96
if isinstance(seconds, int) or isinstance(seconds, long):
97
partlist.append("%s seconds" % seconds)
98
else: partlist.append("%.2f seconds" % seconds)
99
return " ".join(partlist)
101
def intstringtoseconds(interval_string):
102
"""Convert a string expressing an interval (e.g. "4D2s") to seconds"""
104
raise TimeException("""Bad interval string "%s"
106
Intervals are specified like 2Y (2 years) or 2h30m (2.5 hours). The
107
allowed special characters are s, m, h, D, W, M, and Y. See the man
108
page for more information.
109
""" % interval_string)
110
if len(interval_string) < 2: error()
113
while interval_string:
114
match = _interval_regexp.match(interval_string)
115
if not match: error()
116
num, ext = int(match.group(1)), match.group(2)
117
if not ext in _interval_conv_dict or num < 0: error()
118
total += num*_interval_conv_dict[ext]
119
interval_string = interval_string[match.end(0):]
123
"""Return w3's timezone identification string.
125
Expresed as [+/-]hh:mm. For instance, PST is -08:00. Zone is
126
coincides with what localtime(), etc., use.
129
if time.daylight: offset = -1 * time.altzone/60
130
else: offset = -1 * time.timezone/60
131
if offset > 0: prefix = "+"
132
elif offset < 0: prefix = "-"
133
else: return "Z" # time is already in UTC
135
hours, minutes = map(abs, divmod(offset, 60))
136
assert 0 <= hours <= 23
137
assert 0 <= minutes <= 59
138
return "%s%02d%s%02d" % (prefix, hours, globals.time_separator, minutes)
140
def tzdtoseconds(tzd):
141
"""Given w3 compliant TZD, return how far ahead UTC is"""
142
if tzd == "Z": return 0
143
assert len(tzd) == 6 # only accept forms like +08:00 for now
144
assert (tzd[0] == "-" or tzd[0] == "+") and \
145
tzd[3] == globals.time_separator
146
return -60 * (60 * int(tzd[:3]) + int(tzd[4:]))
148
def cmp(time1, time2):
149
"""Compare time1 and time2 and return -1, 0, or 1"""
150
if type(time1) is types.StringType:
151
time1 = stringtotime(time1)
152
assert time1 is not None
153
if type(time2) is types.StringType:
154
time2 = stringtotime(time2)
155
assert time2 is not None
157
if time1 < time2: return -1
158
elif time1 == time2: return 0
162
def sleep(sleep_ratio):
163
"""Sleep for period to maintain given sleep_ratio
165
On my system sleeping for periods less than 1/20th of a second
166
doesn't seem to work very accurately, so accumulate at least that
167
much time before sleeping.
170
global been_awake_since
171
if been_awake_since is None: # first running
172
been_awake_since = time.time()
174
elapsed_time = time.time() - been_awake_since
175
sleep_time = elapsed_time * (sleep_ratio/(1-sleep_ratio))
176
if sleep_time >= 0.05:
177
time.sleep(sleep_time)
178
been_awake_since = time.time()
180
def genstrtotime(timestr, override_curtime = None):
181
"""Convert a generic time string to a time in seconds"""
182
if override_curtime is None: override_curtime = curtime
183
if timestr == "now": return override_curtime
186
raise TimeException("""Bad time string "%s"
188
The acceptible time strings are intervals (like "3D64s"), w3-datetime
189
strings, like "2002-04-26T04:22:01-07:00" (strings like
190
"2002-04-26T04:22:01" are also acceptable - rdiff-backup will use the
191
current time zone), or ordinary dates like 2/4/1997 or 2001-04-23
192
(various combinations are acceptable, but the month always precedes
193
the day).""" % timestr)
195
# Test for straight integer
196
if _integer_regexp.search(timestr): return int(timestr)
198
# Test for w3-datetime format, possibly missing tzd
199
t = stringtotime(timestr) or stringtotime(timestr+gettzd())
202
try: # test for an interval, like "2 days ago"
203
return override_curtime - intstringtoseconds(timestr)
204
except TimeException: pass
206
# Now check for dates like 2001/3/23
207
match = _genstr_date_regexp1.search(timestr) or \
208
_genstr_date_regexp2.search(timestr)
209
if not match: error()
210
timestr = "%s-%02d-%02dT00:00:00%s" % (match.group('year'),
211
int(match.group('month')), int(match.group('day')), gettzd())
212
t = stringtotime(timestr)