1 # -*- coding: iso-8859-1 -*- 2 """ 3 MoinMoin - DateSupport library (derived from EventAggregatorSupport) 4 5 @copyright: 2008, 2009, 2010, 2011, 2012, 2013 by Paul Boddie <paul@boddie.org.uk> 6 @license: GNU GPL (v2 or later), see COPYING.txt for details. 7 """ 8 9 import calendar 10 import datetime 11 import re 12 import bisect 13 14 try: 15 import pytz 16 except ImportError: 17 pytz = None 18 19 # Date labels. 20 21 month_labels = ["January", "February", "March", "April", "May", "June", 22 "July", "August", "September", "October", "November", "December"] 23 weekday_labels = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] 24 25 # Month, date, time and datetime parsing. 26 27 month_regexp_str = ur'(?P<year>[0-9]{4})-(?P<month>[0-9]{2})' 28 date_regexp_str = ur'(?P<year>[0-9]{4})-(?P<month>[0-9]{2})-(?P<day>[0-9]{2})' 29 time_regexp_str = ur'(?P<hour>[0-2][0-9]):(?P<minute>[0-5][0-9])(?::(?P<second>[0-6][0-9]))?' 30 timezone_offset_str = ur'(?P<offset>(UTC)?(?:(?P<sign>[-+])?(?P<hours>[0-9]{2})(?::?(?P<minutes>[0-9]{2}))?))' 31 timezone_olson_str = ur'(?P<olson>[a-zA-Z]+(?:/[-_a-zA-Z]+){1,2})' 32 timezone_utc_str = ur'UTC' 33 timezone_regexp_str = ur'(?P<zone>' + timezone_offset_str + '|' + timezone_olson_str + '|' + timezone_utc_str + ')' 34 datetime_regexp_str = date_regexp_str + ur'(?:\s+' + time_regexp_str + ur'(?:\s+' + timezone_regexp_str + ur')?)?' 35 36 month_regexp = re.compile(month_regexp_str, re.UNICODE) 37 date_regexp = re.compile(date_regexp_str, re.UNICODE) 38 time_regexp = re.compile(time_regexp_str, re.UNICODE) 39 timezone_olson_regexp = re.compile(timezone_olson_str, re.UNICODE) 40 timezone_offset_regexp = re.compile(timezone_offset_str, re.UNICODE) 41 datetime_regexp = re.compile(datetime_regexp_str, re.UNICODE) 42 43 # iCalendar date and datetime parsing. 44 45 date_icalendar_regexp_str = ur'(?P<year>[0-9]{4})(?P<month>[0-9]{2})(?P<day>[0-9]{2})' 46 datetime_icalendar_regexp_str = date_icalendar_regexp_str + \ 47 ur'(?:' \ 48 ur'T(?P<hour>[0-2][0-9])(?P<minute>[0-5][0-9])(?P<second>[0-6][0-9])' \ 49 ur'(?P<utc>Z)?' \ 50 ur')?' 51 52 # ISO 8601 date and datetime parsing. 53 54 timezone_iso8601_offset_str = ur'(?P<offset>(?:(?P<sign>[-+])(?P<hours>[0-9]{2}):(?P<minutes>[0-9]{2})))' 55 datetime_iso8601_regexp_str = date_regexp_str + \ 56 ur'(?:T' + time_regexp_str + \ 57 ur'(?:(?P<utc>Z)|(?P<zone>' + timezone_iso8601_offset_str + '))' \ 58 ur')?' 59 60 date_icalendar_regexp = re.compile(date_icalendar_regexp_str, re.UNICODE) 61 datetime_icalendar_regexp = re.compile(datetime_icalendar_regexp_str, re.UNICODE) 62 datetime_iso8601_regexp = re.compile(datetime_iso8601_regexp_str, re.UNICODE) 63 64 # Utility functions. 65 66 def sign(x): 67 if x < 0: 68 return -1 69 else: 70 return 1 71 72 def int_or_none(x): 73 if x is None: 74 return x 75 else: 76 return int(x) 77 78 def getMonthLabel(month): 79 80 "Return an unlocalised label for the given 'month'." 81 82 return month_labels[month - 1] # zero-based labels 83 84 def getDayLabel(weekday): 85 86 "Return an unlocalised label for the given 'weekday'." 87 88 return weekday_labels[weekday] 89 90 # Interfaces. 91 92 class ActsAsTimespan: 93 pass 94 95 # Date-related functions. 96 97 def cmp_dates_as_day_start(a, b): 98 99 """ 100 Compare dates/datetimes 'a' and 'b' treating dates without time information 101 as the earliest time in a particular day. 102 """ 103 104 if a == b: 105 a2 = a.as_datetime_or_date() 106 b2 = b.as_datetime_or_date() 107 108 if isinstance(a2, Date) and isinstance(b2, DateTime): 109 return -1 110 elif isinstance(a2, DateTime) and isinstance(b2, Date): 111 return 1 112 113 return cmp(a, b) 114 115 class Convertible: 116 117 "Support for converting temporal objects." 118 119 def _get_converter(self, resolution): 120 if resolution == "month": 121 return lambda x: x and x.as_month() 122 elif resolution == "date": 123 return lambda x: x and x.as_date() 124 elif resolution == "datetime": 125 return lambda x: x and x.as_datetime_or_date() 126 else: 127 return lambda x: x 128 129 class Temporal(Convertible): 130 131 "A simple temporal representation, common to dates and times." 132 133 def __init__(self, data): 134 self.data = list(data) 135 136 def __repr__(self): 137 return "%s(%r)" % (self.__class__.__name__, self.data) 138 139 def __hash__(self): 140 return hash(self.as_tuple()) 141 142 def as_tuple(self): 143 return tuple(self.data) 144 145 def convert(self, resolution): 146 return self._get_converter(resolution)(self) 147 148 def __cmp__(self, other): 149 150 """ 151 The result of comparing this instance with 'other' is derived from a 152 comparison of the instances' date(time) data at the highest common 153 resolution, meaning that if a date is compared to a datetime, the 154 datetime will be considered as a date. Thus, a date and a datetime 155 referring to the same date will be considered equal. 156 """ 157 158 if not isinstance(other, Temporal): 159 return NotImplemented 160 else: 161 data = self.as_tuple() 162 other_data = other.as_tuple() 163 length = min(len(data), len(other_data)) 164 return cmp(data[:length], other_data[:length]) 165 166 def __sub__(self, other): 167 168 """ 169 Return the difference between this object and the 'other' object at the 170 highest common accuracy of both objects. 171 """ 172 173 if not isinstance(other, Temporal): 174 return NotImplemented 175 else: 176 data = self.as_tuple() 177 other_data = other.as_tuple() 178 direction = self < other and 1 or -1 179 180 if len(data) < len(other_data): 181 return (len(self.until(other)) - 1) * direction 182 else: 183 return (len(other.until(self)) - 1) * -direction 184 185 def _until(self, start, end, nextfn, prevfn): 186 187 """ 188 Return a collection of units of time by starting from the given 'start' 189 and stepping across intervening units until 'end' is reached, using the 190 given 'nextfn' and 'prevfn' to step from one unit to the next. 191 """ 192 193 current = start 194 units = [current] 195 if current < end: 196 while current < end: 197 current = nextfn(current) 198 units.append(current) 199 elif current > end: 200 while current > end: 201 current = prevfn(current) 202 units.append(current) 203 return units 204 205 def ambiguous(self): 206 207 "Only times can be ambiguous." 208 209 return 0 210 211 class Month(Temporal): 212 213 "A simple year-month representation." 214 215 def __str__(self): 216 return "%04d-%02d" % self.as_tuple()[:2] 217 218 def as_datetime(self, day, hour, minute, second, zone): 219 return DateTime(self.as_tuple() + (day, hour, minute, second, zone)) 220 221 def as_date(self, day): 222 if day < 0: 223 weekday, ndays = self.month_properties() 224 day = ndays + 1 + day 225 return Date(self.as_tuple() + (day,)) 226 227 def as_month(self): 228 return self 229 230 def year(self): 231 return self.data[0] 232 233 def month(self): 234 return self.data[1] 235 236 def month_properties(self): 237 238 """ 239 Return the weekday of the 1st of the month, along with the number of 240 days, as a tuple. 241 """ 242 243 year, month = self.as_tuple()[:2] 244 return calendar.monthrange(year, month) 245 246 def month_update(self, n=1): 247 248 "Return the month updated by 'n' months." 249 250 year, month = self.as_tuple()[:2] 251 return Month((year + (month - 1 + n) / 12, (month - 1 + n) % 12 + 1)) 252 253 update = month_update 254 255 def next_month(self): 256 257 "Return the month following this one." 258 259 return self.month_update(1) 260 261 next = next_month 262 263 def previous_month(self): 264 265 "Return the month preceding this one." 266 267 return self.month_update(-1) 268 269 previous = previous_month 270 271 def months_until(self, end): 272 273 "Return the collection of months from this month until 'end'." 274 275 return self._until(self.as_month(), end.as_month(), Month.next_month, Month.previous_month) 276 277 until = months_until 278 279 class Date(Month): 280 281 "A simple year-month-day representation." 282 283 def constrain(self): 284 year, month, day = self.as_tuple()[:3] 285 286 month = max(min(month, 12), 1) 287 wd, last_day = calendar.monthrange(year, month) 288 day = max(min(day, last_day), 1) 289 290 self.data[1:3] = month, day 291 292 def __str__(self): 293 return "%04d-%02d-%02d" % self.as_tuple()[:3] 294 295 def as_datetime(self, hour, minute, second, zone): 296 return DateTime(self.as_tuple() + (hour, minute, second, zone)) 297 298 def as_start_of_day(self): 299 return self.as_datetime(None, None, None, None) 300 301 def as_date(self): 302 return self 303 304 def as_datetime_or_date(self): 305 return self 306 307 def as_month(self): 308 return Month(self.data[:2]) 309 310 def day(self): 311 return self.data[2] 312 313 def day_update(self, n=1): 314 315 "Return the month updated by 'n' days." 316 317 delta = datetime.timedelta(n) 318 dt = datetime.date(*self.as_tuple()[:3]) 319 dt_new = dt + delta 320 return Date((dt_new.year, dt_new.month, dt_new.day)) 321 322 update = day_update 323 324 def next_day(self): 325 326 "Return the date following this one." 327 328 year, month, day = self.as_tuple()[:3] 329 _wd, end_day = calendar.monthrange(year, month) 330 if day == end_day: 331 if month == 12: 332 return Date((year + 1, 1, 1)) 333 else: 334 return Date((year, month + 1, 1)) 335 else: 336 return Date((year, month, day + 1)) 337 338 next = next_day 339 340 def previous_day(self): 341 342 "Return the date preceding this one." 343 344 year, month, day = self.as_tuple()[:3] 345 if day == 1: 346 if month == 1: 347 return Date((year - 1, 12, 31)) 348 else: 349 _wd, end_day = calendar.monthrange(year, month - 1) 350 return Date((year, month - 1, end_day)) 351 else: 352 return Date((year, month, day - 1)) 353 354 previous = previous_day 355 356 def days_until(self, end): 357 358 "Return the collection of days from this date until 'end'." 359 360 return self._until(self.as_date(), end.as_date(), Date.next_day, Date.previous_day) 361 362 until = days_until 363 364 class DateTime(Date): 365 366 "A simple date plus time representation." 367 368 def constrain(self): 369 Date.constrain(self) 370 371 hour, minute, second = self.as_tuple()[3:6] 372 373 if self.has_time(): 374 hour = max(min(hour, 23), 0) 375 minute = max(min(minute, 59), 0) 376 377 if second is not None: 378 second = max(min(second, 60), 0) # support leap seconds 379 380 self.data[3:6] = hour, minute, second 381 382 def __str__(self): 383 return Date.__str__(self) + self.time_string() 384 385 def time_string(self, zone_as_offset=False, time_prefix=" ", zone_prefix=" "): 386 if self.has_time(): 387 data = self.as_tuple() 388 time_str = "%s%02d:%02d" % ((time_prefix,) + data[3:5]) 389 if data[5] is not None: 390 time_str += ":%02d" % data[5] 391 if data[6] is not None: 392 if zone_as_offset: 393 utc_offset = self.utc_offset() 394 if utc_offset: 395 time_str += "%s%+03d:%02d" % ((zone_prefix,) + utc_offset) 396 else: 397 time_str += "%s%s" % (zone_prefix, data[6]) 398 return time_str 399 else: 400 return "" 401 402 def as_HTTP_datetime_string(self): 403 weekday = calendar.weekday(*self.data[:3]) 404 return "%s, %02d %s %04d %02d:%02d:%02d GMT" % (( 405 getDayLabel(weekday), 406 self.data[2], 407 getMonthLabel(self.data[1]), 408 self.data[0] 409 ) + tuple(self.data[3:6])) 410 411 def as_ISO8601_datetime_string(self): 412 return Date.__str__(self) + self.time_string(zone_as_offset=True, time_prefix="T", zone_prefix="") 413 414 def as_datetime(self): 415 return self 416 417 def as_date(self): 418 return Date(self.data[:3]) 419 420 def as_datetime_or_date(self): 421 422 """ 423 Return a date for this datetime if fields are missing. Otherwise, return 424 this datetime itself. 425 """ 426 427 if not self.has_time(): 428 return self.as_date() 429 else: 430 return self 431 432 def __cmp__(self, other): 433 434 """ 435 The result of comparing this instance with 'other' is, if both instances 436 are datetime instances, derived from a comparison of the datetimes 437 converted to UTC. If one or both datetimes cannot be converted to UTC, 438 the datetimes are compared using the basic temporal comparison which 439 compares their raw time data. 440 """ 441 442 this = self 443 444 if this.has_time(): 445 if isinstance(other, DateTime): 446 if other.has_time(): 447 this_utc = this.to_utc() 448 other_utc = other.to_utc() 449 if this_utc is not None and other_utc is not None: 450 return cmp(this_utc.as_tuple(), other_utc.as_tuple()) 451 else: 452 other = other.padded() 453 else: 454 this = this.padded() 455 456 return Date.__cmp__(this, other) 457 458 def __sub__(self, other): 459 460 """ 461 Return the difference between this object and the 'other' object at the 462 highest common accuracy of both objects. 463 """ 464 465 if not isinstance(other, Temporal): 466 return NotImplemented 467 elif not other.has_time(): 468 return self.as_date() - other 469 else: 470 utc = self.to_utc() 471 other = other.to_utc() 472 days = utc.as_date() - other.as_date() 473 h1, m1, s1 = utc.as_tuple()[3:6] 474 h2, m2, s2 = other.as_tuple()[3:6] 475 return days * 24 * 3600 + (h1 - h2) * 3600 + (m1 - m2) * 60 + s1 - s2 476 477 def has_time(self): 478 479 """ 480 Return whether this object has any time information. Objects without 481 time information can refer to the very start of a day. 482 """ 483 484 return self.data[3] is not None and self.data[4] is not None 485 486 def time(self): 487 return self.data[3:] 488 489 def seconds(self): 490 return self.data[5] 491 492 def time_zone(self): 493 return self.data[6] 494 495 def set_time_zone(self, value): 496 self.data[6] = value 497 498 def padded(self, empty_value=0): 499 500 """ 501 Return a datetime with missing fields defined as being the given 502 'empty_value' or 0 if not specified. 503 """ 504 505 data = [] 506 for x in self.data[:6]: 507 if x is None: 508 data.append(empty_value) 509 else: 510 data.append(x) 511 512 data += self.data[6:] 513 return DateTime(data) 514 515 def to_utc(self): 516 517 """ 518 Return this object converted to UTC, or None if such a conversion is not 519 defined. 520 """ 521 522 if not self.has_time(): 523 return None 524 525 offset = self.utc_offset() 526 if offset: 527 hours, minutes = offset 528 529 # Invert the offset to get the correction. 530 531 hours, minutes = -hours, -minutes 532 533 # Get the components. 534 535 hour, minute, second, zone = self.time() 536 date = self.as_date() 537 538 # Add the minutes and hours. 539 540 minute += minutes 541 if minute < 0 or minute > 59: 542 hour += minute / 60 543 minute = minute % 60 544 545 # NOTE: This makes various assumptions and probably would not work 546 # NOTE: for general arithmetic. 547 548 hour += hours 549 if hour < 0: 550 date = date.previous_day() 551 hour += 24 552 elif hour > 23: 553 date = date.next_day() 554 hour -= 24 555 556 return date.as_datetime(hour, minute, second, "UTC") 557 558 # Cannot convert. 559 560 else: 561 return None 562 563 def utc_offset(self): 564 565 "Return the UTC offset in hours and minutes." 566 567 zone = self.time_zone() 568 if not zone: 569 return None 570 571 # Support explicit UTC zones. 572 573 if zone == "UTC": 574 return 0, 0 575 576 # Attempt to return a UTC offset where an explicit offset has been set. 577 578 match = timezone_offset_regexp.match(zone) 579 if match: 580 if match.group("sign") == "-": 581 offset_sign = -1 582 else: 583 offset_sign = 1 584 585 hours = int(match.group("hours")) * offset_sign 586 minutes = int(match.group("minutes") or 0) * offset_sign 587 return hours, minutes 588 589 # Attempt to handle Olson time zone identifiers. 590 591 dt = self.as_olson_datetime() 592 if dt: 593 seconds = dt.utcoffset().seconds + dt.utcoffset().days * 24 * 3600 594 hours = abs(seconds) / 3600 595 minutes = (abs(seconds) % 3600) / 60 596 return sign(seconds) * hours, sign(seconds) * minutes 597 598 # Otherwise return None. 599 600 return None 601 602 def olson_identifier(self): 603 604 "Return the Olson identifier from any zone information." 605 606 zone = self.time_zone() 607 if not zone: 608 return None 609 610 # Attempt to match an identifier. 611 612 match = timezone_olson_regexp.match(zone) 613 if match: 614 return match.group("olson") 615 else: 616 return None 617 618 def _as_olson_datetime(self, hours=None): 619 620 """ 621 Return a Python datetime object for this datetime interpreted using any 622 Olson time zone identifier and the given 'hours' offset, raising one of 623 the pytz exceptions in case of ambiguity. 624 """ 625 626 olson = self.olson_identifier() 627 if olson and pytz: 628 tz = pytz.timezone(olson) 629 data = self.padded().as_tuple()[:6] 630 dt = datetime.datetime(*data) 631 632 # With an hours offset, find a time probably in a previously 633 # applicable time zone. 634 635 if hours is not None: 636 td = datetime.timedelta(0, hours * 3600) 637 dt += td 638 639 ldt = tz.localize(dt, None) 640 641 # With an hours offset, adjust the time to define it within the 642 # previously applicable time zone but at the presumably intended 643 # position. 644 645 if hours is not None: 646 ldt -= td 647 648 return ldt 649 else: 650 return None 651 652 def as_olson_datetime(self): 653 654 """ 655 Return a Python datetime object for this datetime interpreted using any 656 Olson time zone identifier, choosing the time from the zone before the 657 period of ambiguity. 658 """ 659 660 try: 661 return self._as_olson_datetime() 662 except (pytz.UnknownTimeZoneError, pytz.AmbiguousTimeError): 663 664 # Try again, using an earlier local time and then stepping forward 665 # in the chosen zone. 666 # NOTE: Four hours earlier seems reasonable. 667 668 return self._as_olson_datetime(-4) 669 670 def ambiguous(self): 671 672 "Return whether the time is local and ambiguous." 673 674 try: 675 self._as_olson_datetime() 676 except (pytz.UnknownTimeZoneError, pytz.AmbiguousTimeError): 677 return 1 678 679 return 0 680 681 class Timespan(ActsAsTimespan, Convertible): 682 683 """ 684 A period of time which can be compared against others to check for overlaps. 685 """ 686 687 def __init__(self, start, end): 688 self.start = start 689 self.end = end 690 691 # NOTE: Should perhaps catch ambiguous time problems elsewhere. 692 693 if self.ambiguous() and self.start is not None and self.end is not None and start > end: 694 self.start, self.end = end, start 695 696 def __repr__(self): 697 return "%s(%r, %r)" % (self.__class__.__name__, self.start, self.end) 698 699 def __hash__(self): 700 return hash((self.start, self.end)) 701 702 def as_timespan(self): 703 return self 704 705 def as_limits(self): 706 return self.start, self.end 707 708 def ambiguous(self): 709 return self.start is not None and self.start.ambiguous() or self.end is not None and self.end.ambiguous() 710 711 def convert(self, resolution): 712 return Timespan(*map(self._get_converter(resolution), self.as_limits())) 713 714 def is_before(self, a, b): 715 716 """ 717 Return whether 'a' is before 'b'. Since the end datetime of one period 718 may be the same as the start datetime of another period, and yet the 719 first period is intended to be concluded by the end datetime and not 720 overlap with the other period, a different test is employed for datetime 721 comparisons. 722 """ 723 724 # Datetimes without times can be equal to dates and be considered as 725 # occurring before those dates. Generally, datetimes should not be 726 # produced without time information as getDateTime converts such 727 # datetimes to dates. 728 729 if isinstance(a, DateTime) and (isinstance(b, DateTime) or not a.has_time()): 730 return a <= b 731 else: 732 return a < b 733 734 def __contains__(self, other): 735 736 """ 737 This instance is considered to contain 'other' if one is not before or 738 after the other. If this instance overlaps or coincides with 'other', 739 then 'other' is regarded as belonging to this instance's time period. 740 """ 741 742 return self == other 743 744 def __cmp__(self, other): 745 746 """ 747 Return whether this timespan occupies the same period of time as the 748 'other'. Timespans are considered less than others if their end points 749 precede the other's start point, and are considered greater than others 750 if their start points follow the other's end point. 751 """ 752 753 if isinstance(other, ActsAsTimespan): 754 other = other.as_timespan() 755 756 before = self.end is not None and other.start is not None and self.is_before(self.end, other.start) 757 after = self.start is not None and other.end is not None and self.is_before(other.end, self.start) 758 else: 759 before = self.end is not None and self.is_before(self.end, other) 760 after = self.start is not None and self.is_before(other, self.start) 761 762 # Two identical points in time will be "before" each other according to 763 # the is_before test. 764 765 if not before and not after or before and after: 766 return 0 767 elif before: 768 return -1 769 else: 770 return 1 771 772 class TimespanCollection: 773 774 """ 775 A class providing a list-like interface supporting membership tests at a 776 particular resolution in order to maintain a collection of non-overlapping 777 timespans. 778 """ 779 780 def __init__(self, resolution, values=None): 781 self.resolution = resolution 782 self.values = values or [] 783 784 def as_timespan(self): 785 return Timespan(*self.as_limits()) 786 787 def as_limits(self): 788 789 "Return the earliest and latest points in time for this collection." 790 791 if not self.values: 792 return None, None 793 else: 794 first, last = self.values[0], self.values[-1] 795 if isinstance(first, ActsAsTimespan): 796 first = first.as_timespan().start 797 if isinstance(last, ActsAsTimespan): 798 last = last.as_timespan().end 799 return first, last 800 801 def convert(self, value): 802 if isinstance(value, ActsAsTimespan): 803 ts = value.as_timespan() 804 return ts and ts.convert(self.resolution) 805 else: 806 return value.convert(self.resolution) 807 808 def __iter__(self): 809 return iter(self.values) 810 811 def __len__(self): 812 return len(self.values) 813 814 def __getitem__(self, i): 815 return self.values[i] 816 817 def __setitem__(self, i, value): 818 self.values[i] = value 819 820 def __contains__(self, value): 821 test_value = self.convert(value) 822 return test_value in self.values 823 824 def append(self, value): 825 self.values.append(value) 826 827 def insert(self, i, value): 828 self.values.insert(i, value) 829 830 def pop(self): 831 return self.values.pop() 832 833 def insert_in_order(self, value): 834 bisect.insort_left(self, value) 835 836 def getDate(s): 837 838 "Parse the string 's', extracting and returning a date object." 839 840 dt = getDateTime(s) 841 if dt is not None: 842 return dt.as_date() 843 else: 844 return None 845 846 def getDateTime(s): 847 848 """ 849 Parse the string 's', extracting and returning a datetime object where time 850 information has been given or a date object where time information is 851 absent. 852 """ 853 854 m = datetime_regexp.search(s) 855 if m: 856 groups = list(m.groups()) 857 858 # Convert date and time data to integer or None. 859 860 return DateTime(map(int_or_none, groups[:6]) + [m.group("zone")]).as_datetime_or_date() 861 else: 862 return None 863 864 def getDateFromCalendar(s): 865 866 """ 867 Parse the iCalendar format string 's', extracting and returning a date 868 object. 869 """ 870 871 dt = getDateTimeFromCalendar(s) 872 if dt is not None: 873 return dt.as_date() 874 else: 875 return None 876 877 def getDateTimeFromCalendar(s): 878 879 """ 880 Parse the iCalendar format datetime string 's', extracting and returning a 881 datetime object where time information has been given or a date object where 882 time information is absent. 883 """ 884 885 m = datetime_icalendar_regexp.search(s) 886 if m: 887 groups = list(m.groups()) 888 889 # Convert date and time data to integer or None. 890 891 return DateTime(map(int_or_none, groups[:6]) + [m.group("utc") and "UTC" or None]).as_datetime_or_date() 892 else: 893 return None 894 895 def getDateTimeFromISO8601(s): 896 897 """ 898 Parse the ISO 8601 format datetime string 's', returning a datetime object. 899 """ 900 901 m = datetime_iso8601_regexp.search(s) 902 if m: 903 groups = list(m.groups()) 904 905 # Convert date and time data to integer or None. 906 907 return DateTime(map(int_or_none, groups[:6]) + [m.group("utc") and "UTC" or m.group("zone")]).as_datetime_or_date() 908 else: 909 return None 910 911 def getDateStrings(s): 912 913 "Parse the string 's', extracting and returning all date strings." 914 915 start = 0 916 m = date_regexp.search(s, start) 917 l = [] 918 while m: 919 l.append("-".join(m.groups())) 920 m = date_regexp.search(s, m.end()) 921 return l 922 923 def getMonth(s): 924 925 "Parse the string 's', extracting and returning a month object." 926 927 m = month_regexp.search(s) 928 if m: 929 return Month(map(int, m.groups())) 930 else: 931 return None 932 933 def getCurrentDate(): 934 935 "Return the current date as a Date instance." 936 937 today = datetime.date.today() 938 return Date((today.year, today.month, today.day)) 939 940 def getCurrentMonth(): 941 942 "Return the current month as a Month instance." 943 944 today = datetime.date.today() 945 return Month((today.year, today.month)) 946 947 def getCurrentYear(): 948 949 "Return the current year." 950 951 today = datetime.date.today() 952 return today.year 953 954 def getCurrentTime(): 955 956 "Return the current time as a DateTime instance." 957 958 now = datetime.datetime.utcnow() 959 return DateTime((now.year, now.month, now.day, now.hour, now.minute, now.second, "UTC")) 960 961 # vim: tabstop=4 expandtab shiftwidth=4