Mercurial > p > roundup > code
comparison roundup/date.py @ 1597:4d55f90d4af1
granularity based ranges
| author | Andrey Lebedev <kedder@users.sourceforge.net> |
|---|---|
| date | Mon, 21 Apr 2003 14:29:40 +0000 |
| parents | 3faabaeab6aa |
| children | cc96bf971b33 |
comparison
equal
deleted
inserted
replaced
| 1596:33a0d94c7658 | 1597:4d55f90d4af1 |
|---|---|
| 13 # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | 13 # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
| 14 # FOR A PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" | 14 # FOR A PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" |
| 15 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, | 15 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, |
| 16 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. | 16 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. |
| 17 # | 17 # |
| 18 # $Id: date.py,v 1.51 2003-03-22 22:43:21 richard Exp $ | 18 # $Id: date.py,v 1.52 2003-04-21 14:29:39 kedder Exp $ |
| 19 | 19 |
| 20 __doc__ = """ | 20 __doc__ = """ |
| 21 Date, time and time interval handling. | 21 Date, time and time interval handling. |
| 22 """ | 22 """ |
| 23 | 23 |
| 24 import time, re, calendar, types | 24 import time, re, calendar, types |
| 25 from i18n import _ | 25 from i18n import _ |
| 26 | |
| 27 def _add_granularity(src, order, value = 1): | |
| 28 '''Increment first non-None value in src dictionary ordered by 'order' | |
| 29 parameter | |
| 30 ''' | |
| 31 for gran in order: | |
| 32 if src[gran]: | |
| 33 src[gran] = int(src[gran]) + value | |
| 34 break | |
| 26 | 35 |
| 27 class Date: | 36 class Date: |
| 28 ''' | 37 ''' |
| 29 As strings, date-and-time stamps are specified with the date in | 38 As strings, date-and-time stamps are specified with the date in |
| 30 international standard format (yyyy-mm-dd) joined to the time | 39 international standard format (yyyy-mm-dd) joined to the time |
| 78 | 87 |
| 79 The date format 'yyyymmddHHMMSS' (year, month, day, hour, | 88 The date format 'yyyymmddHHMMSS' (year, month, day, hour, |
| 80 minute, second) is the serialisation format returned by the serialise() | 89 minute, second) is the serialisation format returned by the serialise() |
| 81 method, and is accepted as an argument on instatiation. | 90 method, and is accepted as an argument on instatiation. |
| 82 ''' | 91 ''' |
| 83 def __init__(self, spec='.', offset=0): | 92 def __init__(self, spec='.', offset=0, add_granularity=0): |
| 84 """Construct a date given a specification and a time zone offset. | 93 """Construct a date given a specification and a time zone offset. |
| 85 | 94 |
| 86 'spec' is a full date or a partial form, with an optional | 95 'spec' is a full date or a partial form, with an optional |
| 87 added or subtracted interval. Or a date 9-tuple. | 96 added or subtracted interval. Or a date 9-tuple. |
| 88 'offset' is the local time zone offset from GMT in hours. | 97 'offset' is the local time zone offset from GMT in hours. |
| 89 """ | 98 """ |
| 90 if type(spec) == type(''): | 99 if type(spec) == type(''): |
| 91 self.set(spec, offset=offset) | 100 self.set(spec, offset=offset, add_granularity=add_granularity) |
| 92 else: | 101 else: |
| 93 y,m,d,H,M,S,x,x,x = spec | 102 y,m,d,H,M,S,x,x,x = spec |
| 94 ts = calendar.timegm((y,m,d,H+offset,M,S,0,0,0)) | 103 ts = calendar.timegm((y,m,d,H+offset,M,S,0,0,0)) |
| 95 self.year, self.month, self.day, self.hour, self.minute, \ | 104 self.year, self.month, self.day, self.hour, self.minute, \ |
| 96 self.second, x, x, x = time.gmtime(ts) | 105 self.second, x, x, x = time.gmtime(ts) |
| 100 (?P<n>\.)? # . | 109 (?P<n>\.)? # . |
| 101 (((?P<H>\d?\d):(?P<M>\d\d))?(:(?P<S>\d\d))?)? # hh:mm:ss | 110 (((?P<H>\d?\d):(?P<M>\d\d))?(:(?P<S>\d\d))?)? # hh:mm:ss |
| 102 (?P<o>.+)? # offset | 111 (?P<o>.+)? # offset |
| 103 ''', re.VERBOSE), serialised_re=re.compile(r''' | 112 ''', re.VERBOSE), serialised_re=re.compile(r''' |
| 104 (\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d) | 113 (\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d) |
| 105 ''', re.VERBOSE)): | 114 ''', re.VERBOSE), add_granularity=0): |
| 106 ''' set the date to the value in spec | 115 ''' set the date to the value in spec |
| 107 ''' | 116 ''' |
| 117 | |
| 108 m = serialised_re.match(spec) | 118 m = serialised_re.match(spec) |
| 109 if m is not None: | 119 if m is not None: |
| 110 # we're serialised - easy! | 120 # we're serialised - easy! |
| 111 self.year, self.month, self.day, self.hour, self.minute, \ | 121 self.year, self.month, self.day, self.hour, self.minute, \ |
| 112 self.second = map(int, m.groups()[:6]) | 122 self.second = map(int, m.groups()[:6]) |
| 117 if m is None: | 127 if m is None: |
| 118 raise ValueError, _('Not a date spec: [[yyyy-]mm-dd].' | 128 raise ValueError, _('Not a date spec: [[yyyy-]mm-dd].' |
| 119 '[[h]h:mm[:ss]][offset]') | 129 '[[h]h:mm[:ss]][offset]') |
| 120 | 130 |
| 121 info = m.groupdict() | 131 info = m.groupdict() |
| 132 | |
| 133 if add_granularity: | |
| 134 _add_granularity(info, 'SMHdmy') | |
| 122 | 135 |
| 123 # get the current date as our default | 136 # get the current date as our default |
| 124 y,m,d,H,M,S,x,x,x = time.gmtime(time.time()) | 137 y,m,d,H,M,S,x,x,x = time.gmtime(time.time()) |
| 125 | 138 |
| 126 # override year, month, day parts | 139 # override year, month, day parts |
| 138 H = int(info['H']) - offset | 151 H = int(info['H']) - offset |
| 139 M = int(info['M']) | 152 M = int(info['M']) |
| 140 S = 0 | 153 S = 0 |
| 141 if info['S'] is not None: S = int(info['S']) | 154 if info['S'] is not None: S = int(info['S']) |
| 142 | 155 |
| 156 if add_granularity: | |
| 157 S = S - 1 | |
| 158 | |
| 143 # now handle the adjustment of hour | 159 # now handle the adjustment of hour |
| 144 ts = calendar.timegm((y,m,d,H,M,S,0,0,0)) | 160 ts = calendar.timegm((y,m,d,H,M,S,0,0,0)) |
| 145 self.year, self.month, self.day, self.hour, self.minute, \ | 161 self.year, self.month, self.day, self.hour, self.minute, \ |
| 146 self.second, x, x, x = time.gmtime(ts) | 162 self.second, x, x, x = time.gmtime(ts) |
| 147 | 163 |
| 343 minute, second) is the serialisation format returned by the serialise() | 359 minute, second) is the serialisation format returned by the serialise() |
| 344 method, and is accepted as an argument on instatiation. | 360 method, and is accepted as an argument on instatiation. |
| 345 | 361 |
| 346 TODO: more examples, showing the order of addition operation | 362 TODO: more examples, showing the order of addition operation |
| 347 ''' | 363 ''' |
| 348 def __init__(self, spec, sign=1, allowdate=1): | 364 def __init__(self, spec, sign=1, allowdate=1, add_granularity=0): |
| 349 """Construct an interval given a specification.""" | 365 """Construct an interval given a specification.""" |
| 350 if type(spec) == type(''): | 366 if type(spec) == type(''): |
| 351 self.set(spec, allowdate) | 367 self.set(spec, allowdate=allowdate, add_granularity=add_granularity) |
| 352 else: | 368 else: |
| 353 if len(spec) == 7: | 369 if len(spec) == 7: |
| 354 self.sign, self.year, self.month, self.day, self.hour, \ | 370 self.sign, self.year, self.month, self.day, self.hour, \ |
| 355 self.minute, self.second = spec | 371 self.minute, self.second = spec |
| 356 else: | 372 else: |
| 370 (\d\d\d\d[/-])?(\d\d?)?[/-](\d\d?)? # [yyyy-]mm-dd | 386 (\d\d\d\d[/-])?(\d\d?)?[/-](\d\d?)? # [yyyy-]mm-dd |
| 371 \.? # . | 387 \.? # . |
| 372 (\d?\d:\d\d)?(:\d\d)? # hh:mm:ss | 388 (\d?\d:\d\d)?(:\d\d)? # hh:mm:ss |
| 373 )?''', re.VERBOSE), serialised_re=re.compile(''' | 389 )?''', re.VERBOSE), serialised_re=re.compile(''' |
| 374 (?P<s>[+-])?1?(?P<y>([ ]{3}\d|\d{4}))(?P<m>\d{2})(?P<d>\d{2}) | 390 (?P<s>[+-])?1?(?P<y>([ ]{3}\d|\d{4}))(?P<m>\d{2})(?P<d>\d{2}) |
| 375 (?P<H>\d{2})(?P<M>\d{2})(?P<S>\d{2})''', re.VERBOSE)): | 391 (?P<H>\d{2})(?P<M>\d{2})(?P<S>\d{2})''', re.VERBOSE), |
| 392 add_granularity=0): | |
| 376 ''' set the date to the value in spec | 393 ''' set the date to the value in spec |
| 377 ''' | 394 ''' |
| 378 self.year = self.month = self.week = self.day = self.hour = \ | 395 self.year = self.month = self.week = self.day = self.hour = \ |
| 379 self.minute = self.second = 0 | 396 self.minute = self.second = 0 |
| 380 self.sign = 1 | 397 self.sign = 1 |
| 387 else: | 404 else: |
| 388 allowdate = 0 | 405 allowdate = 0 |
| 389 | 406 |
| 390 # pull out all the info specified | 407 # pull out all the info specified |
| 391 info = m.groupdict() | 408 info = m.groupdict() |
| 409 if add_granularity: | |
| 410 _add_granularity(info, 'SMHdwmy', (info['s']=='-' and -1 or 1)) | |
| 411 | |
| 392 valid = 0 | 412 valid = 0 |
| 393 for group, attr in {'y':'year', 'm':'month', 'w':'week', 'd':'day', | 413 for group, attr in {'y':'year', 'm':'month', 'w':'week', 'd':'day', |
| 394 'H':'hour', 'M':'minute', 'S':'second'}.items(): | 414 'H':'hour', 'M':'minute', 'S':'second'}.items(): |
| 395 if info.get(group, None) is not None: | 415 if info.get(group, None) is not None: |
| 396 valid = 1 | 416 valid = 1 |
| 652 | 672 |
| 653 >>> Range("; 20:00 +1d") | 673 >>> Range("; 20:00 +1d") |
| 654 <Range from None to 2003-03-09.20:00:00> | 674 <Range from None to 2003-03-09.20:00:00> |
| 655 | 675 |
| 656 """ | 676 """ |
| 657 def __init__(self, spec, Type, **params): | 677 def __init__(self, spec, Type, allow_granularity=1, **params): |
| 658 """Initializes Range of type <Type> from given <spec> string. | 678 """Initializes Range of type <Type> from given <spec> string. |
| 659 | 679 |
| 660 Sets two properties - from_value and to_value. None assigned to any of | 680 Sets two properties - from_value and to_value. None assigned to any of |
| 661 this properties means "infinitum" (-infinitum to from_value and | 681 this properties means "infinitum" (-infinitum to from_value and |
| 662 +infinitum to to_value) | 682 +infinitum to to_value) |
| 664 The Type parameter here should be class itself (e.g. Date), not a | 684 The Type parameter here should be class itself (e.g. Date), not a |
| 665 class instance. | 685 class instance. |
| 666 | 686 |
| 667 """ | 687 """ |
| 668 self.range_type = Type | 688 self.range_type = Type |
| 669 re_range = r'(?:^|(?:from)?(.+?))(?:to(.+?)$|$)' | 689 re_range = r'(?:^|from(.+?))(?:to(.+?)$|$)' |
| 670 re_geek_range = r'(?:^|(.+?))(?:;(.+?)$|$)' | 690 re_geek_range = r'(?:^|(.+?));(?:(.+?)$|$)' |
| 671 # Check which syntax to use | 691 # Check which syntax to use |
| 672 if spec.find(';') == -1: | 692 if spec.find(';') == -1: |
| 673 # Native english | 693 # Native english |
| 674 mch_range = re.search(re_range, spec.strip(), re.IGNORECASE) | 694 mch_range = re.search(re_range, spec.strip(), re.IGNORECASE) |
| 675 else: | 695 else: |
| 680 if self.from_value: | 700 if self.from_value: |
| 681 self.from_value = Type(self.from_value.strip(), **params) | 701 self.from_value = Type(self.from_value.strip(), **params) |
| 682 if self.to_value: | 702 if self.to_value: |
| 683 self.to_value = Type(self.to_value.strip(), **params) | 703 self.to_value = Type(self.to_value.strip(), **params) |
| 684 else: | 704 else: |
| 685 raise ValueError, "Invalid range" | 705 if allow_granularity: |
| 706 self.from_value = Type(spec, **params) | |
| 707 self.to_value = Type(spec, add_granularity=1, **params) | |
| 708 else: | |
| 709 raise ValueError, "Invalid range" | |
| 686 | 710 |
| 687 def __str__(self): | 711 def __str__(self): |
| 688 return "from %s to %s" % (self.from_value, self.to_value) | 712 return "from %s to %s" % (self.from_value, self.to_value) |
| 689 | 713 |
| 690 def __repr__(self): | 714 def __repr__(self): |
| 691 return "<Range %s>" % self.__str__() | 715 return "<Range %s>" % self.__str__() |
| 692 | 716 |
| 693 def test_range(): | 717 def test_range(): |
| 694 rspecs = ("from 2-12 to 4-2", "18:00 TO +2m", "12:00", "tO +3d", | 718 rspecs = ("from 2-12 to 4-2", "from 18:00 TO +2m", "12:00;", "tO +3d", |
| 695 "2002-11-10; 2002-12-12", "; 20:00 +1d") | 719 "2002-11-10; 2002-12-12", "; 20:00 +1d", '2002-10-12') |
| 720 rispecs = ('from -1w 2d 4:32 to 4d', '-2w 1d') | |
| 696 for rspec in rspecs: | 721 for rspec in rspecs: |
| 697 print '>>> Range("%s")' % rspec | 722 print '>>> Range("%s")' % rspec |
| 698 print `Range(rspec, Date)` | 723 print `Range(rspec, Date)` |
| 724 print | |
| 725 for rspec in rispecs: | |
| 726 print '>>> Range("%s")' % rspec | |
| 727 print `Range(rspec, Interval)` | |
| 699 print | 728 print |
| 700 | 729 |
| 701 def test(): | 730 def test(): |
| 702 intervals = (" 3w 1 d 2:00", " + 2d", "3w") | 731 intervals = (" 3w 1 d 2:00", " + 2d", "3w") |
| 703 for interval in intervals: | 732 for interval in intervals: |
| 704 print '>>> Interval("%s")'%interval | 733 print '>>> Interval("%s")'%interval |
| 705 print `Interval(interval)` | 734 print `Interval(interval)` |
| 706 | 735 |
| 707 dates = (".", "2000-06-25.19:34:02", ". + 2d", "1997-04-17", "01-25", | 736 dates = (".", "2000-06-25.19:34:02", ". + 2d", "1997-04-17", "01-25", |
| 708 "08-13.22:13", "14:25") | 737 "08-13.22:13", "14:25", '2002-12') |
| 709 for date in dates: | 738 for date in dates: |
| 710 print '>>> Date("%s")'%date | 739 print '>>> Date("%s")'%date |
| 711 print `Date(date)` | 740 print `Date(date)` |
| 712 | 741 |
| 713 sums = ((". + 2d", "3w"), (".", " 3w 1 d 2:00")) | 742 sums = ((". + 2d", "3w"), (".", " 3w 1 d 2:00")) |
| 714 for date, interval in sums: | 743 for date, interval in sums: |
| 715 print '>>> Date("%s") + Interval("%s")'%(date, interval) | 744 print '>>> Date("%s") + Interval("%s")'%(date, interval) |
| 716 print `Date(date) + Interval(interval)` | 745 print `Date(date) + Interval(interval)` |
| 717 | 746 |
| 718 if __name__ == '__main__': | 747 if __name__ == '__main__': |
| 719 test_range() | 748 test() |
| 720 | 749 |
| 721 # vim: set filetype=python ts=4 sw=4 et si | 750 # vim: set filetype=python ts=4 sw=4 et si |
