| 1 | """ |
|---|
| 2 | DateInterval.py |
|---|
| 3 | |
|---|
| 4 | Convert interval strings (in the form of 1w2d, etc) to |
|---|
| 5 | seconds, and back again. Is not exactly about months or |
|---|
| 6 | years (leap years in particular). |
|---|
| 7 | |
|---|
| 8 | Accepts (y)ear, (b)month, (w)eek, (d)ay, (h)our, (m)inute, (s)econd. |
|---|
| 9 | |
|---|
| 10 | Exports only timeEncode and timeDecode functions. |
|---|
| 11 | """ |
|---|
| 12 | |
|---|
| 13 | import re |
|---|
| 14 | |
|---|
| 15 | __all__ = ['interval_decode', 'interval_encode'] |
|---|
| 16 | |
|---|
| 17 | second = 1 |
|---|
| 18 | minute = second*60 |
|---|
| 19 | hour = minute*60 |
|---|
| 20 | day = hour*24 |
|---|
| 21 | week = day*7 |
|---|
| 22 | month = day*30 |
|---|
| 23 | year = day*365 |
|---|
| 24 | timeValues = { |
|---|
| 25 | 'y': year, |
|---|
| 26 | 'b': month, |
|---|
| 27 | 'w': week, |
|---|
| 28 | 'd': day, |
|---|
| 29 | 'h': hour, |
|---|
| 30 | 'm': minute, |
|---|
| 31 | 's': second, |
|---|
| 32 | } |
|---|
| 33 | timeOrdered = timeValues.items() |
|---|
| 34 | timeOrdered.sort(lambda a, b: -cmp(a[1], b[1])) |
|---|
| 35 | |
|---|
| 36 | def interval_encode(seconds, include_sign=False): |
|---|
| 37 | """Encodes a number of seconds (representing a time interval) |
|---|
| 38 | into a form like 1h2d3s. |
|---|
| 39 | |
|---|
| 40 | >>> interval_encode(10) |
|---|
| 41 | '10s' |
|---|
| 42 | >>> interval_encode(493939) |
|---|
| 43 | '5d17h12m19s' |
|---|
| 44 | """ |
|---|
| 45 | s = '' |
|---|
| 46 | orig = seconds |
|---|
| 47 | seconds = abs(seconds) |
|---|
| 48 | for char, amount in timeOrdered: |
|---|
| 49 | if seconds >= amount: |
|---|
| 50 | i, seconds = divmod(seconds, amount) |
|---|
| 51 | s += '%i%s' % (i, char) |
|---|
| 52 | if orig < 0: |
|---|
| 53 | s = '-' + s |
|---|
| 54 | elif not orig: |
|---|
| 55 | return '0' |
|---|
| 56 | elif include_sign: |
|---|
| 57 | s = '+' + s |
|---|
| 58 | return s |
|---|
| 59 | |
|---|
| 60 | _timeRE = re.compile(r'[0-9]+[a-zA-Z]') |
|---|
| 61 | def interval_decode(s): |
|---|
| 62 | """Decodes a number in the format 1h4d3m (1 hour, 3 days, 3 minutes) |
|---|
| 63 | into a number of seconds |
|---|
| 64 | |
|---|
| 65 | >>> interval_decode('40s') |
|---|
| 66 | 40 |
|---|
| 67 | >>> interval_decode('10000s') |
|---|
| 68 | 10000 |
|---|
| 69 | >>> interval_decode('3d1w45s') |
|---|
| 70 | 864045 |
|---|
| 71 | """ |
|---|
| 72 | time = 0 |
|---|
| 73 | sign = 1 |
|---|
| 74 | s = s.strip() |
|---|
| 75 | if s.startswith('-'): |
|---|
| 76 | s = s[1:] |
|---|
| 77 | sign = -1 |
|---|
| 78 | elif s.startswith('+'): |
|---|
| 79 | s = s[1:] |
|---|
| 80 | for match in allMatches(s, _timeRE): |
|---|
| 81 | char = match.group(0)[-1].lower() |
|---|
| 82 | if not timeValues.has_key(char): |
|---|
| 83 | # @@: should signal error |
|---|
| 84 | continue |
|---|
| 85 | time += int(match.group(0)[:-1]) * timeValues[char] |
|---|
| 86 | return time |
|---|
| 87 | |
|---|
| 88 | # @@-sgd 2002-12-23 - this function does not belong in this module, find a better place. |
|---|
| 89 | def allMatches(source, regex): |
|---|
| 90 | """Return a list of matches for regex in source |
|---|
| 91 | """ |
|---|
| 92 | pos = 0 |
|---|
| 93 | end = len(source) |
|---|
| 94 | rv = [] |
|---|
| 95 | match = regex.search(source, pos) |
|---|
| 96 | while match: |
|---|
| 97 | rv.append(match) |
|---|
| 98 | match = regex.search(source, match.end() ) |
|---|
| 99 | return rv |
|---|
| 100 | |
|---|
| 101 | if __name__ == '__main__': |
|---|
| 102 | import doctest |
|---|
| 103 | doctest.testmod() |
|---|