Package duplicity :: Module dup_time
[hide private]
[frames] | no frames]

Source Code for Module duplicity.dup_time

  1  # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- 
  2  # 
  3  # Copyright 2002 Ben Escoto <ben@emerose.org> 
  4  # Copyright 2007 Kenneth Loafman <kenneth@loafman.com> 
  5  # 
  6  # This file is part of duplicity. 
  7  # 
  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. 
 12  # 
 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. 
 17  # 
 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 
 21   
 22  """Provide time related exceptions and functions""" 
 23   
 24  import time, types, re, calendar 
 25  from duplicity import globals 
 26   
 27   
28 -class TimeException(Exception):
29 pass
30 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})" 42 "(?P<month>[0-9]{2})" 43 "(?P<day>[0-9]{2})Z$") 44 curtime = curtimestr = None 45 prevtime = prevtimestr = None 46 47 bad_interval_string = _("""Bad interval string "%s" 48 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.""") 52 53 bad_time_string = _("""Bad time string "%s" 54 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 60 the day).""") 61
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)
68
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)
74
75 -def timetostring(timeinseconds):
76 """Return w3 or duplicity datetime compliant listing of timeinseconds""" 77 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]) 86 else: 87 # DST never applies to UTC 88 lcltime = time.gmtime(timeinseconds) 89 return time.strftime("%Y%m%dT%H%M%SZ", lcltime)
90
91 -def stringtotime(timestring):
92 """Return time in seconds from w3 or duplicity timestring 93 94 If there is an error parsing the string, or it doesn't look 95 like a valid datetime string, return None. 96 """ 97 try: 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]]) 105 else: 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) 121 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) 128 else: 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 132 # off in the tuple 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 138 # string 139 if len(timestring) == 16: 140 return long(utc_in_secs) 141 else: 142 return long(utc_in_secs + tzdtoseconds(timestring[19:])) 143 except (TypeError, ValueError, AssertionError): 144 return None
145
146 -def timetopretty(timeinseconds):
147 """Return pretty version of time""" 148 return time.asctime(time.localtime(timeinseconds))
149
150 -def stringtopretty(timestring):
151 """Return pretty version of time given w3 time string""" 152 return timetopretty(stringtotime(timestring))
153
154 -def inttopretty(seconds):
155 """Convert num of seconds to readable string like "2 hours".""" 156 partlist = [] 157 hours, seconds = divmod(seconds, 3600) 158 if hours > 1: 159 partlist.append("%d hours" % hours) 160 elif hours == 1: 161 partlist.append("1 hour") 162 163 minutes, seconds = divmod(seconds, 60) 164 if minutes > 1: 165 partlist.append("%d minutes" % minutes) 166 elif minutes == 1: 167 partlist.append("1 minute") 168 169 if seconds == 1: 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) 174 else: 175 partlist.append("%.2f seconds" % seconds) 176 return " ".join(partlist)
177
178 -def intstringtoseconds(interval_string):
179 """Convert a string expressing an interval (e.g. "4D2s") to seconds""" 180 def error(): 181 raise TimeException(bad_interval_string % interval_string)
182 183 if len(interval_string) < 2: 184 error() 185 186 total = 0 187 while interval_string: 188 match = _interval_regexp.match(interval_string) 189 if not match: 190 error() 191 num, ext = int(match.group(1)), match.group(2) 192 if not ext in _interval_conv_dict or num < 0: 193 error() 194 total += num*_interval_conv_dict[ext] 195 interval_string = interval_string[match.end(0):] 196 return total 197
198 -def gettzd(dstflag):
199 """Return w3's timezone identification string. 200 201 Expresed as [+/-]hh:mm. For instance, PST is -08:00. Zone is 202 coincides with what localtime(), etc., use. 203 204 """ 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 209 # time.localtime() 210 211 if dstflag > 0: 212 offset = -1 * time.altzone/60 213 else: 214 offset = -1 * time.timezone/60 215 if offset > 0: 216 prefix = "+" 217 elif offset < 0: 218 prefix = "-" 219 else: 220 return "Z" # time is already in UTC 221 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)
226
227 -def tzdtoseconds(tzd):
228 """Given w3 compliant TZD, return how far ahead UTC is""" 229 if tzd == "Z": 230 return 0 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:]))
235
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 244 245 if time1 < time2: 246 return -1 247 elif time1 == time2: 248 return 0 249 else: 250 return 1
251
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 256 if timestr == "now": 257 return override_curtime 258 259 def error(): 260 raise TimeException(bad_time_string % timestr)
261 262 # Test for straight integer 263 if _integer_regexp.search(timestr): 264 return int(timestr) 265 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 272 273 t = stringtotime(timestr) or stringtotime(timestr+gettzd(0)) 274 if t: 275 return t 276 277 try: # test for an interval, like "2 days ago" 278 return override_curtime - intstringtoseconds(timestr) 279 except TimeException: 280 pass 281 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) 286 if not match: 287 error() 288 timestr = "%s-%02d-%02dT00:00:00%s" % (match.group('year'), 289 int(match.group('month')), 290 int(match.group('day')), 291 gettzd(0)) 292 t = stringtotime(timestr) 293 if t: 294 return t 295 else: 296 error() 297