~ubuntu-branches/ubuntu/trusty/duplicity/trusty

« back to all changes in this revision

Viewing changes to duplicity/dup_time.py

  • Committer: Package Import Robot
  • Author(s): Michael Terry
  • Date: 2011-12-06 14:15:01 UTC
  • mfrom: (1.9.4)
  • Revision ID: package-import@ubuntu.com-20111206141501-nvfaaauqivpwyb7f
Tags: 0.6.17-0ubuntu1
* New upstream release
* debian/patches/06_use_passphrase.dpatch,
  debian/patches/07_large_rackspace_list.dpatch,
  debian/patches/08_check_volumes.dpatch:
  - Dropped, applied upstream
* debian/rules:
  - Run new upstream test suite during build
* debian/control:
  - Add rdiff as a build-dep to run above test suite
* debian/patches/06testfixes.dpatch:
  - Fix a few tests to not fail erroneously
* debian/patches/07fixincresume.dpatch:
  - Fix a bug with resuming an incremental backup that would result in
    a bogus error.  Also patches in a test for it.
* debian/tests/full-cycle-local:
  - New DEP-8 test script that backs up locally, restores, and checks files
* debian/tests/full-cycle-u1:
  - New DEP-8 test script that does the same as above, but to Ubuntu One
* debian/tests/control:
  - Start of DEP-8 test suite.  Only enable above full-cycle-local test
    for automatic execution.  The other is for manual testing right now.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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()