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