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

« back to all changes in this revision

Viewing changes to src/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
25
 
 
26
 
from duplicity import globals
27
 
 
28
 
 
29
 
class TimeException(Exception):
30
 
    pass
31
 
 
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})"
43
 
                                  "(?P<month>[0-9]{2})"
44
 
                                  "(?P<day>[0-9]{2})Z$")
45
 
curtime = curtimestr = None
46
 
prevtime = prevtimestr = None
47
 
 
48
 
bad_interval_string = _("""Bad interval string "%s"
49
 
 
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.""")
53
 
 
54
 
bad_time_string = _("""Bad time string "%s"
55
 
 
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
61
 
the day).""")
62
 
 
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)
69
 
 
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)
75
 
 
76
 
def timetostring(timeinseconds):
77
 
    """Return w3 or duplicity datetime compliant listing of timeinseconds"""
78
 
 
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])
87
 
    else:
88
 
        # DST never applies to UTC
89
 
        lcltime = time.gmtime(timeinseconds)
90
 
        return time.strftime("%Y%m%dT%H%M%SZ", lcltime)
91
 
 
92
 
def stringtotime(timestring):
93
 
    """Return time in seconds from w3 or duplicity timestring
94
 
 
95
 
    If there is an error parsing the string, or it doesn't look
96
 
    like a valid datetime string, return None.
97
 
    """
98
 
    try:
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]])
106
 
        else:
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
126
 
        # off in the tuple
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
131
 
        # string
132
 
        if len(timestring) == 16:
133
 
            return long(utc_in_secs)
134
 
        else:
135
 
            return long(utc_in_secs + tzdtoseconds(timestring[19:]))
136
 
    except (TypeError, ValueError, AssertionError):
137
 
        return None
138
 
 
139
 
def timetopretty(timeinseconds):
140
 
    """Return pretty version of time"""
141
 
    return time.asctime(time.localtime(timeinseconds))
142
 
 
143
 
def stringtopretty(timestring):
144
 
    """Return pretty version of time given w3 time string"""
145
 
    return timetopretty(stringtotime(timestring))
146
 
 
147
 
def inttopretty(seconds):
148
 
    """Convert num of seconds to readable string like "2 hours"."""
149
 
    partlist = []
150
 
    hours, seconds = divmod(seconds, 3600)
151
 
    if hours > 1:
152
 
        partlist.append("%d hours" % hours)
153
 
    elif hours == 1:
154
 
        partlist.append("1 hour")
155
 
 
156
 
    minutes, seconds = divmod(seconds, 60)
157
 
    if minutes > 1:
158
 
        partlist.append("%d minutes" % minutes)
159
 
    elif minutes == 1:
160
 
        partlist.append("1 minute")
161
 
 
162
 
    if seconds == 1:
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)
167
 
        else:
168
 
            partlist.append("%.2f seconds" % seconds)
169
 
    return " ".join(partlist)
170
 
 
171
 
def intstringtoseconds(interval_string):
172
 
    """Convert a string expressing an interval (e.g. "4D2s") to seconds"""
173
 
    def error():
174
 
        raise TimeException(bad_interval_string % interval_string)
175
 
 
176
 
    if len(interval_string) < 2:
177
 
        error()
178
 
 
179
 
    total = 0
180
 
    while interval_string:
181
 
        match = _interval_regexp.match(interval_string)
182
 
        if not match:
183
 
            error()
184
 
        num, ext = int(match.group(1)), match.group(2)
185
 
        if not ext in _interval_conv_dict or num < 0:
186
 
            error()
187
 
        total += num*_interval_conv_dict[ext]
188
 
        interval_string = interval_string[match.end(0):]
189
 
    return total
190
 
 
191
 
def gettzd(dstflag):
192
 
    """Return w3's timezone identification string.
193
 
 
194
 
    Expresed as [+/-]hh:mm.  For instance, PST is -08:00.  Zone is
195
 
    coincides with what localtime(), etc., use.
196
 
 
197
 
    """
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
202
 
    # time.localtime()
203
 
 
204
 
    if dstflag > 0:
205
 
        offset = -1 * time.altzone/60
206
 
    else:
207
 
        offset = -1 * time.timezone/60
208
 
    if offset > 0:
209
 
        prefix = "+"
210
 
    elif offset < 0:
211
 
        prefix = "-"
212
 
    else:
213
 
        return "Z" # time is already in UTC
214
 
 
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)
219
 
 
220
 
def tzdtoseconds(tzd):
221
 
    """Given w3 compliant TZD, return how far ahead UTC is"""
222
 
    if tzd == "Z":
223
 
        return 0
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:]))
228
 
 
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
237
 
 
238
 
    if time1 < time2:
239
 
        return -1
240
 
    elif time1 == time2:
241
 
        return 0
242
 
    else:
243
 
        return 1
244
 
 
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
249
 
    if timestr == "now":
250
 
        return override_curtime
251
 
 
252
 
    def error():
253
 
        raise TimeException(bad_time_string % timestr)
254
 
 
255
 
    # Test for straight integer
256
 
    if _integer_regexp.search(timestr):
257
 
        return int(timestr)
258
 
 
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
265
 
 
266
 
    t = stringtotime(timestr) or stringtotime(timestr+gettzd(0))
267
 
    if t:
268
 
        return t
269
 
 
270
 
    try: # test for an interval, like "2 days ago"
271
 
        return override_curtime - intstringtoseconds(timestr)
272
 
    except TimeException:
273
 
        pass
274
 
 
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)
279
 
    if not match:
280
 
        error()
281
 
    timestr = "%s-%02d-%02dT00:00:00%s" % (match.group('year'),
282
 
                                           int(match.group('month')),
283
 
                                           int(match.group('day')),
284
 
                                           gettzd(0))
285
 
    t = stringtotime(timestr)
286
 
    if t:
287
 
        return t
288
 
    else:
289
 
        error()