python-dateutil-2.4.2/0000777000000000000000000000000012506556560013015 5ustar 00000000000000python-dateutil-2.4.2/dateutil/0000777000000000000000000000000012506556557014636 5ustar 00000000000000python-dateutil-2.4.2/dateutil/easter.py0000666000000000000000000000506312505254345016464 0ustar 00000000000000# -*- coding: utf-8 -*- """ This module offers a generic easter computing method for any given year, using Western, Orthodox or Julian algorithms. """ import datetime __all__ = ["easter", "EASTER_JULIAN", "EASTER_ORTHODOX", "EASTER_WESTERN"] EASTER_JULIAN = 1 EASTER_ORTHODOX = 2 EASTER_WESTERN = 3 def easter(year, method=EASTER_WESTERN): """ This method was ported from the work done by GM Arts, on top of the algorithm by Claus Tondering, which was based in part on the algorithm of Ouding (1940), as quoted in "Explanatory Supplement to the Astronomical Almanac", P. Kenneth Seidelmann, editor. This algorithm implements three different easter calculation methods: 1 - Original calculation in Julian calendar, valid in dates after 326 AD 2 - Original method, with date converted to Gregorian calendar, valid in years 1583 to 4099 3 - Revised method, in Gregorian calendar, valid in years 1583 to 4099 as well These methods are represented by the constants: EASTER_JULIAN = 1 EASTER_ORTHODOX = 2 EASTER_WESTERN = 3 The default method is method 3. More about the algorithm may be found at: http://users.chariot.net.au/~gmarts/eastalg.htm and http://www.tondering.dk/claus/calendar.html """ if not (1 <= method <= 3): raise ValueError("invalid method") # g - Golden year - 1 # c - Century # h - (23 - Epact) mod 30 # i - Number of days from March 21 to Paschal Full Moon # j - Weekday for PFM (0=Sunday, etc) # p - Number of days from March 21 to Sunday on or before PFM # (-6 to 28 methods 1 & 3, to 56 for method 2) # e - Extra days to add for method 2 (converting Julian # date to Gregorian date) y = year g = y % 19 e = 0 if method < 3: # Old method i = (19*g + 15) % 30 j = (y + y//4 + i) % 7 if method == 2: # Extra dates to convert Julian to Gregorian date e = 10 if y > 1600: e = e + y//100 - 16 - (y//100 - 16)//4 else: # New method c = y//100 h = (c - c//4 - (8*c + 13)//25 + 19*g + 15) % 30 i = h - (h//28)*(1 - (h//28)*(29//(h + 1))*((21 - g)//11)) j = (y + y//4 + i + 2 - c + c//4) % 7 # p can be from -6 to 56 corresponding to dates 22 March to 23 May # (later dates apply to method 2, although 23 May never actually occurs) p = i - j + e d = 1 + (p + 27 + (p + 6)//40) % 31 m = 3 + (p + 26)//30 return datetime.date(int(y), int(m), int(d)) python-dateutil-2.4.2/dateutil/parser.py0000666000000000000000000012606512506520336016500 0ustar 00000000000000# -*- coding:iso-8859-1 -*- """ This module offers a generic date/time string parser which is able to parse most known formats to represent a date and/or time. Additional resources about date/time string formats can be found below: - `A summary of the international standard date and time notation `_ - `W3C Date and Time Formats `_ - `Time Formats (Planetary Rings Node) `_ - `CPAN ParseDate module `_ - `Java SimpleDateFormat Class `_ """ from __future__ import unicode_literals import datetime import string import time import collections from io import StringIO from six import text_type, binary_type, integer_types from . import relativedelta from . import tz __all__ = ["parse", "parserinfo"] class _timelex(object): def __init__(self, instream): if isinstance(instream, text_type): instream = StringIO(instream) self.instream = instream self.wordchars = ('abcdfeghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_' '' '') self.numchars = '0123456789' self.whitespace = ' \t\r\n' self.charstack = [] self.tokenstack = [] self.eof = False def get_token(self): """ This function breaks the time string into lexical units (tokens), which can be parsed by the parser. Lexical units are demarcated by changes in the character set, so any continuous string of letters is considered one unit, any continuous string of numbers is considered one unit. The main complication arises from the fact that dots ('.') can be used both as separators (e.g. "Sep.20.2009") or decimal points (e.g. "4:30:21.447"). As such, it is necessary to read the full context of any dot-separated strings before breaking it into tokens; as such, this function maintains a "token stack", for when the ambiguous context demands that multiple tokens be parsed at once. """ if self.tokenstack: return self.tokenstack.pop(0) seenletters = False token = None state = None wordchars = self.wordchars numchars = self.numchars whitespace = self.whitespace while not self.eof: # We only realize that we've reached the end of a token when we find # a character that's not part of the current token - since that # character may be part of the next token, it's stored in the # charstack. if self.charstack: nextchar = self.charstack.pop(0) else: nextchar = self.instream.read(1) while nextchar == '\x00': nextchar = self.instream.read(1) if not nextchar: self.eof = True break elif not state: # First character of the token - determines if we're starting # to parse a word, a number or something else. token = nextchar if nextchar in wordchars: state = 'a' elif nextchar in numchars: state = '0' elif nextchar in whitespace: token = ' ' break # emit token else: break # emit token elif state == 'a': # If we've already started reading a word, we keep reading # letters until we find something that's not part of a word. seenletters = True if nextchar in wordchars: token += nextchar elif nextchar == '.': token += nextchar state = 'a.' else: self.charstack.append(nextchar) break # emit token elif state == '0': # If we've already started reading a number, we keep reading # numbers until we find something that doesn't fit. if nextchar in numchars: token += nextchar elif nextchar == '.': token += nextchar state = '0.' else: self.charstack.append(nextchar) break # emit token elif state == 'a.': # If we've seen some letters and a dot separator, continue # parsing, and the tokens will be broken up later. seenletters = True if nextchar == '.' or nextchar in wordchars: token += nextchar elif nextchar in numchars and token[-1] == '.': token += nextchar state = '0.' else: self.charstack.append(nextchar) break # emit token elif state == '0.': # If we've seen at least one dot separator, keep going, we'll # break up the tokens later. if nextchar == '.' or nextchar in numchars: token += nextchar elif nextchar in wordchars and token[-1] == '.': token += nextchar state = 'a.' else: self.charstack.append(nextchar) break # emit token if (state in ('a.', '0.') and (seenletters or token.count('.') > 1 or token[-1] == '.')): l = token.split('.') token = l[0] for tok in l[1:]: self.tokenstack.append('.') if tok: self.tokenstack.append(tok) return token def __iter__(self): return self def __next__(self): token = self.get_token() if token is None: raise StopIteration return token def next(self): return self.__next__() # Python 2.x support def split(cls, s): return list(cls(s)) split = classmethod(split) class _resultbase(object): def __init__(self): for attr in self.__slots__: setattr(self, attr, None) def _repr(self, classname): l = [] for attr in self.__slots__: value = getattr(self, attr) if value is not None: l.append("%s=%s" % (attr, repr(value))) return "%s(%s)" % (classname, ", ".join(l)) def __repr__(self): return self._repr(self.__class__.__name__) class parserinfo(object): """ Class which handles what inputs are accepted. Subclass this to customize the language and acceptable values for each parameter. :param dayfirst: Whether to interpret the first value in an ambiguous 3-integer date (e.g. 01/05/09) as the day (`True`) or month (`False`). If `yearfirst` is set to `True`, this distinguishes between YDM and YMD. Default is `False`. :param yearfirst: Whether to interpret the first value in an ambiguous 3-integer date (e.g. 01/05/09) as the year. If `True`, the first number is taken to be the year, otherwise the last number is taken to be the year. Default is `False`. """ # m from a.m/p.m, t from ISO T separator JUMP = [" ", ".", ",", ";", "-", "/", "'", "at", "on", "and", "ad", "m", "t", "of", "st", "nd", "rd", "th"] WEEKDAYS = [("Mon", "Monday"), ("Tue", "Tuesday"), ("Wed", "Wednesday"), ("Thu", "Thursday"), ("Fri", "Friday"), ("Sat", "Saturday"), ("Sun", "Sunday")] MONTHS = [("Jan", "January"), ("Feb", "February"), ("Mar", "March"), ("Apr", "April"), ("May", "May"), ("Jun", "June"), ("Jul", "July"), ("Aug", "August"), ("Sep", "Sept", "September"), ("Oct", "October"), ("Nov", "November"), ("Dec", "December")] HMS = [("h", "hour", "hours"), ("m", "minute", "minutes"), ("s", "second", "seconds")] AMPM = [("am", "a"), ("pm", "p")] UTCZONE = ["UTC", "GMT", "Z"] PERTAIN = ["of"] TZOFFSET = {} def __init__(self, dayfirst=False, yearfirst=False): self._jump = self._convert(self.JUMP) self._weekdays = self._convert(self.WEEKDAYS) self._months = self._convert(self.MONTHS) self._hms = self._convert(self.HMS) self._ampm = self._convert(self.AMPM) self._utczone = self._convert(self.UTCZONE) self._pertain = self._convert(self.PERTAIN) self.dayfirst = dayfirst self.yearfirst = yearfirst self._year = time.localtime().tm_year self._century = self._year // 100*100 def _convert(self, lst): dct = {} for i, v in enumerate(lst): if isinstance(v, tuple): for v in v: dct[v.lower()] = i else: dct[v.lower()] = i return dct def jump(self, name): return name.lower() in self._jump def weekday(self, name): if len(name) >= 3: try: return self._weekdays[name.lower()] except KeyError: pass return None def month(self, name): if len(name) >= 3: try: return self._months[name.lower()]+1 except KeyError: pass return None def hms(self, name): try: return self._hms[name.lower()] except KeyError: return None def ampm(self, name): try: return self._ampm[name.lower()] except KeyError: return None def pertain(self, name): return name.lower() in self._pertain def utczone(self, name): return name.lower() in self._utczone def tzoffset(self, name): if name in self._utczone: return 0 return self.TZOFFSET.get(name) def convertyear(self, year): if year < 100: year += self._century if abs(year-self._year) >= 50: if year < self._year: year += 100 else: year -= 100 return year def validate(self, res): # move to info if res.year is not None: res.year = self.convertyear(res.year) if res.tzoffset == 0 and not res.tzname or res.tzname == 'Z': res.tzname = "UTC" res.tzoffset = 0 elif res.tzoffset != 0 and res.tzname and self.utczone(res.tzname): res.tzoffset = 0 return True class parser(object): def __init__(self, info=None): self.info = info or parserinfo() def parse(self, timestr, default=None, ignoretz=False, tzinfos=None, **kwargs): """ Parse the date/time string into a datetime object. :param timestr: Any date/time string using the supported formats. :param default: The default datetime object, if this is a datetime object and not `None`, elements specified in `timestr` replace elements in the default object. :param ignoretz: Whether or not to ignore the time zone. :param tzinfos: A time zone, to be applied to the date, if `ignoretz` is `True`. This can be either a subclass of `tzinfo`, a time zone string or an integer offset. :param **kwargs: Keyword arguments as passed to `_parse()`. :return: Returns a `datetime.datetime` object or, if the `fuzzy_with_tokens` option is `True`, returns a tuple, the first element being a `datetime.datetime` object, the second a tuple containing the fuzzy tokens. :raises ValueError: Raised for invalid or unknown string format, if the provided `tzinfo` is not in a valid format, or if an invalid date would be created. :raises OverFlowError: Raised if the parsed date exceeds the largest valid C integer on your system. """ default_specified = default is not None if not default_specified: default = datetime.datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) if kwargs.get('fuzzy_with_tokens', False): res, skipped_tokens = self._parse(timestr, **kwargs) else: res = self._parse(timestr, **kwargs) if res is None: raise ValueError("Unknown string format") repl = {} for attr in ["year", "month", "day", "hour", "minute", "second", "microsecond"]: value = getattr(res, attr) if value is not None: repl[attr] = value ret = default.replace(**repl) if res.weekday is not None and not res.day: ret = ret+relativedelta.relativedelta(weekday=res.weekday) if not ignoretz: if (isinstance(tzinfos, collections.Callable) or tzinfos and res.tzname in tzinfos): if isinstance(tzinfos, collections.Callable): tzdata = tzinfos(res.tzname, res.tzoffset) else: tzdata = tzinfos.get(res.tzname) if isinstance(tzdata, datetime.tzinfo): tzinfo = tzdata elif isinstance(tzdata, text_type): tzinfo = tz.tzstr(tzdata) elif isinstance(tzdata, integer_types): tzinfo = tz.tzoffset(res.tzname, tzdata) else: raise ValueError("Offset must be tzinfo subclass, " "tz string, or int offset.") ret = ret.replace(tzinfo=tzinfo) elif res.tzname and res.tzname in time.tzname: ret = ret.replace(tzinfo=tz.tzlocal()) elif res.tzoffset == 0: ret = ret.replace(tzinfo=tz.tzutc()) elif res.tzoffset: ret = ret.replace(tzinfo=tz.tzoffset(res.tzname, res.tzoffset)) if kwargs.get('fuzzy_with_tokens', False): return ret, skipped_tokens else: return ret class _result(_resultbase): __slots__ = ["year", "month", "day", "weekday", "hour", "minute", "second", "microsecond", "tzname", "tzoffset", "ampm"] def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False, fuzzy_with_tokens=False): """ Private method which performs the heavy lifting of parsing, called from `parse()`, which passes on its `kwargs` to this function. :param timestr: The string to parse. :param dayfirst: Whether to interpret the first value in an ambiguous 3-integer date (e.g. 01/05/09) as the day (`True`) or month (`False`). If `yearfirst` is set to `True`, this distinguishes between YDM and YMD. If set to `None`, this value is retrieved from the current `parserinfo` object (which itself defaults to `False`). :param yearfirst: Whether to interpret the first value in an ambiguous 3-integer date (e.g. 01/05/09) as the year. If `True`, the first number is taken to be the year, otherwise the last number is taken to be the year. If this is set to `None`, the value is retrieved from the current `parserinfo` object (which itself defaults to `False`). :param fuzzy: Whether to allow fuzzy parsing, allowing for string like "Today is January 1, 2047 at 8:21:00AM". :param fuzzy_with_tokens: If `True`, `fuzzy` is automatically set to True, and the parser will return a tuple where the first element is the parsed `datetime.datetime` datetimestamp and the second element is a tuple containing the portions of the string which were ignored, e.g. "Today is January 1, 2047 at 8:21:00AM" should return `(datetime.datetime(2011, 1, 1, 8, 21), (u'Today is ', u' ', u'at '))` """ if fuzzy_with_tokens: fuzzy = True info = self.info if dayfirst is None: dayfirst = info.dayfirst if yearfirst is None: yearfirst = info.yearfirst res = self._result() l = _timelex.split(timestr) # Splits the timestr into tokens # keep up with the last token skipped so we can recombine # consecutively skipped tokens (-2 for when i begins at 0). last_skipped_token_i = -2 skipped_tokens = list() try: # year/month/day list ymd = [] # Index of the month string in ymd mstridx = -1 len_l = len(l) i = 0 while i < len_l: # Check if it's a number try: value_repr = l[i] value = float(value_repr) except ValueError: value = None if value is not None: # Token is a number len_li = len(l[i]) i += 1 if (len(ymd) == 3 and len_li in (2, 4) and res.hour is None and (i >= len_l or (l[i] != ':' and info.hms(l[i]) is None))): # 19990101T23[59] s = l[i-1] res.hour = int(s[:2]) if len_li == 4: res.minute = int(s[2:]) elif len_li == 6 or (len_li > 6 and l[i-1].find('.') == 6): # YYMMDD or HHMMSS[.ss] s = l[i-1] if not ymd and l[i-1].find('.') == -1: ymd.append(info.convertyear(int(s[:2]))) ymd.append(int(s[2:4])) ymd.append(int(s[4:])) else: # 19990101T235959[.59] res.hour = int(s[:2]) res.minute = int(s[2:4]) res.second, res.microsecond = _parsems(s[4:]) elif len_li == 8: # YYYYMMDD s = l[i-1] ymd.append(int(s[:4])) ymd.append(int(s[4:6])) ymd.append(int(s[6:])) elif len_li in (12, 14): # YYYYMMDDhhmm[ss] s = l[i-1] ymd.append(int(s[:4])) ymd.append(int(s[4:6])) ymd.append(int(s[6:8])) res.hour = int(s[8:10]) res.minute = int(s[10:12]) if len_li == 14: res.second = int(s[12:]) elif ((i < len_l and info.hms(l[i]) is not None) or (i+1 < len_l and l[i] == ' ' and info.hms(l[i+1]) is not None)): # HH[ ]h or MM[ ]m or SS[.ss][ ]s if l[i] == ' ': i += 1 idx = info.hms(l[i]) while True: if idx == 0: res.hour = int(value) if value % 1: res.minute = int(60*(value % 1)) elif idx == 1: res.minute = int(value) if value % 1: res.second = int(60*(value % 1)) elif idx == 2: res.second, res.microsecond = \ _parsems(value_repr) i += 1 if i >= len_l or idx == 2: break # 12h00 try: value_repr = l[i] value = float(value_repr) except ValueError: break else: i += 1 idx += 1 if i < len_l: newidx = info.hms(l[i]) if newidx is not None: idx = newidx elif (i == len_l and l[i-2] == ' ' and info.hms(l[i-3]) is not None): # X h MM or X m SS idx = info.hms(l[i-3]) + 1 if idx == 1: res.minute = int(value) if value % 1: res.second = int(60*(value % 1)) elif idx == 2: res.second, res.microsecond = \ _parsems(value_repr) i += 1 elif i+1 < len_l and l[i] == ':': # HH:MM[:SS[.ss]] res.hour = int(value) i += 1 value = float(l[i]) res.minute = int(value) if value % 1: res.second = int(60*(value % 1)) i += 1 if i < len_l and l[i] == ':': res.second, res.microsecond = _parsems(l[i+1]) i += 2 elif i < len_l and l[i] in ('-', '/', '.'): sep = l[i] ymd.append(int(value)) i += 1 if i < len_l and not info.jump(l[i]): try: # 01-01[-01] ymd.append(int(l[i])) except ValueError: # 01-Jan[-01] value = info.month(l[i]) if value is not None: ymd.append(value) assert mstridx == -1 mstridx = len(ymd)-1 else: return None i += 1 if i < len_l and l[i] == sep: # We have three members i += 1 value = info.month(l[i]) if value is not None: ymd.append(value) mstridx = len(ymd)-1 assert mstridx == -1 else: ymd.append(int(l[i])) i += 1 elif i >= len_l or info.jump(l[i]): if i+1 < len_l and info.ampm(l[i+1]) is not None: # 12 am res.hour = int(value) if res.hour < 12 and info.ampm(l[i+1]) == 1: res.hour += 12 elif res.hour == 12 and info.ampm(l[i+1]) == 0: res.hour = 0 i += 1 else: # Year, month or day ymd.append(int(value)) i += 1 elif info.ampm(l[i]) is not None: # 12am res.hour = int(value) if res.hour < 12 and info.ampm(l[i]) == 1: res.hour += 12 elif res.hour == 12 and info.ampm(l[i]) == 0: res.hour = 0 i += 1 elif not fuzzy: return None else: i += 1 continue # Check weekday value = info.weekday(l[i]) if value is not None: res.weekday = value i += 1 continue # Check month name value = info.month(l[i]) if value is not None: ymd.append(value) assert mstridx == -1 mstridx = len(ymd)-1 i += 1 if i < len_l: if l[i] in ('-', '/'): # Jan-01[-99] sep = l[i] i += 1 ymd.append(int(l[i])) i += 1 if i < len_l and l[i] == sep: # Jan-01-99 i += 1 ymd.append(int(l[i])) i += 1 elif (i+3 < len_l and l[i] == l[i+2] == ' ' and info.pertain(l[i+1])): # Jan of 01 # In this case, 01 is clearly year try: value = int(l[i+3]) except ValueError: # Wrong guess pass else: # Convert it here to become unambiguous ymd.append(info.convertyear(value)) i += 4 continue # Check am/pm value = info.ampm(l[i]) if value is not None: # For fuzzy parsing, 'a' or 'am' (both valid English words) # may erroneously trigger the AM/PM flag. Deal with that # here. val_is_ampm = True # If there's already an AM/PM flag, this one isn't one. if fuzzy and res.ampm is not None: val_is_ampm = False # If AM/PM is found and hour is not, raise a ValueError if res.hour is None: if fuzzy: val_is_ampm = False else: raise ValueError('No hour specified with ' + 'AM or PM flag.') elif not 0 <= res.hour <= 12: # If AM/PM is found, it's a 12 hour clock, so raise # an error for invalid range if fuzzy: val_is_ampm = False else: raise ValueError('Invalid hour specified for ' + '12-hour clock.') if val_is_ampm: if value == 1 and res.hour < 12: res.hour += 12 elif value == 0 and res.hour == 12: res.hour = 0 res.ampm = value i += 1 continue # Check for a timezone name if (res.hour is not None and len(l[i]) <= 5 and res.tzname is None and res.tzoffset is None and not [x for x in l[i] if x not in string.ascii_uppercase]): res.tzname = l[i] res.tzoffset = info.tzoffset(res.tzname) i += 1 # Check for something like GMT+3, or BRST+3. Notice # that it doesn't mean "I am 3 hours after GMT", but # "my time +3 is GMT". If found, we reverse the # logic so that timezone parsing code will get it # right. if i < len_l and l[i] in ('+', '-'): l[i] = ('+', '-')[l[i] == '+'] res.tzoffset = None if info.utczone(res.tzname): # With something like GMT+3, the timezone # is *not* GMT. res.tzname = None continue # Check for a numbered timezone if res.hour is not None and l[i] in ('+', '-'): signal = (-1, 1)[l[i] == '+'] i += 1 len_li = len(l[i]) if len_li == 4: # -0300 res.tzoffset = int(l[i][:2])*3600+int(l[i][2:])*60 elif i+1 < len_l and l[i+1] == ':': # -03:00 res.tzoffset = int(l[i])*3600+int(l[i+2])*60 i += 2 elif len_li <= 2: # -[0]3 res.tzoffset = int(l[i][:2])*3600 else: return None i += 1 res.tzoffset *= signal # Look for a timezone name between parenthesis if (i+3 < len_l and info.jump(l[i]) and l[i+1] == '(' and l[i+3] == ')' and 3 <= len(l[i+2]) <= 5 and not [x for x in l[i+2] if x not in string.ascii_uppercase]): # -0300 (BRST) res.tzname = l[i+2] i += 4 continue # Check jumps if not (info.jump(l[i]) or fuzzy): return None if last_skipped_token_i == i - 1: # recombine the tokens skipped_tokens[-1] += l[i] else: # just append skipped_tokens.append(l[i]) last_skipped_token_i = i i += 1 # Process year/month/day len_ymd = len(ymd) if len_ymd > 3: # More than three members!? return None elif len_ymd == 1 or (mstridx != -1 and len_ymd == 2): # One member, or two members with a month string if mstridx != -1: res.month = ymd[mstridx] del ymd[mstridx] if len_ymd > 1 or mstridx == -1: if ymd[0] > 31: res.year = ymd[0] else: res.day = ymd[0] elif len_ymd == 2: # Two members with numbers if ymd[0] > 31: # 99-01 res.year, res.month = ymd elif ymd[1] > 31: # 01-99 res.month, res.year = ymd elif dayfirst and ymd[1] <= 12: # 13-01 res.day, res.month = ymd else: # 01-13 res.month, res.day = ymd elif len_ymd == 3: # Three members if mstridx == 0: res.month, res.day, res.year = ymd elif mstridx == 1: if ymd[0] > 31 or (yearfirst and ymd[2] <= 31): # 99-Jan-01 res.year, res.month, res.day = ymd else: # 01-Jan-01 # Give precendence to day-first, since # two-digit years is usually hand-written. res.day, res.month, res.year = ymd elif mstridx == 2: # WTF!? if ymd[1] > 31: # 01-99-Jan res.day, res.year, res.month = ymd else: # 99-01-Jan res.year, res.day, res.month = ymd else: if ymd[0] > 31 or \ (yearfirst and ymd[1] <= 12 and ymd[2] <= 31): # 99-01-01 res.year, res.month, res.day = ymd elif ymd[0] > 12 or (dayfirst and ymd[1] <= 12): # 13-01-01 res.day, res.month, res.year = ymd else: # 01-13-01 res.month, res.day, res.year = ymd except (IndexError, ValueError, AssertionError): return None if not info.validate(res): return None if fuzzy_with_tokens: return res, tuple(skipped_tokens) else: return res DEFAULTPARSER = parser() def parse(timestr, parserinfo=None, **kwargs): """ Parse a string in one of the supported formats, using the `parserinfo` parameters. :param timestr: A string containing a date/time stamp. :param parserinfo: A :class:`parserinfo` object containing parameters for the parser. If `None`, the default arguments to the `parserinfo` constructor are used. The `**kwargs` parameter takes the following keyword arguments: :param default: The default datetime object, if this is a datetime object and not `None`, elements specified in `timestr` replace elements in the default object. :param ignoretz: Whether or not to ignore the time zone (boolean). :param tzinfos: A time zone, to be applied to the date, if `ignoretz` is `True`. This can be either a subclass of `tzinfo`, a time zone string or an integer offset. :param dayfirst: Whether to interpret the first value in an ambiguous 3-integer date (e.g. 01/05/09) as the day (`True`) or month (`False`). If `yearfirst` is set to `True`, this distinguishes between YDM and YMD. If set to `None`, this value is retrieved from the current :class:`parserinfo` object (which itself defaults to `False`). :param yearfirst: Whether to interpret the first value in an ambiguous 3-integer date (e.g. 01/05/09) as the year. If `True`, the first number is taken to be the year, otherwise the last number is taken to be the year. If this is set to `None`, the value is retrieved from the current :class:`parserinfo` object (which itself defaults to `False`). :param fuzzy: Whether to allow fuzzy parsing, allowing for string like "Today is January 1, 2047 at 8:21:00AM". :param fuzzy_with_tokens: If `True`, `fuzzy` is automatically set to True, and the parser will return a tuple where the first element is the parsed `datetime.datetime` datetimestamp and the second element is a tuple containing the portions of the string which were ignored, e.g. "Today is January 1, 2047 at 8:21:00AM" should return `(datetime.datetime(2011, 1, 1, 8, 21), (u'Today is ', u' ', u'at '))` """ # Python 2.x support: datetimes return their string presentation as # bytes in 2.x and unicode in 3.x, so it's reasonable to expect that # the parser will get both kinds. Internally we use unicode only. if isinstance(timestr, binary_type): timestr = timestr.decode() if parserinfo: return parser(parserinfo).parse(timestr, **kwargs) else: return DEFAULTPARSER.parse(timestr, **kwargs) class _tzparser(object): class _result(_resultbase): __slots__ = ["stdabbr", "stdoffset", "dstabbr", "dstoffset", "start", "end"] class _attr(_resultbase): __slots__ = ["month", "week", "weekday", "yday", "jyday", "day", "time"] def __repr__(self): return self._repr("") def __init__(self): _resultbase.__init__(self) self.start = self._attr() self.end = self._attr() def parse(self, tzstr): # Python 2.x compatibility: tzstr should be converted to unicode before # being passed to _timelex. if isinstance(tzstr, binary_type): tzstr = tzstr.decode() res = self._result() l = _timelex.split(tzstr) try: len_l = len(l) i = 0 while i < len_l: # BRST+3[BRDT[+2]] j = i while j < len_l and not [x for x in l[j] if x in "0123456789:,-+"]: j += 1 if j != i: if not res.stdabbr: offattr = "stdoffset" res.stdabbr = "".join(l[i:j]) else: offattr = "dstoffset" res.dstabbr = "".join(l[i:j]) i = j if (i < len_l and (l[i] in ('+', '-') or l[i][0] in "0123456789")): if l[i] in ('+', '-'): # Yes, that's right. See the TZ variable # documentation. signal = (1, -1)[l[i] == '+'] i += 1 else: signal = -1 len_li = len(l[i]) if len_li == 4: # -0300 setattr(res, offattr, (int(l[i][:2])*3600 + int(l[i][2:])*60)*signal) elif i+1 < len_l and l[i+1] == ':': # -03:00 setattr(res, offattr, (int(l[i])*3600+int(l[i+2])*60)*signal) i += 2 elif len_li <= 2: # -[0]3 setattr(res, offattr, int(l[i][:2])*3600*signal) else: return None i += 1 if res.dstabbr: break else: break if i < len_l: for j in range(i, len_l): if l[j] == ';': l[j] = ',' assert l[i] == ',' i += 1 if i >= len_l: pass elif (8 <= l.count(',') <= 9 and not [y for x in l[i:] if x != ',' for y in x if y not in "0123456789"]): # GMT0BST,3,0,30,3600,10,0,26,7200[,3600] for x in (res.start, res.end): x.month = int(l[i]) i += 2 if l[i] == '-': value = int(l[i+1])*-1 i += 1 else: value = int(l[i]) i += 2 if value: x.week = value x.weekday = (int(l[i])-1) % 7 else: x.day = int(l[i]) i += 2 x.time = int(l[i]) i += 2 if i < len_l: if l[i] in ('-', '+'): signal = (-1, 1)[l[i] == "+"] i += 1 else: signal = 1 res.dstoffset = (res.stdoffset+int(l[i]))*signal elif (l.count(',') == 2 and l[i:].count('/') <= 2 and not [y for x in l[i:] if x not in (',', '/', 'J', 'M', '.', '-', ':') for y in x if y not in "0123456789"]): for x in (res.start, res.end): if l[i] == 'J': # non-leap year day (1 based) i += 1 x.jyday = int(l[i]) elif l[i] == 'M': # month[-.]week[-.]weekday i += 1 x.month = int(l[i]) i += 1 assert l[i] in ('-', '.') i += 1 x.week = int(l[i]) if x.week == 5: x.week = -1 i += 1 assert l[i] in ('-', '.') i += 1 x.weekday = (int(l[i])-1) % 7 else: # year day (zero based) x.yday = int(l[i])+1 i += 1 if i < len_l and l[i] == '/': i += 1 # start time len_li = len(l[i]) if len_li == 4: # -0300 x.time = (int(l[i][:2])*3600+int(l[i][2:])*60) elif i+1 < len_l and l[i+1] == ':': # -03:00 x.time = int(l[i])*3600+int(l[i+2])*60 i += 2 if i+1 < len_l and l[i+1] == ':': i += 2 x.time += int(l[i]) elif len_li <= 2: # -[0]3 x.time = (int(l[i][:2])*3600) else: return None i += 1 assert i == len_l or l[i] == ',' i += 1 assert i >= len_l except (IndexError, ValueError, AssertionError): return None return res DEFAULTTZPARSER = _tzparser() def _parsetz(tzstr): return DEFAULTTZPARSER.parse(tzstr) def _parsems(value): """Parse a I[.F] seconds value into (seconds, microseconds).""" if "." not in value: return int(value), 0 else: i, f = value.split(".") return int(i), int(f.ljust(6, "0")[:6]) # vim:ts=4:sw=4:et python-dateutil-2.4.2/dateutil/relativedelta.py0000666000000000000000000004337412506517720020035 0ustar 00000000000000# -*- coding: utf-8 -*- import datetime import calendar from six import integer_types __all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"] class weekday(object): __slots__ = ["weekday", "n"] def __init__(self, weekday, n=None): self.weekday = weekday self.n = n def __call__(self, n): if n == self.n: return self else: return self.__class__(self.weekday, n) def __eq__(self, other): try: if self.weekday != other.weekday or self.n != other.n: return False except AttributeError: return False return True def __repr__(self): s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday] if not self.n: return s else: return "%s(%+d)" % (s, self.n) MO, TU, WE, TH, FR, SA, SU = weekdays = tuple([weekday(x) for x in range(7)]) class relativedelta(object): """ The relativedelta type is based on the specification of the excellent work done by M.-A. Lemburg in his `mx.DateTime `_ extension. However, notice that this type does *NOT* implement the same algorithm as his work. Do *NOT* expect it to behave like mx.DateTime's counterpart. There are two different ways to build a relativedelta instance. The first one is passing it two date/datetime classes:: relativedelta(datetime1, datetime2) The second one is passing it any number of the following keyword arguments:: relativedelta(arg1=x,arg2=y,arg3=z...) year, month, day, hour, minute, second, microsecond: Absolute information (argument is singular); adding or subtracting a relativedelta with absolute information does not perform an aritmetic operation, but rather REPLACES the corresponding value in the original datetime with the value(s) in relativedelta. years, months, weeks, days, hours, minutes, seconds, microseconds: Relative information, may be negative (argument is plural); adding or subtracting a relativedelta with relative information performs the corresponding aritmetic operation on the original datetime value with the information in the relativedelta. weekday: One of the weekday instances (MO, TU, etc). These instances may receive a parameter N, specifying the Nth weekday, which could be positive or negative (like MO(+1) or MO(-2). Not specifying it is the same as specifying +1. You can also use an integer, where 0=MO. leapdays: Will add given days to the date found, if year is a leap year, and the date found is post 28 of february. yearday, nlyearday: Set the yearday or the non-leap year day (jump leap days). These are converted to day/month/leapdays information. Here is the behavior of operations with relativedelta: 1. Calculate the absolute year, using the 'year' argument, or the original datetime year, if the argument is not present. 2. Add the relative 'years' argument to the absolute year. 3. Do steps 1 and 2 for month/months. 4. Calculate the absolute day, using the 'day' argument, or the original datetime day, if the argument is not present. Then, subtract from the day until it fits in the year and month found after their operations. 5. Add the relative 'days' argument to the absolute day. Notice that the 'weeks' argument is multiplied by 7 and added to 'days'. 6. Do steps 1 and 2 for hour/hours, minute/minutes, second/seconds, microsecond/microseconds. 7. If the 'weekday' argument is present, calculate the weekday, with the given (wday, nth) tuple. wday is the index of the weekday (0-6, 0=Mon), and nth is the number of weeks to add forward or backward, depending on its signal. Notice that if the calculated date is already Monday, for example, using (0, 1) or (0, -1) won't change the day. """ def __init__(self, dt1=None, dt2=None, years=0, months=0, days=0, leapdays=0, weeks=0, hours=0, minutes=0, seconds=0, microseconds=0, year=None, month=None, day=None, weekday=None, yearday=None, nlyearday=None, hour=None, minute=None, second=None, microsecond=None): if dt1 and dt2: # datetime is a subclass of date. So both must be date if not (isinstance(dt1, datetime.date) and isinstance(dt2, datetime.date)): raise TypeError("relativedelta only diffs datetime/date") # We allow two dates, or two datetimes, so we coerce them to be # of the same type if (isinstance(dt1, datetime.datetime) != isinstance(dt2, datetime.datetime)): if not isinstance(dt1, datetime.datetime): dt1 = datetime.datetime.fromordinal(dt1.toordinal()) elif not isinstance(dt2, datetime.datetime): dt2 = datetime.datetime.fromordinal(dt2.toordinal()) self.years = 0 self.months = 0 self.days = 0 self.leapdays = 0 self.hours = 0 self.minutes = 0 self.seconds = 0 self.microseconds = 0 self.year = None self.month = None self.day = None self.weekday = None self.hour = None self.minute = None self.second = None self.microsecond = None self._has_time = 0 months = (dt1.year*12+dt1.month)-(dt2.year*12+dt2.month) self._set_months(months) dtm = self.__radd__(dt2) if dt1 < dt2: while dt1 > dtm: months += 1 self._set_months(months) dtm = self.__radd__(dt2) else: while dt1 < dtm: months -= 1 self._set_months(months) dtm = self.__radd__(dt2) delta = dt1 - dtm self.seconds = delta.seconds+delta.days*86400 self.microseconds = delta.microseconds else: self.years = years self.months = months self.days = days+weeks*7 self.leapdays = leapdays self.hours = hours self.minutes = minutes self.seconds = seconds self.microseconds = microseconds self.year = year self.month = month self.day = day self.hour = hour self.minute = minute self.second = second self.microsecond = microsecond if isinstance(weekday, integer_types): self.weekday = weekdays[weekday] else: self.weekday = weekday yday = 0 if nlyearday: yday = nlyearday elif yearday: yday = yearday if yearday > 59: self.leapdays = -1 if yday: ydayidx = [31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 366] for idx, ydays in enumerate(ydayidx): if yday <= ydays: self.month = idx+1 if idx == 0: self.day = yday else: self.day = yday-ydayidx[idx-1] break else: raise ValueError("invalid year day (%d)" % yday) self._fix() def _fix(self): if abs(self.microseconds) > 999999: s = self.microseconds//abs(self.microseconds) div, mod = divmod(self.microseconds*s, 1000000) self.microseconds = mod*s self.seconds += div*s if abs(self.seconds) > 59: s = self.seconds//abs(self.seconds) div, mod = divmod(self.seconds*s, 60) self.seconds = mod*s self.minutes += div*s if abs(self.minutes) > 59: s = self.minutes//abs(self.minutes) div, mod = divmod(self.minutes*s, 60) self.minutes = mod*s self.hours += div*s if abs(self.hours) > 23: s = self.hours//abs(self.hours) div, mod = divmod(self.hours*s, 24) self.hours = mod*s self.days += div*s if abs(self.months) > 11: s = self.months//abs(self.months) div, mod = divmod(self.months*s, 12) self.months = mod*s self.years += div*s if (self.hours or self.minutes or self.seconds or self.microseconds or self.hour is not None or self.minute is not None or self.second is not None or self.microsecond is not None): self._has_time = 1 else: self._has_time = 0 def _set_months(self, months): self.months = months if abs(self.months) > 11: s = self.months//abs(self.months) div, mod = divmod(self.months*s, 12) self.months = mod*s self.years = div*s else: self.years = 0 def __add__(self, other): if isinstance(other, relativedelta): return relativedelta(years=other.years+self.years, months=other.months+self.months, days=other.days+self.days, hours=other.hours+self.hours, minutes=other.minutes+self.minutes, seconds=other.seconds+self.seconds, microseconds=(other.microseconds + self.microseconds), leapdays=other.leapdays or self.leapdays, year=other.year or self.year, month=other.month or self.month, day=other.day or self.day, weekday=other.weekday or self.weekday, hour=other.hour or self.hour, minute=other.minute or self.minute, second=other.second or self.second, microsecond=(other.microsecond or self.microsecond)) if not isinstance(other, datetime.date): raise TypeError("unsupported type for add operation") elif self._has_time and not isinstance(other, datetime.datetime): other = datetime.datetime.fromordinal(other.toordinal()) year = (self.year or other.year)+self.years month = self.month or other.month if self.months: assert 1 <= abs(self.months) <= 12 month += self.months if month > 12: year += 1 month -= 12 elif month < 1: year -= 1 month += 12 day = min(calendar.monthrange(year, month)[1], self.day or other.day) repl = {"year": year, "month": month, "day": day} for attr in ["hour", "minute", "second", "microsecond"]: value = getattr(self, attr) if value is not None: repl[attr] = value days = self.days if self.leapdays and month > 2 and calendar.isleap(year): days += self.leapdays ret = (other.replace(**repl) + datetime.timedelta(days=days, hours=self.hours, minutes=self.minutes, seconds=self.seconds, microseconds=self.microseconds)) if self.weekday: weekday, nth = self.weekday.weekday, self.weekday.n or 1 jumpdays = (abs(nth)-1)*7 if nth > 0: jumpdays += (7-ret.weekday()+weekday) % 7 else: jumpdays += (ret.weekday()-weekday) % 7 jumpdays *= -1 ret += datetime.timedelta(days=jumpdays) return ret def __radd__(self, other): return self.__add__(other) def __rsub__(self, other): return self.__neg__().__radd__(other) def __sub__(self, other): if not isinstance(other, relativedelta): raise TypeError("unsupported type for sub operation") return relativedelta(years=self.years-other.years, months=self.months-other.months, days=self.days-other.days, hours=self.hours-other.hours, minutes=self.minutes-other.minutes, seconds=self.seconds-other.seconds, microseconds=self.microseconds-other.microseconds, leapdays=self.leapdays or other.leapdays, year=self.year or other.year, month=self.month or other.month, day=self.day or other.day, weekday=self.weekday or other.weekday, hour=self.hour or other.hour, minute=self.minute or other.minute, second=self.second or other.second, microsecond=self.microsecond or other.microsecond) def __neg__(self): return relativedelta(years=-self.years, months=-self.months, days=-self.days, hours=-self.hours, minutes=-self.minutes, seconds=-self.seconds, microseconds=-self.microseconds, leapdays=self.leapdays, year=self.year, month=self.month, day=self.day, weekday=self.weekday, hour=self.hour, minute=self.minute, second=self.second, microsecond=self.microsecond) def __bool__(self): return not (not self.years and not self.months and not self.days and not self.hours and not self.minutes and not self.seconds and not self.microseconds and not self.leapdays and self.year is None and self.month is None and self.day is None and self.weekday is None and self.hour is None and self.minute is None and self.second is None and self.microsecond is None) # Compatibility with Python 2.x __nonzero__ = __bool__ def __mul__(self, other): f = float(other) return relativedelta(years=int(self.years*f), months=int(self.months*f), days=int(self.days*f), hours=int(self.hours*f), minutes=int(self.minutes*f), seconds=int(self.seconds*f), microseconds=int(self.microseconds*f), leapdays=self.leapdays, year=self.year, month=self.month, day=self.day, weekday=self.weekday, hour=self.hour, minute=self.minute, second=self.second, microsecond=self.microsecond) __rmul__ = __mul__ def __eq__(self, other): if not isinstance(other, relativedelta): return False if self.weekday or other.weekday: if not self.weekday or not other.weekday: return False if self.weekday.weekday != other.weekday.weekday: return False n1, n2 = self.weekday.n, other.weekday.n if n1 != n2 and not ((not n1 or n1 == 1) and (not n2 or n2 == 1)): return False return (self.years == other.years and self.months == other.months and self.days == other.days and self.hours == other.hours and self.minutes == other.minutes and self.seconds == other.seconds and self.leapdays == other.leapdays and self.year == other.year and self.month == other.month and self.day == other.day and self.hour == other.hour and self.minute == other.minute and self.second == other.second and self.microsecond == other.microsecond) def __ne__(self, other): return not self.__eq__(other) def __div__(self, other): return self.__mul__(1/float(other)) __truediv__ = __div__ def __repr__(self): l = [] for attr in ["years", "months", "days", "leapdays", "hours", "minutes", "seconds", "microseconds"]: value = getattr(self, attr) if value: l.append("%s=%+d" % (attr, value)) for attr in ["year", "month", "day", "weekday", "hour", "minute", "second", "microsecond"]: value = getattr(self, attr) if value is not None: l.append("%s=%s" % (attr, repr(value))) return "%s(%s)" % (self.__class__.__name__, ", ".join(l)) # vim:ts=4:sw=4:et python-dateutil-2.4.2/dateutil/rrule.py0000666000000000000000000014706112506517720016337 0ustar 00000000000000# -*- coding: utf-8 -*- """ The rrule module offers a small, complete, and very fast, implementation of the recurrence rules documented in the `iCalendar RFC `_, including support for caching of results. """ import itertools import datetime import calendar import sys from fractions import gcd from six import advance_iterator, integer_types from six.moves import _thread __all__ = ["rrule", "rruleset", "rrulestr", "YEARLY", "MONTHLY", "WEEKLY", "DAILY", "HOURLY", "MINUTELY", "SECONDLY", "MO", "TU", "WE", "TH", "FR", "SA", "SU"] # Every mask is 7 days longer to handle cross-year weekly periods. M366MASK = tuple([1]*31+[2]*29+[3]*31+[4]*30+[5]*31+[6]*30 + [7]*31+[8]*31+[9]*30+[10]*31+[11]*30+[12]*31+[1]*7) M365MASK = list(M366MASK) M29, M30, M31 = list(range(1, 30)), list(range(1, 31)), list(range(1, 32)) MDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7]) MDAY365MASK = list(MDAY366MASK) M29, M30, M31 = list(range(-29, 0)), list(range(-30, 0)), list(range(-31, 0)) NMDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7]) NMDAY365MASK = list(NMDAY366MASK) M366RANGE = (0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366) M365RANGE = (0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365) WDAYMASK = [0, 1, 2, 3, 4, 5, 6]*55 del M29, M30, M31, M365MASK[59], MDAY365MASK[59], NMDAY365MASK[31] MDAY365MASK = tuple(MDAY365MASK) M365MASK = tuple(M365MASK) (YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, SECONDLY) = list(range(7)) # Imported on demand. easter = None parser = None class weekday(object): __slots__ = ["weekday", "n"] def __init__(self, weekday, n=None): if n == 0: raise ValueError("Can't create weekday with n == 0") self.weekday = weekday self.n = n def __call__(self, n): if n == self.n: return self else: return self.__class__(self.weekday, n) def __eq__(self, other): try: if self.weekday != other.weekday or self.n != other.n: return False except AttributeError: return False return True def __repr__(self): s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday] if not self.n: return s else: return "%s(%+d)" % (s, self.n) MO, TU, WE, TH, FR, SA, SU = weekdays = tuple([weekday(x) for x in range(7)]) class rrulebase(object): def __init__(self, cache=False): if cache: self._cache = [] self._cache_lock = _thread.allocate_lock() self._cache_gen = self._iter() self._cache_complete = False else: self._cache = None self._cache_complete = False self._len = None def __iter__(self): if self._cache_complete: return iter(self._cache) elif self._cache is None: return self._iter() else: return self._iter_cached() def _iter_cached(self): i = 0 gen = self._cache_gen cache = self._cache acquire = self._cache_lock.acquire release = self._cache_lock.release while gen: if i == len(cache): acquire() if self._cache_complete: break try: for j in range(10): cache.append(advance_iterator(gen)) except StopIteration: self._cache_gen = gen = None self._cache_complete = True break release() yield cache[i] i += 1 while i < self._len: yield cache[i] i += 1 def __getitem__(self, item): if self._cache_complete: return self._cache[item] elif isinstance(item, slice): if item.step and item.step < 0: return list(iter(self))[item] else: return list(itertools.islice(self, item.start or 0, item.stop or sys.maxsize, item.step or 1)) elif item >= 0: gen = iter(self) try: for i in range(item+1): res = advance_iterator(gen) except StopIteration: raise IndexError return res else: return list(iter(self))[item] def __contains__(self, item): if self._cache_complete: return item in self._cache else: for i in self: if i == item: return True elif i > item: return False return False # __len__() introduces a large performance penality. def count(self): """ Returns the number of recurrences in this set. It will have go trough the whole recurrence, if this hasn't been done before. """ if self._len is None: for x in self: pass return self._len def before(self, dt, inc=False): """ Returns the last recurrence before the given datetime instance. The inc keyword defines what happens if dt is an occurrence. With inc=True, if dt itself is an occurrence, it will be returned. """ if self._cache_complete: gen = self._cache else: gen = self last = None if inc: for i in gen: if i > dt: break last = i else: for i in gen: if i >= dt: break last = i return last def after(self, dt, inc=False): """ Returns the first recurrence after the given datetime instance. The inc keyword defines what happens if dt is an occurrence. With inc=True, if dt itself is an occurrence, it will be returned. """ if self._cache_complete: gen = self._cache else: gen = self if inc: for i in gen: if i >= dt: return i else: for i in gen: if i > dt: return i return None def between(self, after, before, inc=False): """ Returns all the occurrences of the rrule between after and before. The inc keyword defines what happens if after and/or before are themselves occurrences. With inc=True, they will be included in the list, if they are found in the recurrence set. """ if self._cache_complete: gen = self._cache else: gen = self started = False l = [] if inc: for i in gen: if i > before: break elif not started: if i >= after: started = True l.append(i) else: l.append(i) else: for i in gen: if i >= before: break elif not started: if i > after: started = True l.append(i) else: l.append(i) return l class rrule(rrulebase): """ That's the base of the rrule operation. It accepts all the keywords defined in the RFC as its constructor parameters (except byday, which was renamed to byweekday) and more. The constructor prototype is:: rrule(freq) Where freq must be one of YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, or SECONDLY. Additionally, it supports the following keyword arguments: :param cache: If given, it must be a boolean value specifying to enable or disable caching of results. If you will use the same rrule instance multiple times, enabling caching will improve the performance considerably. :param dtstart: The recurrence start. Besides being the base for the recurrence, missing parameters in the final recurrence instances will also be extracted from this date. If not given, datetime.now() will be used instead. :param interval: The interval between each freq iteration. For example, when using YEARLY, an interval of 2 means once every two years, but with HOURLY, it means once every two hours. The default interval is 1. :param wkst: The week start day. Must be one of the MO, TU, WE constants, or an integer, specifying the first day of the week. This will affect recurrences based on weekly periods. The default week start is got from calendar.firstweekday(), and may be modified by calendar.setfirstweekday(). :param count: How many occurrences will be generated. :param until: If given, this must be a datetime instance, that will specify the limit of the recurrence. If a recurrence instance happens to be the same as the datetime instance given in the until keyword, this will be the last occurrence. :param bysetpos: If given, it must be either an integer, or a sequence of integers, positive or negative. Each given integer will specify an occurrence number, corresponding to the nth occurrence of the rule inside the frequency period. For example, a bysetpos of -1 if combined with a MONTHLY frequency, and a byweekday of (MO, TU, WE, TH, FR), will result in the last work day of every month. :param bymonth: If given, it must be either an integer, or a sequence of integers, meaning the months to apply the recurrence to. :param bymonthday: If given, it must be either an integer, or a sequence of integers, meaning the month days to apply the recurrence to. :param byyearday: If given, it must be either an integer, or a sequence of integers, meaning the year days to apply the recurrence to. :param byweekno: If given, it must be either an integer, or a sequence of integers, meaning the week numbers to apply the recurrence to. Week numbers have the meaning described in ISO8601, that is, the first week of the year is that containing at least four days of the new year. :param byweekday: If given, it must be either an integer (0 == MO), a sequence of integers, one of the weekday constants (MO, TU, etc), or a sequence of these constants. When given, these variables will define the weekdays where the recurrence will be applied. It's also possible to use an argument n for the weekday instances, which will mean the nth occurrence of this weekday in the period. For example, with MONTHLY, or with YEARLY and BYMONTH, using FR(+1) in byweekday will specify the first friday of the month where the recurrence happens. Notice that in the RFC documentation, this is specified as BYDAY, but was renamed to avoid the ambiguity of that keyword. :param byhour: If given, it must be either an integer, or a sequence of integers, meaning the hours to apply the recurrence to. :param byminute: If given, it must be either an integer, or a sequence of integers, meaning the minutes to apply the recurrence to. :param bysecond: If given, it must be either an integer, or a sequence of integers, meaning the seconds to apply the recurrence to. :param byeaster: If given, it must be either an integer, or a sequence of integers, positive or negative. Each integer will define an offset from the Easter Sunday. Passing the offset 0 to byeaster will yield the Easter Sunday itself. This is an extension to the RFC specification. """ def __init__(self, freq, dtstart=None, interval=1, wkst=None, count=None, until=None, bysetpos=None, bymonth=None, bymonthday=None, byyearday=None, byeaster=None, byweekno=None, byweekday=None, byhour=None, byminute=None, bysecond=None, cache=False): super(rrule, self).__init__(cache) global easter if not dtstart: dtstart = datetime.datetime.now().replace(microsecond=0) elif not isinstance(dtstart, datetime.datetime): dtstart = datetime.datetime.fromordinal(dtstart.toordinal()) else: dtstart = dtstart.replace(microsecond=0) self._dtstart = dtstart self._tzinfo = dtstart.tzinfo self._freq = freq self._interval = interval self._count = count if until and not isinstance(until, datetime.datetime): until = datetime.datetime.fromordinal(until.toordinal()) self._until = until if wkst is None: self._wkst = calendar.firstweekday() elif isinstance(wkst, integer_types): self._wkst = wkst else: self._wkst = wkst.weekday if bysetpos is None: self._bysetpos = None elif isinstance(bysetpos, integer_types): if bysetpos == 0 or not (-366 <= bysetpos <= 366): raise ValueError("bysetpos must be between 1 and 366, " "or between -366 and -1") self._bysetpos = (bysetpos,) else: self._bysetpos = tuple(bysetpos) for pos in self._bysetpos: if pos == 0 or not (-366 <= pos <= 366): raise ValueError("bysetpos must be between 1 and 366, " "or between -366 and -1") if (byweekno is None and byyearday is None and bymonthday is None and byweekday is None and byeaster is None): if freq == YEARLY: if bymonth is None: bymonth = dtstart.month bymonthday = dtstart.day elif freq == MONTHLY: bymonthday = dtstart.day elif freq == WEEKLY: byweekday = dtstart.weekday() # bymonth if bymonth is None: self._bymonth = None else: if isinstance(bymonth, integer_types): bymonth = (bymonth,) self._bymonth = tuple(sorted(set(bymonth))) # byyearday if byyearday is None: self._byyearday = None else: if isinstance(byyearday, integer_types): byyearday = (byyearday,) self._byyearday = tuple(sorted(set(byyearday))) # byeaster if byeaster is not None: if not easter: from dateutil import easter if isinstance(byeaster, integer_types): self._byeaster = (byeaster,) else: self._byeaster = tuple(sorted(byeaster)) else: self._byeaster = None # bymonthay if bymonthday is None: self._bymonthday = () self._bynmonthday = () else: if isinstance(bymonthday, integer_types): bymonthday = (bymonthday,) self._bymonthday = tuple(sorted(set([x for x in bymonthday if x > 0]))) self._bynmonthday = tuple(sorted(set([x for x in bymonthday if x < 0]))) # byweekno if byweekno is None: self._byweekno = None else: if isinstance(byweekno, integer_types): byweekno = (byweekno,) self._byweekno = tuple(sorted(set(byweekno))) # byweekday / bynweekday if byweekday is None: self._byweekday = None self._bynweekday = None else: # If it's one of the valid non-sequence types, convert to a # single-element sequence before the iterator that builds the # byweekday set. if isinstance(byweekday, integer_types) or hasattr(byweekday, "n"): byweekday = (byweekday,) self._byweekday = set() self._bynweekday = set() for wday in byweekday: if isinstance(wday, integer_types): self._byweekday.add(wday) elif not wday.n or freq > MONTHLY: self._byweekday.add(wday.weekday) else: self._bynweekday.add((wday.weekday, wday.n)) if not self._byweekday: self._byweekday = None elif not self._bynweekday: self._bynweekday = None if self._byweekday is not None: self._byweekday = tuple(sorted(self._byweekday)) if self._bynweekday is not None: self._bynweekday = tuple(sorted(self._bynweekday)) # byhour if byhour is None: if freq < HOURLY: self._byhour = set((dtstart.hour,)) else: self._byhour = None else: if isinstance(byhour, integer_types): byhour = (byhour,) if freq == HOURLY: self._byhour = self.__construct_byset(start=dtstart.hour, byxxx=byhour, base=24) else: self._byhour = set(byhour) self._byhour = tuple(sorted(self._byhour)) # byminute if byminute is None: if freq < MINUTELY: self._byminute = set((dtstart.minute,)) else: self._byminute = None else: if isinstance(byminute, integer_types): byminute = (byminute,) if freq == MINUTELY: self._byminute = self.__construct_byset(start=dtstart.minute, byxxx=byminute, base=60) else: self._byminute = set(byminute) self._byminute = tuple(sorted(self._byminute)) # bysecond if bysecond is None: if freq < SECONDLY: self._bysecond = ((dtstart.second,)) else: self._bysecond = None else: if isinstance(bysecond, integer_types): bysecond = (bysecond,) self._bysecond = set(bysecond) if freq == SECONDLY: self._bysecond = self.__construct_byset(start=dtstart.second, byxxx=bysecond, base=60) else: self._bysecond = set(bysecond) self._bysecond = tuple(sorted(self._bysecond)) if self._freq >= HOURLY: self._timeset = None else: self._timeset = [] for hour in self._byhour: for minute in self._byminute: for second in self._bysecond: self._timeset.append( datetime.time(hour, minute, second, tzinfo=self._tzinfo)) self._timeset.sort() self._timeset = tuple(self._timeset) def _iter(self): year, month, day, hour, minute, second, weekday, yearday, _ = \ self._dtstart.timetuple() # Some local variables to speed things up a bit freq = self._freq interval = self._interval wkst = self._wkst until = self._until bymonth = self._bymonth byweekno = self._byweekno byyearday = self._byyearday byweekday = self._byweekday byeaster = self._byeaster bymonthday = self._bymonthday bynmonthday = self._bynmonthday bysetpos = self._bysetpos byhour = self._byhour byminute = self._byminute bysecond = self._bysecond ii = _iterinfo(self) ii.rebuild(year, month) getdayset = {YEARLY: ii.ydayset, MONTHLY: ii.mdayset, WEEKLY: ii.wdayset, DAILY: ii.ddayset, HOURLY: ii.ddayset, MINUTELY: ii.ddayset, SECONDLY: ii.ddayset}[freq] if freq < HOURLY: timeset = self._timeset else: gettimeset = {HOURLY: ii.htimeset, MINUTELY: ii.mtimeset, SECONDLY: ii.stimeset}[freq] if ((freq >= HOURLY and self._byhour and hour not in self._byhour) or (freq >= MINUTELY and self._byminute and minute not in self._byminute) or (freq >= SECONDLY and self._bysecond and second not in self._bysecond)): timeset = () else: timeset = gettimeset(hour, minute, second) total = 0 count = self._count while True: # Get dayset with the right frequency dayset, start, end = getdayset(year, month, day) # Do the "hard" work ;-) filtered = False for i in dayset[start:end]: if ((bymonth and ii.mmask[i] not in bymonth) or (byweekno and not ii.wnomask[i]) or (byweekday and ii.wdaymask[i] not in byweekday) or (ii.nwdaymask and not ii.nwdaymask[i]) or (byeaster and not ii.eastermask[i]) or ((bymonthday or bynmonthday) and ii.mdaymask[i] not in bymonthday and ii.nmdaymask[i] not in bynmonthday) or (byyearday and ((i < ii.yearlen and i+1 not in byyearday and -ii.yearlen+i not in byyearday) or (i >= ii.yearlen and i+1-ii.yearlen not in byyearday and -ii.nextyearlen+i-ii.yearlen not in byyearday)))): dayset[i] = None filtered = True # Output results if bysetpos and timeset: poslist = [] for pos in bysetpos: if pos < 0: daypos, timepos = divmod(pos, len(timeset)) else: daypos, timepos = divmod(pos-1, len(timeset)) try: i = [x for x in dayset[start:end] if x is not None][daypos] time = timeset[timepos] except IndexError: pass else: date = datetime.date.fromordinal(ii.yearordinal+i) res = datetime.datetime.combine(date, time) if res not in poslist: poslist.append(res) poslist.sort() for res in poslist: if until and res > until: self._len = total return elif res >= self._dtstart: total += 1 yield res if count: count -= 1 if not count: self._len = total return else: for i in dayset[start:end]: if i is not None: date = datetime.date.fromordinal(ii.yearordinal+i) for time in timeset: res = datetime.datetime.combine(date, time) if until and res > until: self._len = total return elif res >= self._dtstart: total += 1 yield res if count: count -= 1 if not count: self._len = total return # Handle frequency and interval fixday = False if freq == YEARLY: year += interval if year > datetime.MAXYEAR: self._len = total return ii.rebuild(year, month) elif freq == MONTHLY: month += interval if month > 12: div, mod = divmod(month, 12) month = mod year += div if month == 0: month = 12 year -= 1 if year > datetime.MAXYEAR: self._len = total return ii.rebuild(year, month) elif freq == WEEKLY: if wkst > weekday: day += -(weekday+1+(6-wkst))+self._interval*7 else: day += -(weekday-wkst)+self._interval*7 weekday = wkst fixday = True elif freq == DAILY: day += interval fixday = True elif freq == HOURLY: if filtered: # Jump to one iteration before next day hour += ((23-hour)//interval)*interval if byhour: ndays, hour = self.__mod_distance(value=hour, byxxx=self._byhour, base=24) else: ndays, hour = divmod(hour+interval, 24) if ndays: day += ndays fixday = True timeset = gettimeset(hour, minute, second) elif freq == MINUTELY: if filtered: # Jump to one iteration before next day minute += ((1439-(hour*60+minute))//interval)*interval valid = False rep_rate = (24*60) for j in range(rep_rate // gcd(interval, rep_rate)): if byminute: nhours, minute = \ self.__mod_distance(value=minute, byxxx=self._byminute, base=60) else: nhours, minute = divmod(minute+interval, 60) div, hour = divmod(hour+nhours, 24) if div: day += div fixday = True filtered = False if not byhour or hour in byhour: valid = True break if not valid: raise ValueError('Invalid combination of interval and ' + 'byhour resulting in empty rule.') timeset = gettimeset(hour, minute, second) elif freq == SECONDLY: if filtered: # Jump to one iteration before next day second += (((86399-(hour*3600+minute*60+second)) // interval)*interval) rep_rate = (24*3600) valid = False for j in range(0, rep_rate // gcd(interval, rep_rate)): if bysecond: nminutes, second = \ self.__mod_distance(value=second, byxxx=self._bysecond, base=60) else: nminutes, second = divmod(second+interval, 60) div, minute = divmod(minute+nminutes, 60) if div: hour += div div, hour = divmod(hour, 24) if div: day += div fixday = True if ((not byhour or hour in byhour) and (not byminute or minute in byminute) and (not bysecond or second in bysecond)): valid = True break if not valid: raise ValueError('Invalid combination of interval, ' + 'byhour and byminute resulting in empty' + ' rule.') timeset = gettimeset(hour, minute, second) if fixday and day > 28: daysinmonth = calendar.monthrange(year, month)[1] if day > daysinmonth: while day > daysinmonth: day -= daysinmonth month += 1 if month == 13: month = 1 year += 1 if year > datetime.MAXYEAR: self._len = total return daysinmonth = calendar.monthrange(year, month)[1] ii.rebuild(year, month) def __construct_byset(self, start, byxxx, base): """ If a `BYXXX` sequence is passed to the constructor at the same level as `FREQ` (e.g. `FREQ=HOURLY,BYHOUR={2,4,7},INTERVAL=3`), there are some specifications which cannot be reached given some starting conditions. This occurs whenever the interval is not coprime with the base of a given unit and the difference between the starting position and the ending position is not coprime with the greatest common denominator between the interval and the base. For example, with a FREQ of hourly starting at 17:00 and an interval of 4, the only valid values for BYHOUR would be {21, 1, 5, 9, 13, 17}, because 4 and 24 are not coprime. :param start: Specifies the starting position. :param byxxx: An iterable containing the list of allowed values. :param base: The largest allowable value for the specified frequency (e.g. 24 hours, 60 minutes). This does not preserve the type of the iterable, returning a set, since the values should be unique and the order is irrelevant, this will speed up later lookups. In the event of an empty set, raises a :exception:`ValueError`, as this results in an empty rrule. """ cset = set() # Support a single byxxx value. if isinstance(byxxx, integer_types): byxxx = (byxxx, ) for num in byxxx: i_gcd = gcd(self._interval, base) # Use divmod rather than % because we need to wrap negative nums. if i_gcd == 1 or divmod(num - start, i_gcd)[1] == 0: cset.add(num) if len(cset) == 0: raise ValueError("Invalid rrule byxxx generates an empty set.") return cset def __mod_distance(self, value, byxxx, base): """ Calculates the next value in a sequence where the `FREQ` parameter is specified along with a `BYXXX` parameter at the same "level" (e.g. `HOURLY` specified with `BYHOUR`). :param value: The old value of the component. :param byxxx: The `BYXXX` set, which should have been generated by `rrule._construct_byset`, or something else which checks that a valid rule is present. :param base: The largest allowable value for the specified frequency (e.g. 24 hours, 60 minutes). If a valid value is not found after `base` iterations (the maximum number before the sequence would start to repeat), this raises a :exception:`ValueError`, as no valid values were found. This returns a tuple of `divmod(n*interval, base)`, where `n` is the smallest number of `interval` repetitions until the next specified value in `byxxx` is found. """ accumulator = 0 for ii in range(1, base + 1): # Using divmod() over % to account for negative intervals div, value = divmod(value + self._interval, base) accumulator += div if value in byxxx: return (accumulator, value) class _iterinfo(object): __slots__ = ["rrule", "lastyear", "lastmonth", "yearlen", "nextyearlen", "yearordinal", "yearweekday", "mmask", "mrange", "mdaymask", "nmdaymask", "wdaymask", "wnomask", "nwdaymask", "eastermask"] def __init__(self, rrule): for attr in self.__slots__: setattr(self, attr, None) self.rrule = rrule def rebuild(self, year, month): # Every mask is 7 days longer to handle cross-year weekly periods. rr = self.rrule if year != self.lastyear: self.yearlen = 365+calendar.isleap(year) self.nextyearlen = 365+calendar.isleap(year+1) firstyday = datetime.date(year, 1, 1) self.yearordinal = firstyday.toordinal() self.yearweekday = firstyday.weekday() wday = datetime.date(year, 1, 1).weekday() if self.yearlen == 365: self.mmask = M365MASK self.mdaymask = MDAY365MASK self.nmdaymask = NMDAY365MASK self.wdaymask = WDAYMASK[wday:] self.mrange = M365RANGE else: self.mmask = M366MASK self.mdaymask = MDAY366MASK self.nmdaymask = NMDAY366MASK self.wdaymask = WDAYMASK[wday:] self.mrange = M366RANGE if not rr._byweekno: self.wnomask = None else: self.wnomask = [0]*(self.yearlen+7) # no1wkst = firstwkst = self.wdaymask.index(rr._wkst) no1wkst = firstwkst = (7-self.yearweekday+rr._wkst) % 7 if no1wkst >= 4: no1wkst = 0 # Number of days in the year, plus the days we got # from last year. wyearlen = self.yearlen+(self.yearweekday-rr._wkst) % 7 else: # Number of days in the year, minus the days we # left in last year. wyearlen = self.yearlen-no1wkst div, mod = divmod(wyearlen, 7) numweeks = div+mod//4 for n in rr._byweekno: if n < 0: n += numweeks+1 if not (0 < n <= numweeks): continue if n > 1: i = no1wkst+(n-1)*7 if no1wkst != firstwkst: i -= 7-firstwkst else: i = no1wkst for j in range(7): self.wnomask[i] = 1 i += 1 if self.wdaymask[i] == rr._wkst: break if 1 in rr._byweekno: # Check week number 1 of next year as well # TODO: Check -numweeks for next year. i = no1wkst+numweeks*7 if no1wkst != firstwkst: i -= 7-firstwkst if i < self.yearlen: # If week starts in next year, we # don't care about it. for j in range(7): self.wnomask[i] = 1 i += 1 if self.wdaymask[i] == rr._wkst: break if no1wkst: # Check last week number of last year as # well. If no1wkst is 0, either the year # started on week start, or week number 1 # got days from last year, so there are no # days from last year's last week number in # this year. if -1 not in rr._byweekno: lyearweekday = datetime.date(year-1, 1, 1).weekday() lno1wkst = (7-lyearweekday+rr._wkst) % 7 lyearlen = 365+calendar.isleap(year-1) if lno1wkst >= 4: lno1wkst = 0 lnumweeks = 52+(lyearlen + (lyearweekday-rr._wkst) % 7) % 7//4 else: lnumweeks = 52+(self.yearlen-no1wkst) % 7//4 else: lnumweeks = -1 if lnumweeks in rr._byweekno: for i in range(no1wkst): self.wnomask[i] = 1 if (rr._bynweekday and (month != self.lastmonth or year != self.lastyear)): ranges = [] if rr._freq == YEARLY: if rr._bymonth: for month in rr._bymonth: ranges.append(self.mrange[month-1:month+1]) else: ranges = [(0, self.yearlen)] elif rr._freq == MONTHLY: ranges = [self.mrange[month-1:month+1]] if ranges: # Weekly frequency won't get here, so we may not # care about cross-year weekly periods. self.nwdaymask = [0]*self.yearlen for first, last in ranges: last -= 1 for wday, n in rr._bynweekday: if n < 0: i = last+(n+1)*7 i -= (self.wdaymask[i]-wday) % 7 else: i = first+(n-1)*7 i += (7-self.wdaymask[i]+wday) % 7 if first <= i <= last: self.nwdaymask[i] = 1 if rr._byeaster: self.eastermask = [0]*(self.yearlen+7) eyday = easter.easter(year).toordinal()-self.yearordinal for offset in rr._byeaster: self.eastermask[eyday+offset] = 1 self.lastyear = year self.lastmonth = month def ydayset(self, year, month, day): return list(range(self.yearlen)), 0, self.yearlen def mdayset(self, year, month, day): dset = [None]*self.yearlen start, end = self.mrange[month-1:month+1] for i in range(start, end): dset[i] = i return dset, start, end def wdayset(self, year, month, day): # We need to handle cross-year weeks here. dset = [None]*(self.yearlen+7) i = datetime.date(year, month, day).toordinal()-self.yearordinal start = i for j in range(7): dset[i] = i i += 1 # if (not (0 <= i < self.yearlen) or # self.wdaymask[i] == self.rrule._wkst): # This will cross the year boundary, if necessary. if self.wdaymask[i] == self.rrule._wkst: break return dset, start, i def ddayset(self, year, month, day): dset = [None]*self.yearlen i = datetime.date(year, month, day).toordinal()-self.yearordinal dset[i] = i return dset, i, i+1 def htimeset(self, hour, minute, second): tset = [] rr = self.rrule for minute in rr._byminute: for second in rr._bysecond: tset.append(datetime.time(hour, minute, second, tzinfo=rr._tzinfo)) tset.sort() return tset def mtimeset(self, hour, minute, second): tset = [] rr = self.rrule for second in rr._bysecond: tset.append(datetime.time(hour, minute, second, tzinfo=rr._tzinfo)) tset.sort() return tset def stimeset(self, hour, minute, second): return (datetime.time(hour, minute, second, tzinfo=self.rrule._tzinfo),) class rruleset(rrulebase): """ The rruleset type allows more complex recurrence setups, mixing multiple rules, dates, exclusion rules, and exclusion dates. The type constructor takes the following keyword arguments: :param cache: If True, caching of results will be enabled, improving performance of multiple queries considerably. """ class _genitem(object): def __init__(self, genlist, gen): try: self.dt = advance_iterator(gen) genlist.append(self) except StopIteration: pass self.genlist = genlist self.gen = gen def __next__(self): try: self.dt = advance_iterator(self.gen) except StopIteration: self.genlist.remove(self) next = __next__ def __lt__(self, other): return self.dt < other.dt def __gt__(self, other): return self.dt > other.dt def __eq__(self, other): return self.dt == other.dt def __ne__(self, other): return self.dt != other.dt def __init__(self, cache=False): super(rruleset, self).__init__(cache) self._rrule = [] self._rdate = [] self._exrule = [] self._exdate = [] def rrule(self, rrule): """ Include the given :py:class:`rrule` instance in the recurrence set generation. """ self._rrule.append(rrule) def rdate(self, rdate): """ Include the given :py:class:`datetime` instance in the recurrence set generation. """ self._rdate.append(rdate) def exrule(self, exrule): """ Include the given rrule instance in the recurrence set exclusion list. Dates which are part of the given recurrence rules will not be generated, even if some inclusive rrule or rdate matches them. """ self._exrule.append(exrule) def exdate(self, exdate): """ Include the given datetime instance in the recurrence set exclusion list. Dates included that way will not be generated, even if some inclusive rrule or rdate matches them. """ self._exdate.append(exdate) def _iter(self): rlist = [] self._rdate.sort() self._genitem(rlist, iter(self._rdate)) for gen in [iter(x) for x in self._rrule]: self._genitem(rlist, gen) rlist.sort() exlist = [] self._exdate.sort() self._genitem(exlist, iter(self._exdate)) for gen in [iter(x) for x in self._exrule]: self._genitem(exlist, gen) exlist.sort() lastdt = None total = 0 while rlist: ritem = rlist[0] if not lastdt or lastdt != ritem.dt: while exlist and exlist[0] < ritem: advance_iterator(exlist[0]) exlist.sort() if not exlist or ritem != exlist[0]: total += 1 yield ritem.dt lastdt = ritem.dt advance_iterator(ritem) rlist.sort() self._len = total class _rrulestr(object): _freq_map = {"YEARLY": YEARLY, "MONTHLY": MONTHLY, "WEEKLY": WEEKLY, "DAILY": DAILY, "HOURLY": HOURLY, "MINUTELY": MINUTELY, "SECONDLY": SECONDLY} _weekday_map = {"MO": 0, "TU": 1, "WE": 2, "TH": 3, "FR": 4, "SA": 5, "SU": 6} def _handle_int(self, rrkwargs, name, value, **kwargs): rrkwargs[name.lower()] = int(value) def _handle_int_list(self, rrkwargs, name, value, **kwargs): rrkwargs[name.lower()] = [int(x) for x in value.split(',')] _handle_INTERVAL = _handle_int _handle_COUNT = _handle_int _handle_BYSETPOS = _handle_int_list _handle_BYMONTH = _handle_int_list _handle_BYMONTHDAY = _handle_int_list _handle_BYYEARDAY = _handle_int_list _handle_BYEASTER = _handle_int_list _handle_BYWEEKNO = _handle_int_list _handle_BYHOUR = _handle_int_list _handle_BYMINUTE = _handle_int_list _handle_BYSECOND = _handle_int_list def _handle_FREQ(self, rrkwargs, name, value, **kwargs): rrkwargs["freq"] = self._freq_map[value] def _handle_UNTIL(self, rrkwargs, name, value, **kwargs): global parser if not parser: from dateutil import parser try: rrkwargs["until"] = parser.parse(value, ignoretz=kwargs.get("ignoretz"), tzinfos=kwargs.get("tzinfos")) except ValueError: raise ValueError("invalid until date") def _handle_WKST(self, rrkwargs, name, value, **kwargs): rrkwargs["wkst"] = self._weekday_map[value] def _handle_BYWEEKDAY(self, rrkwargs, name, value, **kwarsg): l = [] for wday in value.split(','): for i in range(len(wday)): if wday[i] not in '+-0123456789': break n = wday[:i] or None w = wday[i:] if n: n = int(n) l.append(weekdays[self._weekday_map[w]](n)) rrkwargs["byweekday"] = l _handle_BYDAY = _handle_BYWEEKDAY def _parse_rfc_rrule(self, line, dtstart=None, cache=False, ignoretz=False, tzinfos=None): if line.find(':') != -1: name, value = line.split(':') if name != "RRULE": raise ValueError("unknown parameter name") else: value = line rrkwargs = {} for pair in value.split(';'): name, value = pair.split('=') name = name.upper() value = value.upper() try: getattr(self, "_handle_"+name)(rrkwargs, name, value, ignoretz=ignoretz, tzinfos=tzinfos) except AttributeError: raise ValueError("unknown parameter '%s'" % name) except (KeyError, ValueError): raise ValueError("invalid '%s': %s" % (name, value)) return rrule(dtstart=dtstart, cache=cache, **rrkwargs) def _parse_rfc(self, s, dtstart=None, cache=False, unfold=False, forceset=False, compatible=False, ignoretz=False, tzinfos=None): global parser if compatible: forceset = True unfold = True s = s.upper() if not s.strip(): raise ValueError("empty string") if unfold: lines = s.splitlines() i = 0 while i < len(lines): line = lines[i].rstrip() if not line: del lines[i] elif i > 0 and line[0] == " ": lines[i-1] += line[1:] del lines[i] else: i += 1 else: lines = s.split() if (not forceset and len(lines) == 1 and (s.find(':') == -1 or s.startswith('RRULE:'))): return self._parse_rfc_rrule(lines[0], cache=cache, dtstart=dtstart, ignoretz=ignoretz, tzinfos=tzinfos) else: rrulevals = [] rdatevals = [] exrulevals = [] exdatevals = [] for line in lines: if not line: continue if line.find(':') == -1: name = "RRULE" value = line else: name, value = line.split(':', 1) parms = name.split(';') if not parms: raise ValueError("empty property name") name = parms[0] parms = parms[1:] if name == "RRULE": for parm in parms: raise ValueError("unsupported RRULE parm: "+parm) rrulevals.append(value) elif name == "RDATE": for parm in parms: if parm != "VALUE=DATE-TIME": raise ValueError("unsupported RDATE parm: "+parm) rdatevals.append(value) elif name == "EXRULE": for parm in parms: raise ValueError("unsupported EXRULE parm: "+parm) exrulevals.append(value) elif name == "EXDATE": for parm in parms: if parm != "VALUE=DATE-TIME": raise ValueError("unsupported RDATE parm: "+parm) exdatevals.append(value) elif name == "DTSTART": for parm in parms: raise ValueError("unsupported DTSTART parm: "+parm) if not parser: from dateutil import parser dtstart = parser.parse(value, ignoretz=ignoretz, tzinfos=tzinfos) else: raise ValueError("unsupported property: "+name) if (forceset or len(rrulevals) > 1 or rdatevals or exrulevals or exdatevals): if not parser and (rdatevals or exdatevals): from dateutil import parser rset = rruleset(cache=cache) for value in rrulevals: rset.rrule(self._parse_rfc_rrule(value, dtstart=dtstart, ignoretz=ignoretz, tzinfos=tzinfos)) for value in rdatevals: for datestr in value.split(','): rset.rdate(parser.parse(datestr, ignoretz=ignoretz, tzinfos=tzinfos)) for value in exrulevals: rset.exrule(self._parse_rfc_rrule(value, dtstart=dtstart, ignoretz=ignoretz, tzinfos=tzinfos)) for value in exdatevals: for datestr in value.split(','): rset.exdate(parser.parse(datestr, ignoretz=ignoretz, tzinfos=tzinfos)) if compatible and dtstart: rset.rdate(dtstart) return rset else: return self._parse_rfc_rrule(rrulevals[0], dtstart=dtstart, cache=cache, ignoretz=ignoretz, tzinfos=tzinfos) def __call__(self, s, **kwargs): return self._parse_rfc(s, **kwargs) rrulestr = _rrulestr() # vim:ts=4:sw=4:et python-dateutil-2.4.2/dateutil/test/0000777000000000000000000000000012506556557015615 5ustar 00000000000000python-dateutil-2.4.2/dateutil/test/test.py0000666000000000000000000054464312506520253017146 0ustar 00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals import calendar import base64 import sys from six import StringIO, BytesIO, PY3 try: # python2.6 unittest has no skipUnless. So we use unittest2. # if you have python >= 2.7, you don't need unittest2, but it won't harm import unittest2 as unittest except ImportError: import unittest MISSING_TARBALL = ("This test fails if you don't have the dateutil " "timezone file installed. Please read the README") from datetime import * from dateutil.relativedelta import * from dateutil.parser import * from dateutil.easter import * from dateutil.rrule import * from dateutil.tz import * from dateutil import zoneinfo try: from dateutil import tzwin except ImportError: pass class RelativeDeltaTest(unittest.TestCase): now = datetime(2003, 9, 17, 20, 54, 47, 282310) today = date(2003, 9, 17) def testNextMonth(self): self.assertEqual(self.now+relativedelta(months=+1), datetime(2003, 10, 17, 20, 54, 47, 282310)) def testNextMonthPlusOneWeek(self): self.assertEqual(self.now+relativedelta(months=+1, weeks=+1), datetime(2003, 10, 24, 20, 54, 47, 282310)) def testNextMonthPlusOneWeek10am(self): self.assertEqual(self.today + relativedelta(months=+1, weeks=+1, hour=10), datetime(2003, 10, 24, 10, 0)) def testNextMonthPlusOneWeek10amDiff(self): self.assertEqual(relativedelta(datetime(2003, 10, 24, 10, 0), self.today), relativedelta(months=+1, days=+7, hours=+10)) def testOneMonthBeforeOneYear(self): self.assertEqual(self.now+relativedelta(years=+1, months=-1), datetime(2004, 8, 17, 20, 54, 47, 282310)) def testMonthsOfDiffNumOfDays(self): self.assertEqual(date(2003, 1, 27)+relativedelta(months=+1), date(2003, 2, 27)) self.assertEqual(date(2003, 1, 31)+relativedelta(months=+1), date(2003, 2, 28)) self.assertEqual(date(2003, 1, 31)+relativedelta(months=+2), date(2003, 3, 31)) def testMonthsOfDiffNumOfDaysWithYears(self): self.assertEqual(date(2000, 2, 28)+relativedelta(years=+1), date(2001, 2, 28)) self.assertEqual(date(2000, 2, 29)+relativedelta(years=+1), date(2001, 2, 28)) self.assertEqual(date(1999, 2, 28)+relativedelta(years=+1), date(2000, 2, 28)) self.assertEqual(date(1999, 3, 1)+relativedelta(years=+1), date(2000, 3, 1)) self.assertEqual(date(1999, 3, 1)+relativedelta(years=+1), date(2000, 3, 1)) self.assertEqual(date(2001, 2, 28)+relativedelta(years=-1), date(2000, 2, 28)) self.assertEqual(date(2001, 3, 1)+relativedelta(years=-1), date(2000, 3, 1)) def testNextFriday(self): self.assertEqual(self.today+relativedelta(weekday=FR), date(2003, 9, 19)) def testNextFridayInt(self): self.assertEqual(self.today+relativedelta(weekday=calendar.FRIDAY), date(2003, 9, 19)) def testLastFridayInThisMonth(self): self.assertEqual(self.today+relativedelta(day=31, weekday=FR(-1)), date(2003, 9, 26)) def testNextWednesdayIsToday(self): self.assertEqual(self.today+relativedelta(weekday=WE), date(2003, 9, 17)) def testNextWenesdayNotToday(self): self.assertEqual(self.today+relativedelta(days=+1, weekday=WE), date(2003, 9, 24)) def test15thISOYearWeek(self): self.assertEqual(date(2003, 1, 1) + relativedelta(day=4, weeks=+14, weekday=MO(-1)), date(2003, 4, 7)) def testMillenniumAge(self): self.assertEqual(relativedelta(self.now, date(2001, 1, 1)), relativedelta(years=+2, months=+8, days=+16, hours=+20, minutes=+54, seconds=+47, microseconds=+282310)) def testJohnAge(self): self.assertEqual(relativedelta(self.now, datetime(1978, 4, 5, 12, 0)), relativedelta(years=+25, months=+5, days=+12, hours=+8, minutes=+54, seconds=+47, microseconds=+282310)) def testJohnAgeWithDate(self): self.assertEqual(relativedelta(self.today, datetime(1978, 4, 5, 12, 0)), relativedelta(years=+25, months=+5, days=+11, hours=+12)) def testYearDay(self): self.assertEqual(date(2003, 1, 1)+relativedelta(yearday=260), date(2003, 9, 17)) self.assertEqual(date(2002, 1, 1)+relativedelta(yearday=260), date(2002, 9, 17)) self.assertEqual(date(2000, 1, 1)+relativedelta(yearday=260), date(2000, 9, 16)) self.assertEqual(self.today+relativedelta(yearday=261), date(2003, 9, 18)) def testYearDayBug(self): # Tests a problem reported by Adam Ryan. self.assertEqual(date(2010, 1, 1)+relativedelta(yearday=15), date(2010, 1, 15)) def testNonLeapYearDay(self): self.assertEqual(date(2003, 1, 1)+relativedelta(nlyearday=260), date(2003, 9, 17)) self.assertEqual(date(2002, 1, 1)+relativedelta(nlyearday=260), date(2002, 9, 17)) self.assertEqual(date(2000, 1, 1)+relativedelta(nlyearday=260), date(2000, 9, 17)) self.assertEqual(self.today+relativedelta(yearday=261), date(2003, 9, 18)) def testAddition(self): self.assertEqual(relativedelta(days=10) + relativedelta(years=1, months=2, days=3, hours=4, minutes=5, microseconds=6), relativedelta(years=1, months=2, days=13, hours=4, minutes=5, microseconds=6)) def testAdditionToDatetime(self): self.assertEqual(datetime(2000, 1, 1) + relativedelta(days=1), datetime(2000, 1, 2)) def testRightAdditionToDatetime(self): self.assertEqual(relativedelta(days=1) + datetime(2000, 1, 1), datetime(2000, 1, 2)) def testSubtraction(self): self.assertEqual(relativedelta(days=10) - relativedelta(years=1, months=2, days=3, hours=4, minutes=5, microseconds=6), relativedelta(years=-1, months=-2, days=7, hours=-4, minutes=-5, microseconds=-6)) def testRightSubtractionFromDatetime(self): self.assertEqual(datetime(2000, 1, 2) - relativedelta(days=1), datetime(2000, 1, 1)) def testSubractionWithDatetime(self): self.assertRaises(TypeError, lambda x, y: x - y, (relativedelta(days=1), datetime(2000, 1, 1))) def testMultiplication(self): self.assertEqual(datetime(2000, 1, 1) + relativedelta(days=1) * 28, datetime(2000, 1, 29)) self.assertEqual(datetime(2000, 1, 1) + 28 * relativedelta(days=1), datetime(2000, 1, 29)) def testDivision(self): self.assertEqual(datetime(2000, 1, 1) + relativedelta(days=28) / 28, datetime(2000, 1, 2)) def testBoolean(self): self.assertFalse(relativedelta(days=0)) self.assertTrue(relativedelta(days=1)) class RRuleTest(unittest.TestCase): def testYearly(self): self.assertEqual(list(rrule(YEARLY, count=3, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1998, 9, 2, 9, 0), datetime(1999, 9, 2, 9, 0)]) def testYearlyInterval(self): self.assertEqual(list(rrule(YEARLY, count=3, interval=2, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1999, 9, 2, 9, 0), datetime(2001, 9, 2, 9, 0)]) def testYearlyIntervalLarge(self): self.assertEqual(list(rrule(YEARLY, count=3, interval=100, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(2097, 9, 2, 9, 0), datetime(2197, 9, 2, 9, 0)]) def testYearlyByMonth(self): self.assertEqual(list(rrule(YEARLY, count=3, bymonth=(1, 3), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 2, 9, 0), datetime(1998, 3, 2, 9, 0), datetime(1999, 1, 2, 9, 0)]) def testYearlyByMonthDay(self): self.assertEqual(list(rrule(YEARLY, count=3, bymonthday=(1, 3), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 3, 9, 0), datetime(1997, 10, 1, 9, 0), datetime(1997, 10, 3, 9, 0)]) def testYearlyByMonthAndMonthDay(self): self.assertEqual(list(rrule(YEARLY, count=3, bymonth=(1, 3), bymonthday=(5, 7), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 5, 9, 0), datetime(1998, 1, 7, 9, 0), datetime(1998, 3, 5, 9, 0)]) def testYearlyByWeekDay(self): self.assertEqual(list(rrule(YEARLY, count=3, byweekday=(TU, TH), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 4, 9, 0), datetime(1997, 9, 9, 9, 0)]) def testYearlyByNWeekDay(self): self.assertEqual(list(rrule(YEARLY, count=3, byweekday=(TU(1), TH(-1)), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 25, 9, 0), datetime(1998, 1, 6, 9, 0), datetime(1998, 12, 31, 9, 0)]) def testYearlyByNWeekDayLarge(self): self.assertEqual(list(rrule(YEARLY, count=3, byweekday=(TU(3), TH(-3)), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 11, 9, 0), datetime(1998, 1, 20, 9, 0), datetime(1998, 12, 17, 9, 0)]) def testYearlyByMonthAndWeekDay(self): self.assertEqual(list(rrule(YEARLY, count=3, bymonth=(1, 3), byweekday=(TU, TH), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 1, 6, 9, 0), datetime(1998, 1, 8, 9, 0)]) def testYearlyByMonthAndNWeekDay(self): self.assertEqual(list(rrule(YEARLY, count=3, bymonth=(1, 3), byweekday=(TU(1), TH(-1)), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 6, 9, 0), datetime(1998, 1, 29, 9, 0), datetime(1998, 3, 3, 9, 0)]) def testYearlyByMonthAndNWeekDayLarge(self): # This is interesting because the TH(-3) ends up before # the TU(3). self.assertEqual(list(rrule(YEARLY, count=3, bymonth=(1, 3), byweekday=(TU(3), TH(-3)), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 15, 9, 0), datetime(1998, 1, 20, 9, 0), datetime(1998, 3, 12, 9, 0)]) def testYearlyByMonthDayAndWeekDay(self): self.assertEqual(list(rrule(YEARLY, count=3, bymonthday=(1, 3), byweekday=(TU, TH), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 2, 3, 9, 0), datetime(1998, 3, 3, 9, 0)]) def testYearlyByMonthAndMonthDayAndWeekDay(self): self.assertEqual(list(rrule(YEARLY, count=3, bymonth=(1, 3), bymonthday=(1, 3), byweekday=(TU, TH), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 3, 3, 9, 0), datetime(2001, 3, 1, 9, 0)]) def testYearlyByYearDay(self): self.assertEqual(list(rrule(YEARLY, count=4, byyearday=(1, 100, 200, 365), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 31, 9, 0), datetime(1998, 1, 1, 9, 0), datetime(1998, 4, 10, 9, 0), datetime(1998, 7, 19, 9, 0)]) def testYearlyByYearDayNeg(self): self.assertEqual(list(rrule(YEARLY, count=4, byyearday=(-365, -266, -166, -1), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 31, 9, 0), datetime(1998, 1, 1, 9, 0), datetime(1998, 4, 10, 9, 0), datetime(1998, 7, 19, 9, 0)]) def testYearlyByMonthAndYearDay(self): self.assertEqual(list(rrule(YEARLY, count=4, bymonth=(4, 7), byyearday=(1, 100, 200, 365), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 4, 10, 9, 0), datetime(1998, 7, 19, 9, 0), datetime(1999, 4, 10, 9, 0), datetime(1999, 7, 19, 9, 0)]) def testYearlyByMonthAndYearDayNeg(self): self.assertEqual(list(rrule(YEARLY, count=4, bymonth=(4, 7), byyearday=(-365, -266, -166, -1), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 4, 10, 9, 0), datetime(1998, 7, 19, 9, 0), datetime(1999, 4, 10, 9, 0), datetime(1999, 7, 19, 9, 0)]) def testYearlyByWeekNo(self): self.assertEqual(list(rrule(YEARLY, count=3, byweekno=20, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 5, 11, 9, 0), datetime(1998, 5, 12, 9, 0), datetime(1998, 5, 13, 9, 0)]) def testYearlyByWeekNoAndWeekDay(self): # That's a nice one. The first days of week number one # may be in the last year. self.assertEqual(list(rrule(YEARLY, count=3, byweekno=1, byweekday=MO, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 29, 9, 0), datetime(1999, 1, 4, 9, 0), datetime(2000, 1, 3, 9, 0)]) def testYearlyByWeekNoAndWeekDayLarge(self): # Another nice test. The last days of week number 52/53 # may be in the next year. self.assertEqual(list(rrule(YEARLY, count=3, byweekno=52, byweekday=SU, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 28, 9, 0), datetime(1998, 12, 27, 9, 0), datetime(2000, 1, 2, 9, 0)]) def testYearlyByWeekNoAndWeekDayLast(self): self.assertEqual(list(rrule(YEARLY, count=3, byweekno=-1, byweekday=SU, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 28, 9, 0), datetime(1999, 1, 3, 9, 0), datetime(2000, 1, 2, 9, 0)]) def testYearlyByEaster(self): self.assertEqual(list(rrule(YEARLY, count=3, byeaster=0, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 4, 12, 9, 0), datetime(1999, 4, 4, 9, 0), datetime(2000, 4, 23, 9, 0)]) def testYearlyByEasterPos(self): self.assertEqual(list(rrule(YEARLY, count=3, byeaster=1, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 4, 13, 9, 0), datetime(1999, 4, 5, 9, 0), datetime(2000, 4, 24, 9, 0)]) def testYearlyByEasterNeg(self): self.assertEqual(list(rrule(YEARLY, count=3, byeaster=-1, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 4, 11, 9, 0), datetime(1999, 4, 3, 9, 0), datetime(2000, 4, 22, 9, 0)]) def testYearlyByWeekNoAndWeekDay53(self): self.assertEqual(list(rrule(YEARLY, count=3, byweekno=53, byweekday=MO, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 12, 28, 9, 0), datetime(2004, 12, 27, 9, 0), datetime(2009, 12, 28, 9, 0)]) def testYearlyByHour(self): self.assertEqual(list(rrule(YEARLY, count=3, byhour=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 18, 0), datetime(1998, 9, 2, 6, 0), datetime(1998, 9, 2, 18, 0)]) def testYearlyByMinute(self): self.assertEqual(list(rrule(YEARLY, count=3, byminute=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 6), datetime(1997, 9, 2, 9, 18), datetime(1998, 9, 2, 9, 6)]) def testYearlyBySecond(self): self.assertEqual(list(rrule(YEARLY, count=3, bysecond=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0, 6), datetime(1997, 9, 2, 9, 0, 18), datetime(1998, 9, 2, 9, 0, 6)]) def testYearlyByHourAndMinute(self): self.assertEqual(list(rrule(YEARLY, count=3, byhour=(6, 18), byminute=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 18, 6), datetime(1997, 9, 2, 18, 18), datetime(1998, 9, 2, 6, 6)]) def testYearlyByHourAndSecond(self): self.assertEqual(list(rrule(YEARLY, count=3, byhour=(6, 18), bysecond=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 18, 0, 6), datetime(1997, 9, 2, 18, 0, 18), datetime(1998, 9, 2, 6, 0, 6)]) def testYearlyByMinuteAndSecond(self): self.assertEqual(list(rrule(YEARLY, count=3, byminute=(6, 18), bysecond=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 6, 6), datetime(1997, 9, 2, 9, 6, 18), datetime(1997, 9, 2, 9, 18, 6)]) def testYearlyByHourAndMinuteAndSecond(self): self.assertEqual(list(rrule(YEARLY, count=3, byhour=(6, 18), byminute=(6, 18), bysecond=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 18, 6, 6), datetime(1997, 9, 2, 18, 6, 18), datetime(1997, 9, 2, 18, 18, 6)]) def testYearlyBySetPos(self): self.assertEqual(list(rrule(YEARLY, count=3, bymonthday=15, byhour=(6, 18), bysetpos=(3, -3), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 11, 15, 18, 0), datetime(1998, 2, 15, 6, 0), datetime(1998, 11, 15, 18, 0)]) def testMonthly(self): self.assertEqual(list(rrule(MONTHLY, count=3, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 10, 2, 9, 0), datetime(1997, 11, 2, 9, 0)]) def testMonthlyInterval(self): self.assertEqual(list(rrule(MONTHLY, count=3, interval=2, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 11, 2, 9, 0), datetime(1998, 1, 2, 9, 0)]) def testMonthlyIntervalLarge(self): self.assertEqual(list(rrule(MONTHLY, count=3, interval=18, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1999, 3, 2, 9, 0), datetime(2000, 9, 2, 9, 0)]) def testMonthlyByMonth(self): self.assertEqual(list(rrule(MONTHLY, count=3, bymonth=(1, 3), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 2, 9, 0), datetime(1998, 3, 2, 9, 0), datetime(1999, 1, 2, 9, 0)]) def testMonthlyByMonthDay(self): self.assertEqual(list(rrule(MONTHLY, count=3, bymonthday=(1, 3), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 3, 9, 0), datetime(1997, 10, 1, 9, 0), datetime(1997, 10, 3, 9, 0)]) def testMonthlyByMonthAndMonthDay(self): self.assertEqual(list(rrule(MONTHLY, count=3, bymonth=(1, 3), bymonthday=(5, 7), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 5, 9, 0), datetime(1998, 1, 7, 9, 0), datetime(1998, 3, 5, 9, 0)]) def testMonthlyByWeekDay(self): self.assertEqual(list(rrule(MONTHLY, count=3, byweekday=(TU, TH), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 4, 9, 0), datetime(1997, 9, 9, 9, 0)]) # Third Monday of the month self.assertEqual(rrule(MONTHLY, byweekday=(MO(+3)), dtstart=datetime(1997, 9, 1)).between(datetime(1997, 9, 1), datetime(1997, 12, 1)), [datetime(1997, 9, 15, 0, 0), datetime(1997, 10, 20, 0, 0), datetime(1997, 11, 17, 0, 0)]) def testMonthlyByNWeekDay(self): self.assertEqual(list(rrule(MONTHLY, count=3, byweekday=(TU(1), TH(-1)), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 25, 9, 0), datetime(1997, 10, 7, 9, 0)]) def testMonthlyByNWeekDayLarge(self): self.assertEqual(list(rrule(MONTHLY, count=3, byweekday=(TU(3), TH(-3)), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 11, 9, 0), datetime(1997, 9, 16, 9, 0), datetime(1997, 10, 16, 9, 0)]) def testMonthlyByMonthAndWeekDay(self): self.assertEqual(list(rrule(MONTHLY, count=3, bymonth=(1, 3), byweekday=(TU, TH), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 1, 6, 9, 0), datetime(1998, 1, 8, 9, 0)]) def testMonthlyByMonthAndNWeekDay(self): self.assertEqual(list(rrule(MONTHLY, count=3, bymonth=(1, 3), byweekday=(TU(1), TH(-1)), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 6, 9, 0), datetime(1998, 1, 29, 9, 0), datetime(1998, 3, 3, 9, 0)]) def testMonthlyByMonthAndNWeekDayLarge(self): self.assertEqual(list(rrule(MONTHLY, count=3, bymonth=(1, 3), byweekday=(TU(3), TH(-3)), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 15, 9, 0), datetime(1998, 1, 20, 9, 0), datetime(1998, 3, 12, 9, 0)]) def testMonthlyByMonthDayAndWeekDay(self): self.assertEqual(list(rrule(MONTHLY, count=3, bymonthday=(1, 3), byweekday=(TU, TH), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 2, 3, 9, 0), datetime(1998, 3, 3, 9, 0)]) def testMonthlyByMonthAndMonthDayAndWeekDay(self): self.assertEqual(list(rrule(MONTHLY, count=3, bymonth=(1, 3), bymonthday=(1, 3), byweekday=(TU, TH), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 3, 3, 9, 0), datetime(2001, 3, 1, 9, 0)]) def testMonthlyByYearDay(self): self.assertEqual(list(rrule(MONTHLY, count=4, byyearday=(1, 100, 200, 365), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 31, 9, 0), datetime(1998, 1, 1, 9, 0), datetime(1998, 4, 10, 9, 0), datetime(1998, 7, 19, 9, 0)]) def testMonthlyByYearDayNeg(self): self.assertEqual(list(rrule(MONTHLY, count=4, byyearday=(-365, -266, -166, -1), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 31, 9, 0), datetime(1998, 1, 1, 9, 0), datetime(1998, 4, 10, 9, 0), datetime(1998, 7, 19, 9, 0)]) def testMonthlyByMonthAndYearDay(self): self.assertEqual(list(rrule(MONTHLY, count=4, bymonth=(4, 7), byyearday=(1, 100, 200, 365), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 4, 10, 9, 0), datetime(1998, 7, 19, 9, 0), datetime(1999, 4, 10, 9, 0), datetime(1999, 7, 19, 9, 0)]) def testMonthlyByMonthAndYearDayNeg(self): self.assertEqual(list(rrule(MONTHLY, count=4, bymonth=(4, 7), byyearday=(-365, -266, -166, -1), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 4, 10, 9, 0), datetime(1998, 7, 19, 9, 0), datetime(1999, 4, 10, 9, 0), datetime(1999, 7, 19, 9, 0)]) def testMonthlyByWeekNo(self): self.assertEqual(list(rrule(MONTHLY, count=3, byweekno=20, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 5, 11, 9, 0), datetime(1998, 5, 12, 9, 0), datetime(1998, 5, 13, 9, 0)]) def testMonthlyByWeekNoAndWeekDay(self): # That's a nice one. The first days of week number one # may be in the last year. self.assertEqual(list(rrule(MONTHLY, count=3, byweekno=1, byweekday=MO, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 29, 9, 0), datetime(1999, 1, 4, 9, 0), datetime(2000, 1, 3, 9, 0)]) def testMonthlyByWeekNoAndWeekDayLarge(self): # Another nice test. The last days of week number 52/53 # may be in the next year. self.assertEqual(list(rrule(MONTHLY, count=3, byweekno=52, byweekday=SU, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 28, 9, 0), datetime(1998, 12, 27, 9, 0), datetime(2000, 1, 2, 9, 0)]) def testMonthlyByWeekNoAndWeekDayLast(self): self.assertEqual(list(rrule(MONTHLY, count=3, byweekno=-1, byweekday=SU, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 28, 9, 0), datetime(1999, 1, 3, 9, 0), datetime(2000, 1, 2, 9, 0)]) def testMonthlyByWeekNoAndWeekDay53(self): self.assertEqual(list(rrule(MONTHLY, count=3, byweekno=53, byweekday=MO, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 12, 28, 9, 0), datetime(2004, 12, 27, 9, 0), datetime(2009, 12, 28, 9, 0)]) def testMonthlyByEaster(self): self.assertEqual(list(rrule(MONTHLY, count=3, byeaster=0, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 4, 12, 9, 0), datetime(1999, 4, 4, 9, 0), datetime(2000, 4, 23, 9, 0)]) def testMonthlyByEasterPos(self): self.assertEqual(list(rrule(MONTHLY, count=3, byeaster=1, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 4, 13, 9, 0), datetime(1999, 4, 5, 9, 0), datetime(2000, 4, 24, 9, 0)]) def testMonthlyByEasterNeg(self): self.assertEqual(list(rrule(MONTHLY, count=3, byeaster=-1, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 4, 11, 9, 0), datetime(1999, 4, 3, 9, 0), datetime(2000, 4, 22, 9, 0)]) def testMonthlyByHour(self): self.assertEqual(list(rrule(MONTHLY, count=3, byhour=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 18, 0), datetime(1997, 10, 2, 6, 0), datetime(1997, 10, 2, 18, 0)]) def testMonthlyByMinute(self): self.assertEqual(list(rrule(MONTHLY, count=3, byminute=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 6), datetime(1997, 9, 2, 9, 18), datetime(1997, 10, 2, 9, 6)]) def testMonthlyBySecond(self): self.assertEqual(list(rrule(MONTHLY, count=3, bysecond=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0, 6), datetime(1997, 9, 2, 9, 0, 18), datetime(1997, 10, 2, 9, 0, 6)]) def testMonthlyByHourAndMinute(self): self.assertEqual(list(rrule(MONTHLY, count=3, byhour=(6, 18), byminute=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 18, 6), datetime(1997, 9, 2, 18, 18), datetime(1997, 10, 2, 6, 6)]) def testMonthlyByHourAndSecond(self): self.assertEqual(list(rrule(MONTHLY, count=3, byhour=(6, 18), bysecond=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 18, 0, 6), datetime(1997, 9, 2, 18, 0, 18), datetime(1997, 10, 2, 6, 0, 6)]) def testMonthlyByMinuteAndSecond(self): self.assertEqual(list(rrule(MONTHLY, count=3, byminute=(6, 18), bysecond=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 6, 6), datetime(1997, 9, 2, 9, 6, 18), datetime(1997, 9, 2, 9, 18, 6)]) def testMonthlyByHourAndMinuteAndSecond(self): self.assertEqual(list(rrule(MONTHLY, count=3, byhour=(6, 18), byminute=(6, 18), bysecond=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 18, 6, 6), datetime(1997, 9, 2, 18, 6, 18), datetime(1997, 9, 2, 18, 18, 6)]) def testMonthlyBySetPos(self): self.assertEqual(list(rrule(MONTHLY, count=3, bymonthday=(13, 17), byhour=(6, 18), bysetpos=(3, -3), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 13, 18, 0), datetime(1997, 9, 17, 6, 0), datetime(1997, 10, 13, 18, 0)]) def testWeekly(self): self.assertEqual(list(rrule(WEEKLY, count=3, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 9, 9, 0), datetime(1997, 9, 16, 9, 0)]) def testWeeklyInterval(self): self.assertEqual(list(rrule(WEEKLY, count=3, interval=2, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 16, 9, 0), datetime(1997, 9, 30, 9, 0)]) def testWeeklyIntervalLarge(self): self.assertEqual(list(rrule(WEEKLY, count=3, interval=20, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1998, 1, 20, 9, 0), datetime(1998, 6, 9, 9, 0)]) def testWeeklyByMonth(self): self.assertEqual(list(rrule(WEEKLY, count=3, bymonth=(1, 3), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 6, 9, 0), datetime(1998, 1, 13, 9, 0), datetime(1998, 1, 20, 9, 0)]) def testWeeklyByMonthDay(self): self.assertEqual(list(rrule(WEEKLY, count=3, bymonthday=(1, 3), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 3, 9, 0), datetime(1997, 10, 1, 9, 0), datetime(1997, 10, 3, 9, 0)]) def testWeeklyByMonthAndMonthDay(self): self.assertEqual(list(rrule(WEEKLY, count=3, bymonth=(1, 3), bymonthday=(5, 7), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 5, 9, 0), datetime(1998, 1, 7, 9, 0), datetime(1998, 3, 5, 9, 0)]) def testWeeklyByWeekDay(self): self.assertEqual(list(rrule(WEEKLY, count=3, byweekday=(TU, TH), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 4, 9, 0), datetime(1997, 9, 9, 9, 0)]) def testWeeklyByNWeekDay(self): self.assertEqual(list(rrule(WEEKLY, count=3, byweekday=(TU(1), TH(-1)), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 4, 9, 0), datetime(1997, 9, 9, 9, 0)]) def testWeeklyByMonthAndWeekDay(self): # This test is interesting, because it crosses the year # boundary in a weekly period to find day '1' as a # valid recurrence. self.assertEqual(list(rrule(WEEKLY, count=3, bymonth=(1, 3), byweekday=(TU, TH), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 1, 6, 9, 0), datetime(1998, 1, 8, 9, 0)]) def testWeeklyByMonthAndNWeekDay(self): self.assertEqual(list(rrule(WEEKLY, count=3, bymonth=(1, 3), byweekday=(TU(1), TH(-1)), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 1, 6, 9, 0), datetime(1998, 1, 8, 9, 0)]) def testWeeklyByMonthDayAndWeekDay(self): self.assertEqual(list(rrule(WEEKLY, count=3, bymonthday=(1, 3), byweekday=(TU, TH), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 2, 3, 9, 0), datetime(1998, 3, 3, 9, 0)]) def testWeeklyByMonthAndMonthDayAndWeekDay(self): self.assertEqual(list(rrule(WEEKLY, count=3, bymonth=(1, 3), bymonthday=(1, 3), byweekday=(TU, TH), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 3, 3, 9, 0), datetime(2001, 3, 1, 9, 0)]) def testWeeklyByYearDay(self): self.assertEqual(list(rrule(WEEKLY, count=4, byyearday=(1, 100, 200, 365), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 31, 9, 0), datetime(1998, 1, 1, 9, 0), datetime(1998, 4, 10, 9, 0), datetime(1998, 7, 19, 9, 0)]) def testWeeklyByYearDayNeg(self): self.assertEqual(list(rrule(WEEKLY, count=4, byyearday=(-365, -266, -166, -1), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 31, 9, 0), datetime(1998, 1, 1, 9, 0), datetime(1998, 4, 10, 9, 0), datetime(1998, 7, 19, 9, 0)]) def testWeeklyByMonthAndYearDay(self): self.assertEqual(list(rrule(WEEKLY, count=4, bymonth=(1, 7), byyearday=(1, 100, 200, 365), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 7, 19, 9, 0), datetime(1999, 1, 1, 9, 0), datetime(1999, 7, 19, 9, 0)]) def testWeeklyByMonthAndYearDayNeg(self): self.assertEqual(list(rrule(WEEKLY, count=4, bymonth=(1, 7), byyearday=(-365, -266, -166, -1), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 7, 19, 9, 0), datetime(1999, 1, 1, 9, 0), datetime(1999, 7, 19, 9, 0)]) def testWeeklyByWeekNo(self): self.assertEqual(list(rrule(WEEKLY, count=3, byweekno=20, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 5, 11, 9, 0), datetime(1998, 5, 12, 9, 0), datetime(1998, 5, 13, 9, 0)]) def testWeeklyByWeekNoAndWeekDay(self): # That's a nice one. The first days of week number one # may be in the last year. self.assertEqual(list(rrule(WEEKLY, count=3, byweekno=1, byweekday=MO, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 29, 9, 0), datetime(1999, 1, 4, 9, 0), datetime(2000, 1, 3, 9, 0)]) def testWeeklyByWeekNoAndWeekDayLarge(self): # Another nice test. The last days of week number 52/53 # may be in the next year. self.assertEqual(list(rrule(WEEKLY, count=3, byweekno=52, byweekday=SU, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 28, 9, 0), datetime(1998, 12, 27, 9, 0), datetime(2000, 1, 2, 9, 0)]) def testWeeklyByWeekNoAndWeekDayLast(self): self.assertEqual(list(rrule(WEEKLY, count=3, byweekno=-1, byweekday=SU, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 28, 9, 0), datetime(1999, 1, 3, 9, 0), datetime(2000, 1, 2, 9, 0)]) def testWeeklyByWeekNoAndWeekDay53(self): self.assertEqual(list(rrule(WEEKLY, count=3, byweekno=53, byweekday=MO, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 12, 28, 9, 0), datetime(2004, 12, 27, 9, 0), datetime(2009, 12, 28, 9, 0)]) def testWeeklyByEaster(self): self.assertEqual(list(rrule(WEEKLY, count=3, byeaster=0, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 4, 12, 9, 0), datetime(1999, 4, 4, 9, 0), datetime(2000, 4, 23, 9, 0)]) def testWeeklyByEasterPos(self): self.assertEqual(list(rrule(WEEKLY, count=3, byeaster=1, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 4, 13, 9, 0), datetime(1999, 4, 5, 9, 0), datetime(2000, 4, 24, 9, 0)]) def testWeeklyByEasterNeg(self): self.assertEqual(list(rrule(WEEKLY, count=3, byeaster=-1, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 4, 11, 9, 0), datetime(1999, 4, 3, 9, 0), datetime(2000, 4, 22, 9, 0)]) def testWeeklyByHour(self): self.assertEqual(list(rrule(WEEKLY, count=3, byhour=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 18, 0), datetime(1997, 9, 9, 6, 0), datetime(1997, 9, 9, 18, 0)]) def testWeeklyByMinute(self): self.assertEqual(list(rrule(WEEKLY, count=3, byminute=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 6), datetime(1997, 9, 2, 9, 18), datetime(1997, 9, 9, 9, 6)]) def testWeeklyBySecond(self): self.assertEqual(list(rrule(WEEKLY, count=3, bysecond=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0, 6), datetime(1997, 9, 2, 9, 0, 18), datetime(1997, 9, 9, 9, 0, 6)]) def testWeeklyByHourAndMinute(self): self.assertEqual(list(rrule(WEEKLY, count=3, byhour=(6, 18), byminute=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 18, 6), datetime(1997, 9, 2, 18, 18), datetime(1997, 9, 9, 6, 6)]) def testWeeklyByHourAndSecond(self): self.assertEqual(list(rrule(WEEKLY, count=3, byhour=(6, 18), bysecond=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 18, 0, 6), datetime(1997, 9, 2, 18, 0, 18), datetime(1997, 9, 9, 6, 0, 6)]) def testWeeklyByMinuteAndSecond(self): self.assertEqual(list(rrule(WEEKLY, count=3, byminute=(6, 18), bysecond=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 6, 6), datetime(1997, 9, 2, 9, 6, 18), datetime(1997, 9, 2, 9, 18, 6)]) def testWeeklyByHourAndMinuteAndSecond(self): self.assertEqual(list(rrule(WEEKLY, count=3, byhour=(6, 18), byminute=(6, 18), bysecond=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 18, 6, 6), datetime(1997, 9, 2, 18, 6, 18), datetime(1997, 9, 2, 18, 18, 6)]) def testWeeklyBySetPos(self): self.assertEqual(list(rrule(WEEKLY, count=3, byweekday=(TU, TH), byhour=(6, 18), bysetpos=(3, -3), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 18, 0), datetime(1997, 9, 4, 6, 0), datetime(1997, 9, 9, 18, 0)]) def testDaily(self): self.assertEqual(list(rrule(DAILY, count=3, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 3, 9, 0), datetime(1997, 9, 4, 9, 0)]) def testDailyInterval(self): self.assertEqual(list(rrule(DAILY, count=3, interval=2, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 4, 9, 0), datetime(1997, 9, 6, 9, 0)]) def testDailyIntervalLarge(self): self.assertEqual(list(rrule(DAILY, count=3, interval=92, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 12, 3, 9, 0), datetime(1998, 3, 5, 9, 0)]) def testDailyByMonth(self): self.assertEqual(list(rrule(DAILY, count=3, bymonth=(1, 3), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 1, 2, 9, 0), datetime(1998, 1, 3, 9, 0)]) def testDailyByMonthDay(self): self.assertEqual(list(rrule(DAILY, count=3, bymonthday=(1, 3), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 3, 9, 0), datetime(1997, 10, 1, 9, 0), datetime(1997, 10, 3, 9, 0)]) def testDailyByMonthAndMonthDay(self): self.assertEqual(list(rrule(DAILY, count=3, bymonth=(1, 3), bymonthday=(5, 7), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 5, 9, 0), datetime(1998, 1, 7, 9, 0), datetime(1998, 3, 5, 9, 0)]) def testDailyByWeekDay(self): self.assertEqual(list(rrule(DAILY, count=3, byweekday=(TU, TH), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 4, 9, 0), datetime(1997, 9, 9, 9, 0)]) def testDailyByNWeekDay(self): self.assertEqual(list(rrule(DAILY, count=3, byweekday=(TU(1), TH(-1)), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 4, 9, 0), datetime(1997, 9, 9, 9, 0)]) def testDailyByMonthAndWeekDay(self): self.assertEqual(list(rrule(DAILY, count=3, bymonth=(1, 3), byweekday=(TU, TH), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 1, 6, 9, 0), datetime(1998, 1, 8, 9, 0)]) def testDailyByMonthAndNWeekDay(self): self.assertEqual(list(rrule(DAILY, count=3, bymonth=(1, 3), byweekday=(TU(1), TH(-1)), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 1, 6, 9, 0), datetime(1998, 1, 8, 9, 0)]) def testDailyByMonthDayAndWeekDay(self): self.assertEqual(list(rrule(DAILY, count=3, bymonthday=(1, 3), byweekday=(TU, TH), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 2, 3, 9, 0), datetime(1998, 3, 3, 9, 0)]) def testDailyByMonthAndMonthDayAndWeekDay(self): self.assertEqual(list(rrule(DAILY, count=3, bymonth=(1, 3), bymonthday=(1, 3), byweekday=(TU, TH), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 3, 3, 9, 0), datetime(2001, 3, 1, 9, 0)]) def testDailyByYearDay(self): self.assertEqual(list(rrule(DAILY, count=4, byyearday=(1, 100, 200, 365), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 31, 9, 0), datetime(1998, 1, 1, 9, 0), datetime(1998, 4, 10, 9, 0), datetime(1998, 7, 19, 9, 0)]) def testDailyByYearDayNeg(self): self.assertEqual(list(rrule(DAILY, count=4, byyearday=(-365, -266, -166, -1), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 31, 9, 0), datetime(1998, 1, 1, 9, 0), datetime(1998, 4, 10, 9, 0), datetime(1998, 7, 19, 9, 0)]) def testDailyByMonthAndYearDay(self): self.assertEqual(list(rrule(DAILY, count=4, bymonth=(1, 7), byyearday=(1, 100, 200, 365), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 7, 19, 9, 0), datetime(1999, 1, 1, 9, 0), datetime(1999, 7, 19, 9, 0)]) def testDailyByMonthAndYearDayNeg(self): self.assertEqual(list(rrule(DAILY, count=4, bymonth=(1, 7), byyearday=(-365, -266, -166, -1), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 7, 19, 9, 0), datetime(1999, 1, 1, 9, 0), datetime(1999, 7, 19, 9, 0)]) def testDailyByWeekNo(self): self.assertEqual(list(rrule(DAILY, count=3, byweekno=20, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 5, 11, 9, 0), datetime(1998, 5, 12, 9, 0), datetime(1998, 5, 13, 9, 0)]) def testDailyByWeekNoAndWeekDay(self): # That's a nice one. The first days of week number one # may be in the last year. self.assertEqual(list(rrule(DAILY, count=3, byweekno=1, byweekday=MO, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 29, 9, 0), datetime(1999, 1, 4, 9, 0), datetime(2000, 1, 3, 9, 0)]) def testDailyByWeekNoAndWeekDayLarge(self): # Another nice test. The last days of week number 52/53 # may be in the next year. self.assertEqual(list(rrule(DAILY, count=3, byweekno=52, byweekday=SU, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 28, 9, 0), datetime(1998, 12, 27, 9, 0), datetime(2000, 1, 2, 9, 0)]) def testDailyByWeekNoAndWeekDayLast(self): self.assertEqual(list(rrule(DAILY, count=3, byweekno=-1, byweekday=SU, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 28, 9, 0), datetime(1999, 1, 3, 9, 0), datetime(2000, 1, 2, 9, 0)]) def testDailyByWeekNoAndWeekDay53(self): self.assertEqual(list(rrule(DAILY, count=3, byweekno=53, byweekday=MO, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 12, 28, 9, 0), datetime(2004, 12, 27, 9, 0), datetime(2009, 12, 28, 9, 0)]) def testDailyByEaster(self): self.assertEqual(list(rrule(DAILY, count=3, byeaster=0, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 4, 12, 9, 0), datetime(1999, 4, 4, 9, 0), datetime(2000, 4, 23, 9, 0)]) def testDailyByEasterPos(self): self.assertEqual(list(rrule(DAILY, count=3, byeaster=1, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 4, 13, 9, 0), datetime(1999, 4, 5, 9, 0), datetime(2000, 4, 24, 9, 0)]) def testDailyByEasterNeg(self): self.assertEqual(list(rrule(DAILY, count=3, byeaster=-1, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 4, 11, 9, 0), datetime(1999, 4, 3, 9, 0), datetime(2000, 4, 22, 9, 0)]) def testDailyByHour(self): self.assertEqual(list(rrule(DAILY, count=3, byhour=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 18, 0), datetime(1997, 9, 3, 6, 0), datetime(1997, 9, 3, 18, 0)]) def testDailyByMinute(self): self.assertEqual(list(rrule(DAILY, count=3, byminute=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 6), datetime(1997, 9, 2, 9, 18), datetime(1997, 9, 3, 9, 6)]) def testDailyBySecond(self): self.assertEqual(list(rrule(DAILY, count=3, bysecond=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0, 6), datetime(1997, 9, 2, 9, 0, 18), datetime(1997, 9, 3, 9, 0, 6)]) def testDailyByHourAndMinute(self): self.assertEqual(list(rrule(DAILY, count=3, byhour=(6, 18), byminute=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 18, 6), datetime(1997, 9, 2, 18, 18), datetime(1997, 9, 3, 6, 6)]) def testDailyByHourAndSecond(self): self.assertEqual(list(rrule(DAILY, count=3, byhour=(6, 18), bysecond=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 18, 0, 6), datetime(1997, 9, 2, 18, 0, 18), datetime(1997, 9, 3, 6, 0, 6)]) def testDailyByMinuteAndSecond(self): self.assertEqual(list(rrule(DAILY, count=3, byminute=(6, 18), bysecond=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 6, 6), datetime(1997, 9, 2, 9, 6, 18), datetime(1997, 9, 2, 9, 18, 6)]) def testDailyByHourAndMinuteAndSecond(self): self.assertEqual(list(rrule(DAILY, count=3, byhour=(6, 18), byminute=(6, 18), bysecond=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 18, 6, 6), datetime(1997, 9, 2, 18, 6, 18), datetime(1997, 9, 2, 18, 18, 6)]) def testDailyBySetPos(self): self.assertEqual(list(rrule(DAILY, count=3, byhour=(6, 18), byminute=(15, 45), bysetpos=(3, -3), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 18, 15), datetime(1997, 9, 3, 6, 45), datetime(1997, 9, 3, 18, 15)]) def testHourly(self): self.assertEqual(list(rrule(HOURLY, count=3, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 2, 10, 0), datetime(1997, 9, 2, 11, 0)]) def testHourlyInterval(self): self.assertEqual(list(rrule(HOURLY, count=3, interval=2, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 2, 11, 0), datetime(1997, 9, 2, 13, 0)]) def testHourlyIntervalLarge(self): self.assertEqual(list(rrule(HOURLY, count=3, interval=769, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 10, 4, 10, 0), datetime(1997, 11, 5, 11, 0)]) def testHourlyByMonth(self): self.assertEqual(list(rrule(HOURLY, count=3, bymonth=(1, 3), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 1, 0, 0), datetime(1998, 1, 1, 1, 0), datetime(1998, 1, 1, 2, 0)]) def testHourlyByMonthDay(self): self.assertEqual(list(rrule(HOURLY, count=3, bymonthday=(1, 3), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 3, 0, 0), datetime(1997, 9, 3, 1, 0), datetime(1997, 9, 3, 2, 0)]) def testHourlyByMonthAndMonthDay(self): self.assertEqual(list(rrule(HOURLY, count=3, bymonth=(1, 3), bymonthday=(5, 7), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 5, 0, 0), datetime(1998, 1, 5, 1, 0), datetime(1998, 1, 5, 2, 0)]) def testHourlyByWeekDay(self): self.assertEqual(list(rrule(HOURLY, count=3, byweekday=(TU, TH), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 2, 10, 0), datetime(1997, 9, 2, 11, 0)]) def testHourlyByNWeekDay(self): self.assertEqual(list(rrule(HOURLY, count=3, byweekday=(TU(1), TH(-1)), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 2, 10, 0), datetime(1997, 9, 2, 11, 0)]) def testHourlyByMonthAndWeekDay(self): self.assertEqual(list(rrule(HOURLY, count=3, bymonth=(1, 3), byweekday=(TU, TH), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 1, 0, 0), datetime(1998, 1, 1, 1, 0), datetime(1998, 1, 1, 2, 0)]) def testHourlyByMonthAndNWeekDay(self): self.assertEqual(list(rrule(HOURLY, count=3, bymonth=(1, 3), byweekday=(TU(1), TH(-1)), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 1, 0, 0), datetime(1998, 1, 1, 1, 0), datetime(1998, 1, 1, 2, 0)]) def testHourlyByMonthDayAndWeekDay(self): self.assertEqual(list(rrule(HOURLY, count=3, bymonthday=(1, 3), byweekday=(TU, TH), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 1, 0, 0), datetime(1998, 1, 1, 1, 0), datetime(1998, 1, 1, 2, 0)]) def testHourlyByMonthAndMonthDayAndWeekDay(self): self.assertEqual(list(rrule(HOURLY, count=3, bymonth=(1, 3), bymonthday=(1, 3), byweekday=(TU, TH), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 1, 0, 0), datetime(1998, 1, 1, 1, 0), datetime(1998, 1, 1, 2, 0)]) def testHourlyByYearDay(self): self.assertEqual(list(rrule(HOURLY, count=4, byyearday=(1, 100, 200, 365), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 31, 0, 0), datetime(1997, 12, 31, 1, 0), datetime(1997, 12, 31, 2, 0), datetime(1997, 12, 31, 3, 0)]) def testHourlyByYearDayNeg(self): self.assertEqual(list(rrule(HOURLY, count=4, byyearday=(-365, -266, -166, -1), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 31, 0, 0), datetime(1997, 12, 31, 1, 0), datetime(1997, 12, 31, 2, 0), datetime(1997, 12, 31, 3, 0)]) def testHourlyByMonthAndYearDay(self): self.assertEqual(list(rrule(HOURLY, count=4, bymonth=(4, 7), byyearday=(1, 100, 200, 365), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 4, 10, 0, 0), datetime(1998, 4, 10, 1, 0), datetime(1998, 4, 10, 2, 0), datetime(1998, 4, 10, 3, 0)]) def testHourlyByMonthAndYearDayNeg(self): self.assertEqual(list(rrule(HOURLY, count=4, bymonth=(4, 7), byyearday=(-365, -266, -166, -1), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 4, 10, 0, 0), datetime(1998, 4, 10, 1, 0), datetime(1998, 4, 10, 2, 0), datetime(1998, 4, 10, 3, 0)]) def testHourlyByWeekNo(self): self.assertEqual(list(rrule(HOURLY, count=3, byweekno=20, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 5, 11, 0, 0), datetime(1998, 5, 11, 1, 0), datetime(1998, 5, 11, 2, 0)]) def testHourlyByWeekNoAndWeekDay(self): self.assertEqual(list(rrule(HOURLY, count=3, byweekno=1, byweekday=MO, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 29, 0, 0), datetime(1997, 12, 29, 1, 0), datetime(1997, 12, 29, 2, 0)]) def testHourlyByWeekNoAndWeekDayLarge(self): self.assertEqual(list(rrule(HOURLY, count=3, byweekno=52, byweekday=SU, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 28, 0, 0), datetime(1997, 12, 28, 1, 0), datetime(1997, 12, 28, 2, 0)]) def testHourlyByWeekNoAndWeekDayLast(self): self.assertEqual(list(rrule(HOURLY, count=3, byweekno=-1, byweekday=SU, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 28, 0, 0), datetime(1997, 12, 28, 1, 0), datetime(1997, 12, 28, 2, 0)]) def testHourlyByWeekNoAndWeekDay53(self): self.assertEqual(list(rrule(HOURLY, count=3, byweekno=53, byweekday=MO, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 12, 28, 0, 0), datetime(1998, 12, 28, 1, 0), datetime(1998, 12, 28, 2, 0)]) def testHourlyByEaster(self): self.assertEqual(list(rrule(HOURLY, count=3, byeaster=0, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 4, 12, 0, 0), datetime(1998, 4, 12, 1, 0), datetime(1998, 4, 12, 2, 0)]) def testHourlyByEasterPos(self): self.assertEqual(list(rrule(HOURLY, count=3, byeaster=1, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 4, 13, 0, 0), datetime(1998, 4, 13, 1, 0), datetime(1998, 4, 13, 2, 0)]) def testHourlyByEasterNeg(self): self.assertEqual(list(rrule(HOURLY, count=3, byeaster=-1, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 4, 11, 0, 0), datetime(1998, 4, 11, 1, 0), datetime(1998, 4, 11, 2, 0)]) def testHourlyByHour(self): self.assertEqual(list(rrule(HOURLY, count=3, byhour=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 18, 0), datetime(1997, 9, 3, 6, 0), datetime(1997, 9, 3, 18, 0)]) def testHourlyByMinute(self): self.assertEqual(list(rrule(HOURLY, count=3, byminute=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 6), datetime(1997, 9, 2, 9, 18), datetime(1997, 9, 2, 10, 6)]) def testHourlyBySecond(self): self.assertEqual(list(rrule(HOURLY, count=3, bysecond=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0, 6), datetime(1997, 9, 2, 9, 0, 18), datetime(1997, 9, 2, 10, 0, 6)]) def testHourlyByHourAndMinute(self): self.assertEqual(list(rrule(HOURLY, count=3, byhour=(6, 18), byminute=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 18, 6), datetime(1997, 9, 2, 18, 18), datetime(1997, 9, 3, 6, 6)]) def testHourlyByHourAndSecond(self): self.assertEqual(list(rrule(HOURLY, count=3, byhour=(6, 18), bysecond=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 18, 0, 6), datetime(1997, 9, 2, 18, 0, 18), datetime(1997, 9, 3, 6, 0, 6)]) def testHourlyByMinuteAndSecond(self): self.assertEqual(list(rrule(HOURLY, count=3, byminute=(6, 18), bysecond=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 6, 6), datetime(1997, 9, 2, 9, 6, 18), datetime(1997, 9, 2, 9, 18, 6)]) def testHourlyByHourAndMinuteAndSecond(self): self.assertEqual(list(rrule(HOURLY, count=3, byhour=(6, 18), byminute=(6, 18), bysecond=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 18, 6, 6), datetime(1997, 9, 2, 18, 6, 18), datetime(1997, 9, 2, 18, 18, 6)]) def testHourlyBySetPos(self): self.assertEqual(list(rrule(HOURLY, count=3, byminute=(15, 45), bysecond=(15, 45), bysetpos=(3, -3), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 15, 45), datetime(1997, 9, 2, 9, 45, 15), datetime(1997, 9, 2, 10, 15, 45)]) def testMinutely(self): self.assertEqual(list(rrule(MINUTELY, count=3, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 2, 9, 1), datetime(1997, 9, 2, 9, 2)]) def testMinutelyInterval(self): self.assertEqual(list(rrule(MINUTELY, count=3, interval=2, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 2, 9, 2), datetime(1997, 9, 2, 9, 4)]) def testMinutelyIntervalLarge(self): self.assertEqual(list(rrule(MINUTELY, count=3, interval=1501, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 3, 10, 1), datetime(1997, 9, 4, 11, 2)]) def testMinutelyByMonth(self): self.assertEqual(list(rrule(MINUTELY, count=3, bymonth=(1, 3), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 1, 0, 0), datetime(1998, 1, 1, 0, 1), datetime(1998, 1, 1, 0, 2)]) def testMinutelyByMonthDay(self): self.assertEqual(list(rrule(MINUTELY, count=3, bymonthday=(1, 3), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 3, 0, 0), datetime(1997, 9, 3, 0, 1), datetime(1997, 9, 3, 0, 2)]) def testMinutelyByMonthAndMonthDay(self): self.assertEqual(list(rrule(MINUTELY, count=3, bymonth=(1, 3), bymonthday=(5, 7), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 5, 0, 0), datetime(1998, 1, 5, 0, 1), datetime(1998, 1, 5, 0, 2)]) def testMinutelyByWeekDay(self): self.assertEqual(list(rrule(MINUTELY, count=3, byweekday=(TU, TH), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 2, 9, 1), datetime(1997, 9, 2, 9, 2)]) def testMinutelyByNWeekDay(self): self.assertEqual(list(rrule(MINUTELY, count=3, byweekday=(TU(1), TH(-1)), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 2, 9, 1), datetime(1997, 9, 2, 9, 2)]) def testMinutelyByMonthAndWeekDay(self): self.assertEqual(list(rrule(MINUTELY, count=3, bymonth=(1, 3), byweekday=(TU, TH), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 1, 0, 0), datetime(1998, 1, 1, 0, 1), datetime(1998, 1, 1, 0, 2)]) def testMinutelyByMonthAndNWeekDay(self): self.assertEqual(list(rrule(MINUTELY, count=3, bymonth=(1, 3), byweekday=(TU(1), TH(-1)), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 1, 0, 0), datetime(1998, 1, 1, 0, 1), datetime(1998, 1, 1, 0, 2)]) def testMinutelyByMonthDayAndWeekDay(self): self.assertEqual(list(rrule(MINUTELY, count=3, bymonthday=(1, 3), byweekday=(TU, TH), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 1, 0, 0), datetime(1998, 1, 1, 0, 1), datetime(1998, 1, 1, 0, 2)]) def testMinutelyByMonthAndMonthDayAndWeekDay(self): self.assertEqual(list(rrule(MINUTELY, count=3, bymonth=(1, 3), bymonthday=(1, 3), byweekday=(TU, TH), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 1, 0, 0), datetime(1998, 1, 1, 0, 1), datetime(1998, 1, 1, 0, 2)]) def testMinutelyByYearDay(self): self.assertEqual(list(rrule(MINUTELY, count=4, byyearday=(1, 100, 200, 365), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 31, 0, 0), datetime(1997, 12, 31, 0, 1), datetime(1997, 12, 31, 0, 2), datetime(1997, 12, 31, 0, 3)]) def testMinutelyByYearDayNeg(self): self.assertEqual(list(rrule(MINUTELY, count=4, byyearday=(-365, -266, -166, -1), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 31, 0, 0), datetime(1997, 12, 31, 0, 1), datetime(1997, 12, 31, 0, 2), datetime(1997, 12, 31, 0, 3)]) def testMinutelyByMonthAndYearDay(self): self.assertEqual(list(rrule(MINUTELY, count=4, bymonth=(4, 7), byyearday=(1, 100, 200, 365), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 4, 10, 0, 0), datetime(1998, 4, 10, 0, 1), datetime(1998, 4, 10, 0, 2), datetime(1998, 4, 10, 0, 3)]) def testMinutelyByMonthAndYearDayNeg(self): self.assertEqual(list(rrule(MINUTELY, count=4, bymonth=(4, 7), byyearday=(-365, -266, -166, -1), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 4, 10, 0, 0), datetime(1998, 4, 10, 0, 1), datetime(1998, 4, 10, 0, 2), datetime(1998, 4, 10, 0, 3)]) def testMinutelyByWeekNo(self): self.assertEqual(list(rrule(MINUTELY, count=3, byweekno=20, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 5, 11, 0, 0), datetime(1998, 5, 11, 0, 1), datetime(1998, 5, 11, 0, 2)]) def testMinutelyByWeekNoAndWeekDay(self): self.assertEqual(list(rrule(MINUTELY, count=3, byweekno=1, byweekday=MO, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 29, 0, 0), datetime(1997, 12, 29, 0, 1), datetime(1997, 12, 29, 0, 2)]) def testMinutelyByWeekNoAndWeekDayLarge(self): self.assertEqual(list(rrule(MINUTELY, count=3, byweekno=52, byweekday=SU, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 28, 0, 0), datetime(1997, 12, 28, 0, 1), datetime(1997, 12, 28, 0, 2)]) def testMinutelyByWeekNoAndWeekDayLast(self): self.assertEqual(list(rrule(MINUTELY, count=3, byweekno=-1, byweekday=SU, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 28, 0, 0), datetime(1997, 12, 28, 0, 1), datetime(1997, 12, 28, 0, 2)]) def testMinutelyByWeekNoAndWeekDay53(self): self.assertEqual(list(rrule(MINUTELY, count=3, byweekno=53, byweekday=MO, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 12, 28, 0, 0), datetime(1998, 12, 28, 0, 1), datetime(1998, 12, 28, 0, 2)]) def testMinutelyByEaster(self): self.assertEqual(list(rrule(MINUTELY, count=3, byeaster=0, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 4, 12, 0, 0), datetime(1998, 4, 12, 0, 1), datetime(1998, 4, 12, 0, 2)]) def testMinutelyByEasterPos(self): self.assertEqual(list(rrule(MINUTELY, count=3, byeaster=1, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 4, 13, 0, 0), datetime(1998, 4, 13, 0, 1), datetime(1998, 4, 13, 0, 2)]) def testMinutelyByEasterNeg(self): self.assertEqual(list(rrule(MINUTELY, count=3, byeaster=-1, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 4, 11, 0, 0), datetime(1998, 4, 11, 0, 1), datetime(1998, 4, 11, 0, 2)]) def testMinutelyByHour(self): self.assertEqual(list(rrule(MINUTELY, count=3, byhour=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 18, 0), datetime(1997, 9, 2, 18, 1), datetime(1997, 9, 2, 18, 2)]) def testMinutelyByMinute(self): self.assertEqual(list(rrule(MINUTELY, count=3, byminute=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 6), datetime(1997, 9, 2, 9, 18), datetime(1997, 9, 2, 10, 6)]) def testMinutelyBySecond(self): self.assertEqual(list(rrule(MINUTELY, count=3, bysecond=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0, 6), datetime(1997, 9, 2, 9, 0, 18), datetime(1997, 9, 2, 9, 1, 6)]) def testMinutelyByHourAndMinute(self): self.assertEqual(list(rrule(MINUTELY, count=3, byhour=(6, 18), byminute=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 18, 6), datetime(1997, 9, 2, 18, 18), datetime(1997, 9, 3, 6, 6)]) def testMinutelyByHourAndSecond(self): self.assertEqual(list(rrule(MINUTELY, count=3, byhour=(6, 18), bysecond=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 18, 0, 6), datetime(1997, 9, 2, 18, 0, 18), datetime(1997, 9, 2, 18, 1, 6)]) def testMinutelyByMinuteAndSecond(self): self.assertEqual(list(rrule(MINUTELY, count=3, byminute=(6, 18), bysecond=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 6, 6), datetime(1997, 9, 2, 9, 6, 18), datetime(1997, 9, 2, 9, 18, 6)]) def testMinutelyByHourAndMinuteAndSecond(self): self.assertEqual(list(rrule(MINUTELY, count=3, byhour=(6, 18), byminute=(6, 18), bysecond=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 18, 6, 6), datetime(1997, 9, 2, 18, 6, 18), datetime(1997, 9, 2, 18, 18, 6)]) def testMinutelyBySetPos(self): self.assertEqual(list(rrule(MINUTELY, count=3, bysecond=(15, 30, 45), bysetpos=(3, -3), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0, 15), datetime(1997, 9, 2, 9, 0, 45), datetime(1997, 9, 2, 9, 1, 15)]) def testSecondly(self): self.assertEqual(list(rrule(SECONDLY, count=3, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0, 0), datetime(1997, 9, 2, 9, 0, 1), datetime(1997, 9, 2, 9, 0, 2)]) def testSecondlyInterval(self): self.assertEqual(list(rrule(SECONDLY, count=3, interval=2, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0, 0), datetime(1997, 9, 2, 9, 0, 2), datetime(1997, 9, 2, 9, 0, 4)]) def testSecondlyIntervalLarge(self): self.assertEqual(list(rrule(SECONDLY, count=3, interval=90061, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0, 0), datetime(1997, 9, 3, 10, 1, 1), datetime(1997, 9, 4, 11, 2, 2)]) def testSecondlyByMonth(self): self.assertEqual(list(rrule(SECONDLY, count=3, bymonth=(1, 3), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 1, 0, 0, 0), datetime(1998, 1, 1, 0, 0, 1), datetime(1998, 1, 1, 0, 0, 2)]) def testSecondlyByMonthDay(self): self.assertEqual(list(rrule(SECONDLY, count=3, bymonthday=(1, 3), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 3, 0, 0, 0), datetime(1997, 9, 3, 0, 0, 1), datetime(1997, 9, 3, 0, 0, 2)]) def testSecondlyByMonthAndMonthDay(self): self.assertEqual(list(rrule(SECONDLY, count=3, bymonth=(1, 3), bymonthday=(5, 7), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 5, 0, 0, 0), datetime(1998, 1, 5, 0, 0, 1), datetime(1998, 1, 5, 0, 0, 2)]) def testSecondlyByWeekDay(self): self.assertEqual(list(rrule(SECONDLY, count=3, byweekday=(TU, TH), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0, 0), datetime(1997, 9, 2, 9, 0, 1), datetime(1997, 9, 2, 9, 0, 2)]) def testSecondlyByNWeekDay(self): self.assertEqual(list(rrule(SECONDLY, count=3, byweekday=(TU(1), TH(-1)), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0, 0), datetime(1997, 9, 2, 9, 0, 1), datetime(1997, 9, 2, 9, 0, 2)]) def testSecondlyByMonthAndWeekDay(self): self.assertEqual(list(rrule(SECONDLY, count=3, bymonth=(1, 3), byweekday=(TU, TH), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 1, 0, 0, 0), datetime(1998, 1, 1, 0, 0, 1), datetime(1998, 1, 1, 0, 0, 2)]) def testSecondlyByMonthAndNWeekDay(self): self.assertEqual(list(rrule(SECONDLY, count=3, bymonth=(1, 3), byweekday=(TU(1), TH(-1)), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 1, 0, 0, 0), datetime(1998, 1, 1, 0, 0, 1), datetime(1998, 1, 1, 0, 0, 2)]) def testSecondlyByMonthDayAndWeekDay(self): self.assertEqual(list(rrule(SECONDLY, count=3, bymonthday=(1, 3), byweekday=(TU, TH), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 1, 0, 0, 0), datetime(1998, 1, 1, 0, 0, 1), datetime(1998, 1, 1, 0, 0, 2)]) def testSecondlyByMonthAndMonthDayAndWeekDay(self): self.assertEqual(list(rrule(SECONDLY, count=3, bymonth=(1, 3), bymonthday=(1, 3), byweekday=(TU, TH), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 1, 0, 0, 0), datetime(1998, 1, 1, 0, 0, 1), datetime(1998, 1, 1, 0, 0, 2)]) def testSecondlyByYearDay(self): self.assertEqual(list(rrule(SECONDLY, count=4, byyearday=(1, 100, 200, 365), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 31, 0, 0, 0), datetime(1997, 12, 31, 0, 0, 1), datetime(1997, 12, 31, 0, 0, 2), datetime(1997, 12, 31, 0, 0, 3)]) def testSecondlyByYearDayNeg(self): self.assertEqual(list(rrule(SECONDLY, count=4, byyearday=(-365, -266, -166, -1), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 31, 0, 0, 0), datetime(1997, 12, 31, 0, 0, 1), datetime(1997, 12, 31, 0, 0, 2), datetime(1997, 12, 31, 0, 0, 3)]) def testSecondlyByMonthAndYearDay(self): self.assertEqual(list(rrule(SECONDLY, count=4, bymonth=(4, 7), byyearday=(1, 100, 200, 365), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 4, 10, 0, 0, 0), datetime(1998, 4, 10, 0, 0, 1), datetime(1998, 4, 10, 0, 0, 2), datetime(1998, 4, 10, 0, 0, 3)]) def testSecondlyByMonthAndYearDayNeg(self): self.assertEqual(list(rrule(SECONDLY, count=4, bymonth=(4, 7), byyearday=(-365, -266, -166, -1), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 4, 10, 0, 0, 0), datetime(1998, 4, 10, 0, 0, 1), datetime(1998, 4, 10, 0, 0, 2), datetime(1998, 4, 10, 0, 0, 3)]) def testSecondlyByWeekNo(self): self.assertEqual(list(rrule(SECONDLY, count=3, byweekno=20, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 5, 11, 0, 0, 0), datetime(1998, 5, 11, 0, 0, 1), datetime(1998, 5, 11, 0, 0, 2)]) def testSecondlyByWeekNoAndWeekDay(self): self.assertEqual(list(rrule(SECONDLY, count=3, byweekno=1, byweekday=MO, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 29, 0, 0, 0), datetime(1997, 12, 29, 0, 0, 1), datetime(1997, 12, 29, 0, 0, 2)]) def testSecondlyByWeekNoAndWeekDayLarge(self): self.assertEqual(list(rrule(SECONDLY, count=3, byweekno=52, byweekday=SU, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 28, 0, 0, 0), datetime(1997, 12, 28, 0, 0, 1), datetime(1997, 12, 28, 0, 0, 2)]) def testSecondlyByWeekNoAndWeekDayLast(self): self.assertEqual(list(rrule(SECONDLY, count=3, byweekno=-1, byweekday=SU, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 12, 28, 0, 0, 0), datetime(1997, 12, 28, 0, 0, 1), datetime(1997, 12, 28, 0, 0, 2)]) def testSecondlyByWeekNoAndWeekDay53(self): self.assertEqual(list(rrule(SECONDLY, count=3, byweekno=53, byweekday=MO, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 12, 28, 0, 0, 0), datetime(1998, 12, 28, 0, 0, 1), datetime(1998, 12, 28, 0, 0, 2)]) def testSecondlyByEaster(self): self.assertEqual(list(rrule(SECONDLY, count=3, byeaster=0, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 4, 12, 0, 0, 0), datetime(1998, 4, 12, 0, 0, 1), datetime(1998, 4, 12, 0, 0, 2)]) def testSecondlyByEasterPos(self): self.assertEqual(list(rrule(SECONDLY, count=3, byeaster=1, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 4, 13, 0, 0, 0), datetime(1998, 4, 13, 0, 0, 1), datetime(1998, 4, 13, 0, 0, 2)]) def testSecondlyByEasterNeg(self): self.assertEqual(list(rrule(SECONDLY, count=3, byeaster=-1, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 4, 11, 0, 0, 0), datetime(1998, 4, 11, 0, 0, 1), datetime(1998, 4, 11, 0, 0, 2)]) def testSecondlyByHour(self): self.assertEqual(list(rrule(SECONDLY, count=3, byhour=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 18, 0, 0), datetime(1997, 9, 2, 18, 0, 1), datetime(1997, 9, 2, 18, 0, 2)]) def testSecondlyByMinute(self): self.assertEqual(list(rrule(SECONDLY, count=3, byminute=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 6, 0), datetime(1997, 9, 2, 9, 6, 1), datetime(1997, 9, 2, 9, 6, 2)]) def testSecondlyBySecond(self): self.assertEqual(list(rrule(SECONDLY, count=3, bysecond=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0, 6), datetime(1997, 9, 2, 9, 0, 18), datetime(1997, 9, 2, 9, 1, 6)]) def testSecondlyByHourAndMinute(self): self.assertEqual(list(rrule(SECONDLY, count=3, byhour=(6, 18), byminute=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 18, 6, 0), datetime(1997, 9, 2, 18, 6, 1), datetime(1997, 9, 2, 18, 6, 2)]) def testSecondlyByHourAndSecond(self): self.assertEqual(list(rrule(SECONDLY, count=3, byhour=(6, 18), bysecond=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 18, 0, 6), datetime(1997, 9, 2, 18, 0, 18), datetime(1997, 9, 2, 18, 1, 6)]) def testSecondlyByMinuteAndSecond(self): self.assertEqual(list(rrule(SECONDLY, count=3, byminute=(6, 18), bysecond=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 6, 6), datetime(1997, 9, 2, 9, 6, 18), datetime(1997, 9, 2, 9, 18, 6)]) def testSecondlyByHourAndMinuteAndSecond(self): self.assertEqual(list(rrule(SECONDLY, count=3, byhour=(6, 18), byminute=(6, 18), bysecond=(6, 18), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 18, 6, 6), datetime(1997, 9, 2, 18, 6, 18), datetime(1997, 9, 2, 18, 18, 6)]) def testSecondlyByHourAndMinuteAndSecondBug(self): # This explores a bug found by Mathieu Bridon. self.assertEqual(list(rrule(SECONDLY, count=3, bysecond=(0,), byminute=(1,), dtstart=datetime(2010, 3, 22, 12, 1))), [datetime(2010, 3, 22, 12, 1), datetime(2010, 3, 22, 13, 1), datetime(2010, 3, 22, 14, 1)]) def testLongIntegers(self): if not PY3: # There is no longs in python3 self.assertEqual(list(rrule(MINUTELY, count=long(2), interval=long(2), bymonth=long(2), byweekday=long(3), byhour=long(6), byminute=long(6), bysecond=long(6), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 2, 5, 6, 6, 6), datetime(1998, 2, 12, 6, 6, 6)]) self.assertEqual(list(rrule(YEARLY, count=long(2), bymonthday=long(5), byweekno=long(2), dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1998, 1, 5, 9, 0), datetime(2004, 1, 5, 9, 0)]) def testHourlyBadRRule(self): """ When `byhour` is specified with `freq=HOURLY`, there are certain combinations of `dtstart` and `byhour` which result in an rrule with no valid values. See https://github.com/dateutil/dateutil/issues/4 """ self.assertRaises(ValueError, rrule, HOURLY, **dict(interval=4, byhour=(7, 11, 15, 19), dtstart=datetime(1997, 9, 2, 9, 0))) def testMinutelyBadRRule(self): """ See :func:`testHourlyBadRRule` for details. """ self.assertRaises(ValueError, rrule, MINUTELY, **dict(interval=12, byminute=(10, 11, 25, 39, 50), dtstart=datetime(1997, 9, 2, 9, 0))) def testSecondlyBadRRule(self): """ See :func:`testHourlyBadRRule` for details. """ self.assertRaises(ValueError, rrule, SECONDLY, **dict(interval=10, bysecond=(2, 15, 37, 42, 59), dtstart=datetime(1997, 9, 2, 9, 0))) def testMinutelyBadComboRRule(self): """ Certain values of :param:`interval` in :class:`rrule`, when combined with certain values of :param:`byhour` create rules which apply to no valid dates. The library should detect this case in the iterator and raise a :exception:`ValueError`. """ # In Python 2.7 you can use a context manager for this. def make_bad_rrule(): list(rrule(MINUTELY, interval=120, byhour=(10, 12, 14, 16), count=2, dtstart=datetime(1997, 9, 2, 9, 0))) self.assertRaises(ValueError, make_bad_rrule) def testSecondlyBadComboRRule(self): """ See :func:`testMinutelyBadComboRRule' for details. """ # In Python 2.7 you can use a context manager for this. def make_bad_minute_rrule(): list(rrule(SECONDLY, interval=360, byminute=(10, 28, 49), count=4, dtstart=datetime(1997, 9, 2, 9, 0))) def make_bad_hour_rrule(): list(rrule(SECONDLY, interval=43200, byhour=(2, 10, 18, 23), count=4, dtstart=datetime(1997, 9, 2, 9, 0))) self.assertRaises(ValueError, make_bad_minute_rrule) self.assertRaises(ValueError, make_bad_hour_rrule) def testUntilNotMatching(self): self.assertEqual(list(rrule(DAILY, count=3, dtstart=datetime(1997, 9, 2, 9, 0), until=datetime(1997, 9, 5, 8, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 3, 9, 0), datetime(1997, 9, 4, 9, 0)]) def testUntilMatching(self): self.assertEqual(list(rrule(DAILY, count=3, dtstart=datetime(1997, 9, 2, 9, 0), until=datetime(1997, 9, 4, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 3, 9, 0), datetime(1997, 9, 4, 9, 0)]) def testUntilSingle(self): self.assertEqual(list(rrule(DAILY, count=3, dtstart=datetime(1997, 9, 2, 9, 0), until=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0)]) def testUntilEmpty(self): self.assertEqual(list(rrule(DAILY, count=3, dtstart=datetime(1997, 9, 2, 9, 0), until=datetime(1997, 9, 1, 9, 0))), []) def testUntilWithDate(self): self.assertEqual(list(rrule(DAILY, count=3, dtstart=datetime(1997, 9, 2, 9, 0), until=date(1997, 9, 5))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 3, 9, 0), datetime(1997, 9, 4, 9, 0)]) def testWkStIntervalMO(self): self.assertEqual(list(rrule(WEEKLY, count=3, interval=2, byweekday=(TU, SU), wkst=MO, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 7, 9, 0), datetime(1997, 9, 16, 9, 0)]) def testWkStIntervalSU(self): self.assertEqual(list(rrule(WEEKLY, count=3, interval=2, byweekday=(TU, SU), wkst=SU, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 14, 9, 0), datetime(1997, 9, 16, 9, 0)]) def testDTStartIsDate(self): self.assertEqual(list(rrule(DAILY, count=3, dtstart=date(1997, 9, 2))), [datetime(1997, 9, 2, 0, 0), datetime(1997, 9, 3, 0, 0), datetime(1997, 9, 4, 0, 0)]) def testDTStartWithMicroseconds(self): self.assertEqual(list(rrule(DAILY, count=3, dtstart=datetime(1997, 9, 2, 9, 0, 0, 500000))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 3, 9, 0), datetime(1997, 9, 4, 9, 0)]) def testMaxYear(self): self.assertEqual(list(rrule(YEARLY, count=3, bymonth=2, bymonthday=31, dtstart=datetime(9997, 9, 2, 9, 0, 0))), []) def testGetItem(self): self.assertEqual(rrule(DAILY, count=3, dtstart=datetime(1997, 9, 2, 9, 0))[0], datetime(1997, 9, 2, 9, 0)) def testGetItemNeg(self): self.assertEqual(rrule(DAILY, count=3, dtstart=datetime(1997, 9, 2, 9, 0))[-1], datetime(1997, 9, 4, 9, 0)) def testGetItemSlice(self): self.assertEqual(rrule(DAILY, # count=3, dtstart=datetime(1997, 9, 2, 9, 0))[1:2], [datetime(1997, 9, 3, 9, 0)]) def testGetItemSliceEmpty(self): self.assertEqual(rrule(DAILY, count=3, dtstart=datetime(1997, 9, 2, 9, 0))[:], [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 3, 9, 0), datetime(1997, 9, 4, 9, 0)]) def testGetItemSliceStep(self): self.assertEqual(rrule(DAILY, count=3, dtstart=datetime(1997, 9, 2, 9, 0))[::-2], [datetime(1997, 9, 4, 9, 0), datetime(1997, 9, 2, 9, 0)]) def testCount(self): self.assertEqual(rrule(DAILY, count=3, dtstart=datetime(1997, 9, 2, 9, 0)).count(), 3) def testContains(self): rr = rrule(DAILY, count=3, dtstart=datetime(1997, 9, 2, 9, 0)) self.assertEqual(datetime(1997, 9, 3, 9, 0) in rr, True) def testContainsNot(self): rr = rrule(DAILY, count=3, dtstart=datetime(1997, 9, 2, 9, 0)) self.assertEqual(datetime(1997, 9, 3, 9, 0) not in rr, False) def testBefore(self): self.assertEqual(rrule(DAILY, # count=5 dtstart=datetime(1997, 9, 2, 9, 0)).before(datetime(1997, 9, 5, 9, 0)), datetime(1997, 9, 4, 9, 0)) def testBeforeInc(self): self.assertEqual(rrule(DAILY, #count=5, dtstart=datetime(1997, 9, 2, 9, 0)) .before(datetime(1997, 9, 5, 9, 0), inc=True), datetime(1997, 9, 5, 9, 0)) def testAfter(self): self.assertEqual(rrule(DAILY, #count=5, dtstart=datetime(1997, 9, 2, 9, 0)) .after(datetime(1997, 9, 4, 9, 0)), datetime(1997, 9, 5, 9, 0)) def testAfterInc(self): self.assertEqual(rrule(DAILY, #count=5, dtstart=datetime(1997, 9, 2, 9, 0)) .after(datetime(1997, 9, 4, 9, 0), inc=True), datetime(1997, 9, 4, 9, 0)) def testBetween(self): self.assertEqual(rrule(DAILY, #count=5, dtstart=datetime(1997, 9, 2, 9, 0)) .between(datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 6, 9, 0)), [datetime(1997, 9, 3, 9, 0), datetime(1997, 9, 4, 9, 0), datetime(1997, 9, 5, 9, 0)]) def testBetweenInc(self): self.assertEqual(rrule(DAILY, #count=5, dtstart=datetime(1997, 9, 2, 9, 0)) .between(datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 6, 9, 0), inc=True), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 3, 9, 0), datetime(1997, 9, 4, 9, 0), datetime(1997, 9, 5, 9, 0), datetime(1997, 9, 6, 9, 0)]) def testCachePre(self): rr = rrule(DAILY, count=15, cache=True, dtstart=datetime(1997, 9, 2, 9, 0)) self.assertEqual(list(rr), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 3, 9, 0), datetime(1997, 9, 4, 9, 0), datetime(1997, 9, 5, 9, 0), datetime(1997, 9, 6, 9, 0), datetime(1997, 9, 7, 9, 0), datetime(1997, 9, 8, 9, 0), datetime(1997, 9, 9, 9, 0), datetime(1997, 9, 10, 9, 0), datetime(1997, 9, 11, 9, 0), datetime(1997, 9, 12, 9, 0), datetime(1997, 9, 13, 9, 0), datetime(1997, 9, 14, 9, 0), datetime(1997, 9, 15, 9, 0), datetime(1997, 9, 16, 9, 0)]) def testCachePost(self): rr = rrule(DAILY, count=15, cache=True, dtstart=datetime(1997, 9, 2, 9, 0)) for x in rr: pass self.assertEqual(list(rr), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 3, 9, 0), datetime(1997, 9, 4, 9, 0), datetime(1997, 9, 5, 9, 0), datetime(1997, 9, 6, 9, 0), datetime(1997, 9, 7, 9, 0), datetime(1997, 9, 8, 9, 0), datetime(1997, 9, 9, 9, 0), datetime(1997, 9, 10, 9, 0), datetime(1997, 9, 11, 9, 0), datetime(1997, 9, 12, 9, 0), datetime(1997, 9, 13, 9, 0), datetime(1997, 9, 14, 9, 0), datetime(1997, 9, 15, 9, 0), datetime(1997, 9, 16, 9, 0)]) def testCachePostInternal(self): rr = rrule(DAILY, count=15, cache=True, dtstart=datetime(1997, 9, 2, 9, 0)) for x in rr: pass self.assertEqual(rr._cache, [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 3, 9, 0), datetime(1997, 9, 4, 9, 0), datetime(1997, 9, 5, 9, 0), datetime(1997, 9, 6, 9, 0), datetime(1997, 9, 7, 9, 0), datetime(1997, 9, 8, 9, 0), datetime(1997, 9, 9, 9, 0), datetime(1997, 9, 10, 9, 0), datetime(1997, 9, 11, 9, 0), datetime(1997, 9, 12, 9, 0), datetime(1997, 9, 13, 9, 0), datetime(1997, 9, 14, 9, 0), datetime(1997, 9, 15, 9, 0), datetime(1997, 9, 16, 9, 0)]) def testCachePreContains(self): rr = rrule(DAILY, count=3, cache=True, dtstart=datetime(1997, 9, 2, 9, 0)) self.assertEqual(datetime(1997, 9, 3, 9, 0) in rr, True) def testCachePostContains(self): rr = rrule(DAILY, count=3, cache=True, dtstart=datetime(1997, 9, 2, 9, 0)) for x in rr: pass self.assertEqual(datetime(1997, 9, 3, 9, 0) in rr, True) def testSet(self): set = rruleset() set.rrule(rrule(YEARLY, count=2, byweekday=TU, dtstart=datetime(1997, 9, 2, 9, 0))) set.rrule(rrule(YEARLY, count=1, byweekday=TH, dtstart=datetime(1997, 9, 2, 9, 0))) self.assertEqual(list(set), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 4, 9, 0), datetime(1997, 9, 9, 9, 0)]) def testSetDate(self): set = rruleset() set.rrule(rrule(YEARLY, count=1, byweekday=TU, dtstart=datetime(1997, 9, 2, 9, 0))) set.rdate(datetime(1997, 9, 4, 9)) set.rdate(datetime(1997, 9, 9, 9)) self.assertEqual(list(set), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 4, 9, 0), datetime(1997, 9, 9, 9, 0)]) def testSetExRule(self): set = rruleset() set.rrule(rrule(YEARLY, count=6, byweekday=(TU, TH), dtstart=datetime(1997, 9, 2, 9, 0))) set.exrule(rrule(YEARLY, count=3, byweekday=TH, dtstart=datetime(1997, 9, 2, 9, 0))) self.assertEqual(list(set), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 9, 9, 0), datetime(1997, 9, 16, 9, 0)]) def testSetExDate(self): set = rruleset() set.rrule(rrule(YEARLY, count=6, byweekday=(TU, TH), dtstart=datetime(1997, 9, 2, 9, 0))) set.exdate(datetime(1997, 9, 4, 9)) set.exdate(datetime(1997, 9, 11, 9)) set.exdate(datetime(1997, 9, 18, 9)) self.assertEqual(list(set), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 9, 9, 0), datetime(1997, 9, 16, 9, 0)]) def testSetExDateRevOrder(self): set = rruleset() set.rrule(rrule(MONTHLY, count=5, bymonthday=10, dtstart=datetime(2004, 1, 1, 9, 0))) set.exdate(datetime(2004, 4, 10, 9, 0)) set.exdate(datetime(2004, 2, 10, 9, 0)) self.assertEqual(list(set), [datetime(2004, 1, 10, 9, 0), datetime(2004, 3, 10, 9, 0), datetime(2004, 5, 10, 9, 0)]) def testSetDateAndExDate(self): set = rruleset() set.rdate(datetime(1997, 9, 2, 9)) set.rdate(datetime(1997, 9, 4, 9)) set.rdate(datetime(1997, 9, 9, 9)) set.rdate(datetime(1997, 9, 11, 9)) set.rdate(datetime(1997, 9, 16, 9)) set.rdate(datetime(1997, 9, 18, 9)) set.exdate(datetime(1997, 9, 4, 9)) set.exdate(datetime(1997, 9, 11, 9)) set.exdate(datetime(1997, 9, 18, 9)) self.assertEqual(list(set), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 9, 9, 0), datetime(1997, 9, 16, 9, 0)]) def testSetDateAndExRule(self): set = rruleset() set.rdate(datetime(1997, 9, 2, 9)) set.rdate(datetime(1997, 9, 4, 9)) set.rdate(datetime(1997, 9, 9, 9)) set.rdate(datetime(1997, 9, 11, 9)) set.rdate(datetime(1997, 9, 16, 9)) set.rdate(datetime(1997, 9, 18, 9)) set.exrule(rrule(YEARLY, count=3, byweekday=TH, dtstart=datetime(1997, 9, 2, 9, 0))) self.assertEqual(list(set), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 9, 9, 0), datetime(1997, 9, 16, 9, 0)]) def testSetCount(self): set = rruleset() set.rrule(rrule(YEARLY, count=6, byweekday=(TU, TH), dtstart=datetime(1997, 9, 2, 9, 0))) set.exrule(rrule(YEARLY, count=3, byweekday=TH, dtstart=datetime(1997, 9, 2, 9, 0))) self.assertEqual(set.count(), 3) def testSetCachePre(self): set = rruleset() set.rrule(rrule(YEARLY, count=2, byweekday=TU, dtstart=datetime(1997, 9, 2, 9, 0))) set.rrule(rrule(YEARLY, count=1, byweekday=TH, dtstart=datetime(1997, 9, 2, 9, 0))) self.assertEqual(list(set), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 4, 9, 0), datetime(1997, 9, 9, 9, 0)]) def testSetCachePost(self): set = rruleset(cache=True) set.rrule(rrule(YEARLY, count=2, byweekday=TU, dtstart=datetime(1997, 9, 2, 9, 0))) set.rrule(rrule(YEARLY, count=1, byweekday=TH, dtstart=datetime(1997, 9, 2, 9, 0))) for x in set: pass self.assertEqual(list(set), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 4, 9, 0), datetime(1997, 9, 9, 9, 0)]) def testSetCachePostInternal(self): set = rruleset(cache=True) set.rrule(rrule(YEARLY, count=2, byweekday=TU, dtstart=datetime(1997, 9, 2, 9, 0))) set.rrule(rrule(YEARLY, count=1, byweekday=TH, dtstart=datetime(1997, 9, 2, 9, 0))) for x in set: pass self.assertEqual(list(set._cache), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 4, 9, 0), datetime(1997, 9, 9, 9, 0)]) def testStr(self): self.assertEqual(list(rrulestr( "DTSTART:19970902T090000\n" "RRULE:FREQ=YEARLY;COUNT=3\n" )), [datetime(1997, 9, 2, 9, 0), datetime(1998, 9, 2, 9, 0), datetime(1999, 9, 2, 9, 0)]) def testStrType(self): self.assertEqual(isinstance(rrulestr( "DTSTART:19970902T090000\n" "RRULE:FREQ=YEARLY;COUNT=3\n" ), rrule), True) def testStrForceSetType(self): self.assertEqual(isinstance(rrulestr( "DTSTART:19970902T090000\n" "RRULE:FREQ=YEARLY;COUNT=3\n" , forceset=True), rruleset), True) def testStrSetType(self): self.assertEqual(isinstance(rrulestr( "DTSTART:19970902T090000\n" "RRULE:FREQ=YEARLY;COUNT=2;BYDAY=TU\n" "RRULE:FREQ=YEARLY;COUNT=1;BYDAY=TH\n" ), rruleset), True) def testStrCase(self): self.assertEqual(list(rrulestr( "dtstart:19970902T090000\n" "rrule:freq=yearly;count=3\n" )), [datetime(1997, 9, 2, 9, 0), datetime(1998, 9, 2, 9, 0), datetime(1999, 9, 2, 9, 0)]) def testStrSpaces(self): self.assertEqual(list(rrulestr( " DTSTART:19970902T090000 " " RRULE:FREQ=YEARLY;COUNT=3 " )), [datetime(1997, 9, 2, 9, 0), datetime(1998, 9, 2, 9, 0), datetime(1999, 9, 2, 9, 0)]) def testStrSpacesAndLines(self): self.assertEqual(list(rrulestr( " DTSTART:19970902T090000 \n" " \n" " RRULE:FREQ=YEARLY;COUNT=3 \n" )), [datetime(1997, 9, 2, 9, 0), datetime(1998, 9, 2, 9, 0), datetime(1999, 9, 2, 9, 0)]) def testStrNoDTStart(self): self.assertEqual(list(rrulestr( "RRULE:FREQ=YEARLY;COUNT=3\n" , dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1998, 9, 2, 9, 0), datetime(1999, 9, 2, 9, 0)]) def testStrValueOnly(self): self.assertEqual(list(rrulestr( "FREQ=YEARLY;COUNT=3\n" , dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1998, 9, 2, 9, 0), datetime(1999, 9, 2, 9, 0)]) def testStrUnfold(self): self.assertEqual(list(rrulestr( "FREQ=YEA\n RLY;COUNT=3\n", unfold=True, dtstart=datetime(1997, 9, 2, 9, 0))), [datetime(1997, 9, 2, 9, 0), datetime(1998, 9, 2, 9, 0), datetime(1999, 9, 2, 9, 0)]) def testStrSet(self): self.assertEqual(list(rrulestr( "DTSTART:19970902T090000\n" "RRULE:FREQ=YEARLY;COUNT=2;BYDAY=TU\n" "RRULE:FREQ=YEARLY;COUNT=1;BYDAY=TH\n" )), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 4, 9, 0), datetime(1997, 9, 9, 9, 0)]) def testStrSetDate(self): self.assertEqual(list(rrulestr( "DTSTART:19970902T090000\n" "RRULE:FREQ=YEARLY;COUNT=1;BYDAY=TU\n" "RDATE:19970904T090000\n" "RDATE:19970909T090000\n" )), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 4, 9, 0), datetime(1997, 9, 9, 9, 0)]) def testStrSetExRule(self): self.assertEqual(list(rrulestr( "DTSTART:19970902T090000\n" "RRULE:FREQ=YEARLY;COUNT=6;BYDAY=TU,TH\n" "EXRULE:FREQ=YEARLY;COUNT=3;BYDAY=TH\n" )), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 9, 9, 0), datetime(1997, 9, 16, 9, 0)]) def testStrSetExDate(self): self.assertEqual(list(rrulestr( "DTSTART:19970902T090000\n" "RRULE:FREQ=YEARLY;COUNT=6;BYDAY=TU,TH\n" "EXDATE:19970904T090000\n" "EXDATE:19970911T090000\n" "EXDATE:19970918T090000\n" )), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 9, 9, 0), datetime(1997, 9, 16, 9, 0)]) def testStrSetDateAndExDate(self): self.assertEqual(list(rrulestr( "DTSTART:19970902T090000\n" "RDATE:19970902T090000\n" "RDATE:19970904T090000\n" "RDATE:19970909T090000\n" "RDATE:19970911T090000\n" "RDATE:19970916T090000\n" "RDATE:19970918T090000\n" "EXDATE:19970904T090000\n" "EXDATE:19970911T090000\n" "EXDATE:19970918T090000\n" )), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 9, 9, 0), datetime(1997, 9, 16, 9, 0)]) def testStrSetDateAndExRule(self): self.assertEqual(list(rrulestr( "DTSTART:19970902T090000\n" "RDATE:19970902T090000\n" "RDATE:19970904T090000\n" "RDATE:19970909T090000\n" "RDATE:19970911T090000\n" "RDATE:19970916T090000\n" "RDATE:19970918T090000\n" "EXRULE:FREQ=YEARLY;COUNT=3;BYDAY=TH\n" )), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 9, 9, 0), datetime(1997, 9, 16, 9, 0)]) def testStrKeywords(self): self.assertEqual(list(rrulestr( "DTSTART:19970902T090000\n" "RRULE:FREQ=YEARLY;COUNT=3;INTERVAL=3;" "BYMONTH=3;BYWEEKDAY=TH;BYMONTHDAY=3;" "BYHOUR=3;BYMINUTE=3;BYSECOND=3\n" )), [datetime(2033, 3, 3, 3, 3, 3), datetime(2039, 3, 3, 3, 3, 3), datetime(2072, 3, 3, 3, 3, 3)]) def testStrNWeekDay(self): self.assertEqual(list(rrulestr( "DTSTART:19970902T090000\n" "RRULE:FREQ=YEARLY;COUNT=3;BYDAY=1TU,-1TH\n" )), [datetime(1997, 12, 25, 9, 0), datetime(1998, 1, 6, 9, 0), datetime(1998, 12, 31, 9, 0)]) def testBadBySetPos(self): self.assertRaises(ValueError, rrule, MONTHLY, count=1, bysetpos=0, dtstart=datetime(1997, 9, 2, 9, 0)) def testBadBySetPosMany(self): self.assertRaises(ValueError, rrule, MONTHLY, count=1, bysetpos=(-1, 0, 1), dtstart=datetime(1997, 9, 2, 9, 0)) class ParserTest(unittest.TestCase): def setUp(self): self.tzinfos = {"BRST": -10800} self.brsttz = tzoffset("BRST", -10800) self.default = datetime(2003, 9, 25) def testDateCommandFormat(self): self.assertEqual(parse("Thu Sep 25 10:36:28 BRST 2003", tzinfos=self.tzinfos), datetime(2003, 9, 25, 10, 36, 28, tzinfo=self.brsttz)) def testDateCommandFormatUnicode(self): self.assertEqual(parse("Thu Sep 25 10:36:28 BRST 2003", tzinfos=self.tzinfos), datetime(2003, 9, 25, 10, 36, 28, tzinfo=self.brsttz)) def testDateCommandFormatReversed(self): self.assertEqual(parse("2003 10:36:28 BRST 25 Sep Thu", tzinfos=self.tzinfos), datetime(2003, 9, 25, 10, 36, 28, tzinfo=self.brsttz)) def testDateCommandFormatWithLong(self): if not PY3: self.assertEqual(parse("Thu Sep 25 10:36:28 BRST 2003", tzinfos={"BRST": long(-10800)}), datetime(2003, 9, 25, 10, 36, 28, tzinfo=self.brsttz)) def testDateCommandFormatIgnoreTz(self): self.assertEqual(parse("Thu Sep 25 10:36:28 BRST 2003", ignoretz=True), datetime(2003, 9, 25, 10, 36, 28)) def testDateCommandFormatStrip1(self): self.assertEqual(parse("Thu Sep 25 10:36:28 2003"), datetime(2003, 9, 25, 10, 36, 28)) def testDateCommandFormatStrip2(self): self.assertEqual(parse("Thu Sep 25 10:36:28", default=self.default), datetime(2003, 9, 25, 10, 36, 28)) def testDateCommandFormatStrip3(self): self.assertEqual(parse("Thu Sep 10:36:28", default=self.default), datetime(2003, 9, 25, 10, 36, 28)) def testDateCommandFormatStrip4(self): self.assertEqual(parse("Thu 10:36:28", default=self.default), datetime(2003, 9, 25, 10, 36, 28)) def testDateCommandFormatStrip5(self): self.assertEqual(parse("Sep 10:36:28", default=self.default), datetime(2003, 9, 25, 10, 36, 28)) def testDateCommandFormatStrip6(self): self.assertEqual(parse("10:36:28", default=self.default), datetime(2003, 9, 25, 10, 36, 28)) def testDateCommandFormatStrip7(self): self.assertEqual(parse("10:36", default=self.default), datetime(2003, 9, 25, 10, 36)) def testDateCommandFormatStrip8(self): self.assertEqual(parse("Thu Sep 25 2003"), datetime(2003, 9, 25)) def testDateCommandFormatStrip9(self): self.assertEqual(parse("Sep 25 2003"), datetime(2003, 9, 25)) def testDateCommandFormatStrip10(self): self.assertEqual(parse("Sep 2003", default=self.default), datetime(2003, 9, 25)) def testDateCommandFormatStrip11(self): self.assertEqual(parse("Sep", default=self.default), datetime(2003, 9, 25)) def testDateCommandFormatStrip12(self): self.assertEqual(parse("2003", default=self.default), datetime(2003, 9, 25)) def testDateRCommandFormat(self): self.assertEqual(parse("Thu, 25 Sep 2003 10:49:41 -0300"), datetime(2003, 9, 25, 10, 49, 41, tzinfo=self.brsttz)) def testISOFormat(self): self.assertEqual(parse("2003-09-25T10:49:41.5-03:00"), datetime(2003, 9, 25, 10, 49, 41, 500000, tzinfo=self.brsttz)) def testISOFormatStrip1(self): self.assertEqual(parse("2003-09-25T10:49:41-03:00"), datetime(2003, 9, 25, 10, 49, 41, tzinfo=self.brsttz)) def testISOFormatStrip2(self): self.assertEqual(parse("2003-09-25T10:49:41"), datetime(2003, 9, 25, 10, 49, 41)) def testISOFormatStrip3(self): self.assertEqual(parse("2003-09-25T10:49"), datetime(2003, 9, 25, 10, 49)) def testISOFormatStrip4(self): self.assertEqual(parse("2003-09-25T10"), datetime(2003, 9, 25, 10)) def testISOFormatStrip5(self): self.assertEqual(parse("2003-09-25"), datetime(2003, 9, 25)) def testISOStrippedFormat(self): self.assertEqual(parse("20030925T104941.5-0300"), datetime(2003, 9, 25, 10, 49, 41, 500000, tzinfo=self.brsttz)) def testISOStrippedFormatStrip1(self): self.assertEqual(parse("20030925T104941-0300"), datetime(2003, 9, 25, 10, 49, 41, tzinfo=self.brsttz)) def testISOStrippedFormatStrip2(self): self.assertEqual(parse("20030925T104941"), datetime(2003, 9, 25, 10, 49, 41)) def testISOStrippedFormatStrip3(self): self.assertEqual(parse("20030925T1049"), datetime(2003, 9, 25, 10, 49, 0)) def testISOStrippedFormatStrip4(self): self.assertEqual(parse("20030925T10"), datetime(2003, 9, 25, 10)) def testISOStrippedFormatStrip5(self): self.assertEqual(parse("20030925"), datetime(2003, 9, 25)) def testNoSeparator1(self): self.assertEqual(parse("199709020908"), datetime(1997, 9, 2, 9, 8)) def testNoSeparator2(self): self.assertEqual(parse("19970902090807"), datetime(1997, 9, 2, 9, 8, 7)) def testDateWithDash1(self): self.assertEqual(parse("2003-09-25"), datetime(2003, 9, 25)) def testDateWithDash2(self): self.assertEqual(parse("2003-Sep-25"), datetime(2003, 9, 25)) def testDateWithDash3(self): self.assertEqual(parse("25-Sep-2003"), datetime(2003, 9, 25)) def testDateWithDash4(self): self.assertEqual(parse("25-Sep-2003"), datetime(2003, 9, 25)) def testDateWithDash5(self): self.assertEqual(parse("Sep-25-2003"), datetime(2003, 9, 25)) def testDateWithDash6(self): self.assertEqual(parse("09-25-2003"), datetime(2003, 9, 25)) def testDateWithDash7(self): self.assertEqual(parse("25-09-2003"), datetime(2003, 9, 25)) def testDateWithDash8(self): self.assertEqual(parse("10-09-2003", dayfirst=True), datetime(2003, 9, 10)) def testDateWithDash9(self): self.assertEqual(parse("10-09-2003"), datetime(2003, 10, 9)) def testDateWithDash10(self): self.assertEqual(parse("10-09-03"), datetime(2003, 10, 9)) def testDateWithDash11(self): self.assertEqual(parse("10-09-03", yearfirst=True), datetime(2010, 9, 3)) def testDateWithDot1(self): self.assertEqual(parse("2003.09.25"), datetime(2003, 9, 25)) def testDateWithDot2(self): self.assertEqual(parse("2003.Sep.25"), datetime(2003, 9, 25)) def testDateWithDot3(self): self.assertEqual(parse("25.Sep.2003"), datetime(2003, 9, 25)) def testDateWithDot4(self): self.assertEqual(parse("25.Sep.2003"), datetime(2003, 9, 25)) def testDateWithDot5(self): self.assertEqual(parse("Sep.25.2003"), datetime(2003, 9, 25)) def testDateWithDot6(self): self.assertEqual(parse("09.25.2003"), datetime(2003, 9, 25)) def testDateWithDot7(self): self.assertEqual(parse("25.09.2003"), datetime(2003, 9, 25)) def testDateWithDot8(self): self.assertEqual(parse("10.09.2003", dayfirst=True), datetime(2003, 9, 10)) def testDateWithDot9(self): self.assertEqual(parse("10.09.2003"), datetime(2003, 10, 9)) def testDateWithDot10(self): self.assertEqual(parse("10.09.03"), datetime(2003, 10, 9)) def testDateWithDot11(self): self.assertEqual(parse("10.09.03", yearfirst=True), datetime(2010, 9, 3)) def testDateWithSlash1(self): self.assertEqual(parse("2003/09/25"), datetime(2003, 9, 25)) def testDateWithSlash2(self): self.assertEqual(parse("2003/Sep/25"), datetime(2003, 9, 25)) def testDateWithSlash3(self): self.assertEqual(parse("25/Sep/2003"), datetime(2003, 9, 25)) def testDateWithSlash4(self): self.assertEqual(parse("25/Sep/2003"), datetime(2003, 9, 25)) def testDateWithSlash5(self): self.assertEqual(parse("Sep/25/2003"), datetime(2003, 9, 25)) def testDateWithSlash6(self): self.assertEqual(parse("09/25/2003"), datetime(2003, 9, 25)) def testDateWithSlash7(self): self.assertEqual(parse("25/09/2003"), datetime(2003, 9, 25)) def testDateWithSlash8(self): self.assertEqual(parse("10/09/2003", dayfirst=True), datetime(2003, 9, 10)) def testDateWithSlash9(self): self.assertEqual(parse("10/09/2003"), datetime(2003, 10, 9)) def testDateWithSlash10(self): self.assertEqual(parse("10/09/03"), datetime(2003, 10, 9)) def testDateWithSlash11(self): self.assertEqual(parse("10/09/03", yearfirst=True), datetime(2010, 9, 3)) def testDateWithSpace1(self): self.assertEqual(parse("2003 09 25"), datetime(2003, 9, 25)) def testDateWithSpace2(self): self.assertEqual(parse("2003 Sep 25"), datetime(2003, 9, 25)) def testDateWithSpace3(self): self.assertEqual(parse("25 Sep 2003"), datetime(2003, 9, 25)) def testDateWithSpace4(self): self.assertEqual(parse("25 Sep 2003"), datetime(2003, 9, 25)) def testDateWithSpace5(self): self.assertEqual(parse("Sep 25 2003"), datetime(2003, 9, 25)) def testDateWithSpace6(self): self.assertEqual(parse("09 25 2003"), datetime(2003, 9, 25)) def testDateWithSpace7(self): self.assertEqual(parse("25 09 2003"), datetime(2003, 9, 25)) def testDateWithSpace8(self): self.assertEqual(parse("10 09 2003", dayfirst=True), datetime(2003, 9, 10)) def testDateWithSpace9(self): self.assertEqual(parse("10 09 2003"), datetime(2003, 10, 9)) def testDateWithSpace10(self): self.assertEqual(parse("10 09 03"), datetime(2003, 10, 9)) def testDateWithSpace11(self): self.assertEqual(parse("10 09 03", yearfirst=True), datetime(2010, 9, 3)) def testDateWithSpace12(self): self.assertEqual(parse("25 09 03"), datetime(2003, 9, 25)) def testStrangelyOrderedDate1(self): self.assertEqual(parse("03 25 Sep"), datetime(2003, 9, 25)) def testStrangelyOrderedDate2(self): self.assertEqual(parse("2003 25 Sep"), datetime(2003, 9, 25)) def testStrangelyOrderedDate3(self): self.assertEqual(parse("25 03 Sep"), datetime(2025, 9, 3)) def testHourWithLetters(self): self.assertEqual(parse("10h36m28.5s", default=self.default), datetime(2003, 9, 25, 10, 36, 28, 500000)) def testHourWithLettersStrip1(self): self.assertEqual(parse("10h36m28s", default=self.default), datetime(2003, 9, 25, 10, 36, 28)) def testHourWithLettersStrip2(self): self.assertEqual(parse("10h36m", default=self.default), datetime(2003, 9, 25, 10, 36)) def testHourWithLettersStrip3(self): self.assertEqual(parse("10h", default=self.default), datetime(2003, 9, 25, 10)) def testHourWithLettersStrip4(self): self.assertEqual(parse("10 h 36", default=self.default), datetime(2003, 9, 25, 10, 36)) def testHourAmPm1(self): self.assertEqual(parse("10h am", default=self.default), datetime(2003, 9, 25, 10)) def testHourAmPm2(self): self.assertEqual(parse("10h pm", default=self.default), datetime(2003, 9, 25, 22)) def testHourAmPm3(self): self.assertEqual(parse("10am", default=self.default), datetime(2003, 9, 25, 10)) def testHourAmPm4(self): self.assertEqual(parse("10pm", default=self.default), datetime(2003, 9, 25, 22)) def testHourAmPm5(self): self.assertEqual(parse("10:00 am", default=self.default), datetime(2003, 9, 25, 10)) def testHourAmPm6(self): self.assertEqual(parse("10:00 pm", default=self.default), datetime(2003, 9, 25, 22)) def testHourAmPm7(self): self.assertEqual(parse("10:00am", default=self.default), datetime(2003, 9, 25, 10)) def testHourAmPm8(self): self.assertEqual(parse("10:00pm", default=self.default), datetime(2003, 9, 25, 22)) def testHourAmPm9(self): self.assertEqual(parse("10:00a.m", default=self.default), datetime(2003, 9, 25, 10)) def testHourAmPm10(self): self.assertEqual(parse("10:00p.m", default=self.default), datetime(2003, 9, 25, 22)) def testHourAmPm11(self): self.assertEqual(parse("10:00a.m.", default=self.default), datetime(2003, 9, 25, 10)) def testHourAmPm12(self): self.assertEqual(parse("10:00p.m.", default=self.default), datetime(2003, 9, 25, 22)) def testPertain(self): self.assertEqual(parse("Sep 03", default=self.default), datetime(2003, 9, 3)) self.assertEqual(parse("Sep of 03", default=self.default), datetime(2003, 9, 25)) def testWeekdayAlone(self): self.assertEqual(parse("Wed", default=self.default), datetime(2003, 10, 1)) def testLongWeekday(self): self.assertEqual(parse("Wednesday", default=self.default), datetime(2003, 10, 1)) def testLongMonth(self): self.assertEqual(parse("October", default=self.default), datetime(2003, 10, 25)) def testZeroYear(self): self.assertEqual(parse("31-Dec-00", default=self.default), datetime(2000, 12, 31)) def testFuzzy(self): s = "Today is 25 of September of 2003, exactly " \ "at 10:49:41 with timezone -03:00." self.assertEqual(parse(s, fuzzy=True), datetime(2003, 9, 25, 10, 49, 41, tzinfo=self.brsttz)) def testFuzzyWithTokens(self): s = "Today is 25 of September of 2003, exactly " \ "at 10:49:41 with timezone -03:00." self.assertEqual(parse(s, fuzzy_with_tokens=True), (datetime(2003, 9, 25, 10, 49, 41, tzinfo=self.brsttz), ('Today is ', 'of ', ', exactly at ', ' with timezone ', '.'))) def testFuzzyAMPMProblem(self): # Sometimes fuzzy parsing results in AM/PM flag being set without # hours - if it's fuzzy it should ignore that. s1 = "I have a meeting on March 1, 1974." s2 = "On June 8th, 2020, I am going to be the first man on Mars" # Also don't want any erroneous AM or PMs changing the parsed time s3 = "Meet me at the AM/PM on Sunset at 3:00 AM on December 3rd, 2003" s4 = "Meet me at 3:00AM on December 3rd, 2003 at the AM/PM on Sunset" self.assertEqual(parse(s1, fuzzy=True), datetime(1974, 3, 1)) self.assertEqual(parse(s2, fuzzy=True), datetime(2020, 6, 8)) self.assertEqual(parse(s3, fuzzy=True), datetime(2003, 12, 3, 3)) self.assertEqual(parse(s4, fuzzy=True), datetime(2003, 12, 3, 3)) def testExtraSpace(self): self.assertEqual(parse(" July 4 , 1976 12:01:02 am "), datetime(1976, 7, 4, 0, 1, 2)) def testRandomFormat1(self): self.assertEqual(parse("Wed, July 10, '96"), datetime(1996, 7, 10, 0, 0)) def testRandomFormat2(self): self.assertEqual(parse("1996.07.10 AD at 15:08:56 PDT", ignoretz=True), datetime(1996, 7, 10, 15, 8, 56)) def testRandomFormat3(self): self.assertEqual(parse("1996.July.10 AD 12:08 PM"), datetime(1996, 7, 10, 12, 8)) def testRandomFormat4(self): self.assertEqual(parse("Tuesday, April 12, 1952 AD 3:30:42pm PST", ignoretz=True), datetime(1952, 4, 12, 15, 30, 42)) def testRandomFormat5(self): self.assertEqual(parse("November 5, 1994, 8:15:30 am EST", ignoretz=True), datetime(1994, 11, 5, 8, 15, 30)) def testRandomFormat6(self): self.assertEqual(parse("1994-11-05T08:15:30-05:00", ignoretz=True), datetime(1994, 11, 5, 8, 15, 30)) def testRandomFormat7(self): self.assertEqual(parse("1994-11-05T08:15:30Z", ignoretz=True), datetime(1994, 11, 5, 8, 15, 30)) def testRandomFormat8(self): self.assertEqual(parse("July 4, 1976"), datetime(1976, 7, 4)) def testRandomFormat9(self): self.assertEqual(parse("7 4 1976"), datetime(1976, 7, 4)) def testRandomFormat10(self): self.assertEqual(parse("4 jul 1976"), datetime(1976, 7, 4)) def testRandomFormat11(self): self.assertEqual(parse("7-4-76"), datetime(1976, 7, 4)) def testRandomFormat12(self): self.assertEqual(parse("19760704"), datetime(1976, 7, 4)) def testRandomFormat13(self): self.assertEqual(parse("0:01:02", default=self.default), datetime(2003, 9, 25, 0, 1, 2)) def testRandomFormat14(self): self.assertEqual(parse("12h 01m02s am", default=self.default), datetime(2003, 9, 25, 0, 1, 2)) def testRandomFormat15(self): self.assertEqual(parse("0:01:02 on July 4, 1976"), datetime(1976, 7, 4, 0, 1, 2)) def testRandomFormat16(self): self.assertEqual(parse("0:01:02 on July 4, 1976"), datetime(1976, 7, 4, 0, 1, 2)) def testRandomFormat17(self): self.assertEqual(parse("1976-07-04T00:01:02Z", ignoretz=True), datetime(1976, 7, 4, 0, 1, 2)) def testRandomFormat18(self): self.assertEqual(parse("July 4, 1976 12:01:02 am"), datetime(1976, 7, 4, 0, 1, 2)) def testRandomFormat19(self): self.assertEqual(parse("Mon Jan 2 04:24:27 1995"), datetime(1995, 1, 2, 4, 24, 27)) def testRandomFormat20(self): self.assertEqual(parse("Tue Apr 4 00:22:12 PDT 1995", ignoretz=True), datetime(1995, 4, 4, 0, 22, 12)) def testRandomFormat21(self): self.assertEqual(parse("04.04.95 00:22"), datetime(1995, 4, 4, 0, 22)) def testRandomFormat22(self): self.assertEqual(parse("Jan 1 1999 11:23:34.578"), datetime(1999, 1, 1, 11, 23, 34, 578000)) def testRandomFormat23(self): self.assertEqual(parse("950404 122212"), datetime(1995, 4, 4, 12, 22, 12)) def testRandomFormat24(self): self.assertEqual(parse("0:00 PM, PST", default=self.default, ignoretz=True), datetime(2003, 9, 25, 12, 0)) def testRandomFormat25(self): self.assertEqual(parse("12:08 PM", default=self.default), datetime(2003, 9, 25, 12, 8)) def testRandomFormat26(self): self.assertEqual(parse("5:50 A.M. on June 13, 1990"), datetime(1990, 6, 13, 5, 50)) def testRandomFormat27(self): self.assertEqual(parse("3rd of May 2001"), datetime(2001, 5, 3)) def testRandomFormat28(self): self.assertEqual(parse("5th of March 2001"), datetime(2001, 3, 5)) def testRandomFormat29(self): self.assertEqual(parse("1st of May 2003"), datetime(2003, 5, 1)) def testRandomFormat30(self): self.assertEqual(parse("01h02m03", default=self.default), datetime(2003, 9, 25, 1, 2, 3)) def testRandomFormat31(self): self.assertEqual(parse("01h02", default=self.default), datetime(2003, 9, 25, 1, 2)) def testRandomFormat32(self): self.assertEqual(parse("01h02s", default=self.default), datetime(2003, 9, 25, 1, 0, 2)) def testRandomFormat33(self): self.assertEqual(parse("01m02", default=self.default), datetime(2003, 9, 25, 0, 1, 2)) def testRandomFormat34(self): self.assertEqual(parse("01m02h", default=self.default), datetime(2003, 9, 25, 2, 1)) def testRandomFormat35(self): self.assertEqual(parse("2004 10 Apr 11h30m", default=self.default), datetime(2004, 4, 10, 11, 30)) def testErrorType01(self): self.assertRaises(ValueError, parse,'shouldfail') def testIncreasingCTime(self): # This test will check 200 different years, every month, every day, # every hour, every minute, every second, and every weekday, using # a delta of more or less 1 year, 1 month, 1 day, 1 minute and # 1 second. delta = timedelta(days=365+31+1, seconds=1+60+60*60) dt = datetime(1900, 1, 1, 0, 0, 0, 0) for i in range(200): self.assertEqual(parse(dt.ctime()), dt) dt += delta def testIncreasingISOFormat(self): delta = timedelta(days=365+31+1, seconds=1+60+60*60) dt = datetime(1900, 1, 1, 0, 0, 0, 0) for i in range(200): self.assertEqual(parse(dt.isoformat()), dt) dt += delta def testMicrosecondsPrecisionError(self): # Skip found out that sad precision problem. :-( dt1 = parse("00:11:25.01") dt2 = parse("00:12:10.01") self.assertEqual(dt1.microsecond, 10000) self.assertEqual(dt2.microsecond, 10000) def testMicrosecondPrecisionErrorReturns(self): # One more precision issue, discovered by Eric Brown. This should # be the last one, as we're no longer using floating points. for ms in [100001, 100000, 99999, 99998, 10001, 10000, 9999, 9998, 1001, 1000, 999, 998, 101, 100, 99, 98]: dt = datetime(2008, 2, 27, 21, 26, 1, ms) self.assertEqual(parse(dt.isoformat()), dt) def testHighPrecisionSeconds(self): self.assertEqual(parse("20080227T21:26:01.123456789"), datetime(2008, 2, 27, 21, 26, 1, 123456)) def testCustomParserInfo(self): # Custom parser info wasn't working, as Michael Elsdörfer discovered. from dateutil.parser import parserinfo, parser class myparserinfo(parserinfo): MONTHS = parserinfo.MONTHS[:] MONTHS[0] = ("Foo", "Foo") myparser = parser(myparserinfo()) dt = myparser.parse("01/Foo/2007") self.assertEqual(dt, datetime(2007, 1, 1)) class EasterTest(unittest.TestCase): easterlist = [ # WESTERN ORTHODOX (date(1990, 4, 15), date(1990, 4, 15)), (date(1991, 3, 31), date(1991, 4, 7)), (date(1992, 4, 19), date(1992, 4, 26)), (date(1993, 4, 11), date(1993, 4, 18)), (date(1994, 4, 3), date(1994, 5, 1)), (date(1995, 4, 16), date(1995, 4, 23)), (date(1996, 4, 7), date(1996, 4, 14)), (date(1997, 3, 30), date(1997, 4, 27)), (date(1998, 4, 12), date(1998, 4, 19)), (date(1999, 4, 4), date(1999, 4, 11)), (date(2000, 4, 23), date(2000, 4, 30)), (date(2001, 4, 15), date(2001, 4, 15)), (date(2002, 3, 31), date(2002, 5, 5)), (date(2003, 4, 20), date(2003, 4, 27)), (date(2004, 4, 11), date(2004, 4, 11)), (date(2005, 3, 27), date(2005, 5, 1)), (date(2006, 4, 16), date(2006, 4, 23)), (date(2007, 4, 8), date(2007, 4, 8)), (date(2008, 3, 23), date(2008, 4, 27)), (date(2009, 4, 12), date(2009, 4, 19)), (date(2010, 4, 4), date(2010, 4, 4)), (date(2011, 4, 24), date(2011, 4, 24)), (date(2012, 4, 8), date(2012, 4, 15)), (date(2013, 3, 31), date(2013, 5, 5)), (date(2014, 4, 20), date(2014, 4, 20)), (date(2015, 4, 5), date(2015, 4, 12)), (date(2016, 3, 27), date(2016, 5, 1)), (date(2017, 4, 16), date(2017, 4, 16)), (date(2018, 4, 1), date(2018, 4, 8)), (date(2019, 4, 21), date(2019, 4, 28)), (date(2020, 4, 12), date(2020, 4, 19)), (date(2021, 4, 4), date(2021, 5, 2)), (date(2022, 4, 17), date(2022, 4, 24)), (date(2023, 4, 9), date(2023, 4, 16)), (date(2024, 3, 31), date(2024, 5, 5)), (date(2025, 4, 20), date(2025, 4, 20)), (date(2026, 4, 5), date(2026, 4, 12)), (date(2027, 3, 28), date(2027, 5, 2)), (date(2028, 4, 16), date(2028, 4, 16)), (date(2029, 4, 1), date(2029, 4, 8)), (date(2030, 4, 21), date(2030, 4, 28)), (date(2031, 4, 13), date(2031, 4, 13)), (date(2032, 3, 28), date(2032, 5, 2)), (date(2033, 4, 17), date(2033, 4, 24)), (date(2034, 4, 9), date(2034, 4, 9)), (date(2035, 3, 25), date(2035, 4, 29)), (date(2036, 4, 13), date(2036, 4, 20)), (date(2037, 4, 5), date(2037, 4, 5)), (date(2038, 4, 25), date(2038, 4, 25)), (date(2039, 4, 10), date(2039, 4, 17)), (date(2040, 4, 1), date(2040, 5, 6)), (date(2041, 4, 21), date(2041, 4, 21)), (date(2042, 4, 6), date(2042, 4, 13)), (date(2043, 3, 29), date(2043, 5, 3)), (date(2044, 4, 17), date(2044, 4, 24)), (date(2045, 4, 9), date(2045, 4, 9)), (date(2046, 3, 25), date(2046, 4, 29)), (date(2047, 4, 14), date(2047, 4, 21)), (date(2048, 4, 5), date(2048, 4, 5)), (date(2049, 4, 18), date(2049, 4, 25)), (date(2050, 4, 10), date(2050, 4, 17)), ] def testEaster(self): for western, orthodox in self.easterlist: self.assertEqual(western, easter(western.year, EASTER_WESTERN)) self.assertEqual(orthodox, easter(orthodox.year, EASTER_ORTHODOX)) class TZTest(unittest.TestCase): TZFILE_EST5EDT = b""" VFppZgAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAAAAADrAAAABAAAABCeph5wn7rrYKCGAHCh ms1gomXicKOD6eCkaq5wpTWnYKZTyvCnFYlgqDOs8Kj+peCqE47wqt6H4KvzcPCsvmngrdNS8K6e S+CvszTwsH4t4LGcUXCyZ0pgs3wzcLRHLGC1XBVwticOYLc793C4BvBguRvZcLnm0mC7BPXwu8a0 YLzk1/C9r9DgvsS58L+PsuDApJvwwW+U4MKEffDDT3bgxGRf8MUvWODGTXxwxw864MgtXnDI+Fdg yg1AcMrYOWDLiPBw0iP0cNJg++DTdeTw1EDd4NVVxvDWIL/g1zWo8NgAoeDZFYrw2eCD4Nr+p3Db wGXg3N6JcN2pgmDevmtw34lkYOCeTXDhaUZg4n4vcONJKGDkXhFw5Vcu4OZHLfDnNxDg6CcP8OkW 8uDqBvHw6vbU4Ovm0/Ds1rbg7ca18O6/02Dvr9Jw8J+1YPGPtHDyf5dg82+WcPRfeWD1T3hw9j9b YPcvWnD4KHfg+Q88cPoIWeD6+Fjw++g74PzYOvD9yB3g/rgc8P+n/+AAl/7wAYfh4AJ34PADcP5g BGD9cAVQ4GAGQN9wBzDCYAeNGXAJEKRgCa2U8ArwhmAL4IVwDNmi4A3AZ3AOuYTgD6mD8BCZZuAR iWXwEnlI4BNpR/AUWSrgFUkp8BY5DOAXKQvwGCIpYBkI7fAaAgtgGvIKcBvh7WAc0exwHcHPYB6x znAfobFgIHYA8CGBk2AiVeLwI2qv4CQ1xPAlSpHgJhWm8Ccqc+An/sNwKQpV4CnepXAq6jfgK76H cCzTVGAtnmlwLrM2YC9+S3AwkxhgMWdn8DJy+mAzR0nwNFLcYDUnK/A2Mr5gNwcN8Dgb2uA45u/w Ofu84DrG0fA7257gPK/ucD27gOA+j9BwP5ti4EBvsnBBhH9gQk+UcENkYWBEL3ZwRURDYEYPWHBH JCVgR/h08EkEB2BJ2FbwSuPpYEu4OPBMzQXgTZga8E6s5+BPd/zwUIzJ4FFhGXBSbKvgU0D7cFRM jeBVIN1wVixv4FcAv3BYFYxgWOChcFn1bmBawINwW9VQYFypn/BdtTJgXomB8F+VFGBgaWPwYX4w 4GJJRfBjXhLgZCkn8GU99OBmEkRwZx3W4GfyJnBo/bjgadIIcGrdmuBrsepwbMa3YG2RzHBupplg b3GucHCGe2BxWsrwcmZdYHM6rPB0Rj9gdRqO8HYvW+B2+nDweA894HjaUvB57x/gero08HvPAeB8 o1Fwfa7j4H6DM3B/jsXgAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB AAEAAQABAgMBAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB AAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEA AQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB AAEAAQABAAEAAQABAAEAAQABAAEAAf//x8ABAP//ubAABP//x8ABCP//x8ABDEVEVABFU1QARVdU AEVQVAAAAAABAAAAAQ== """ EUROPE_HELSINKI = b""" VFppZgAAAAAAAAAAAAAAAAAAAAAAAAAFAAAABQAAAAAAAAB1AAAABQAAAA2kc28Yy85RYMy/hdAV I+uQFhPckBcDzZAX876QGOOvkBnToJAaw5GQG7y9EBysrhAdnJ8QHoyQEB98gRAgbHIQIVxjECJM VBAjPEUQJCw2ECUcJxAmDBgQJwVDkCf1NJAo5SWQKdUWkCrFB5ArtPiQLKTpkC2U2pAuhMuQL3S8 kDBkrZAxXdkQMnK0EDM9uxA0UpYQNR2dEDYyeBA2/X8QOBuUkDjdYRA5+3aQOr1DEDvbWJA8pl+Q Pbs6kD6GQZA/mxyQQGYjkEGEORBCRgWQQ2QbEEQl55BFQ/0QRgXJkEcj3xBH7uYQSQPBEEnOyBBK 46MQS66qEEzMv5BNjowQTqyhkE9ubhBQjIOQUVeKkFJsZZBTN2yQVExHkFUXTpBWLCmQVvcwkFgV RhBY1xKQWfUoEFq29JBb1QoQXKAREF207BBef/MQX5TOEGBf1RBhfeqQYj+3EGNdzJBkH5kQZT2u kGYItZBnHZCQZ+iXkGj9cpBpyHmQat1UkGuoW5BsxnEQbYg9kG6mUxBvaB+QcIY1EHFRPBByZhcQ czEeEHRF+RB1EQAQdi8VkHbw4hB4DveQeNDEEHnu2ZB6sKYQe867kHyZwpB9rp2QfnmkkH+Of5AC AQIDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQD BAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAME AwQAABdoAAAAACowAQQAABwgAAkAACowAQQAABwgAAlITVQARUVTVABFRVQAAAAAAQEAAAABAQ== """ NEW_YORK = b""" VFppZgAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAABcAAADrAAAABAAAABCeph5wn7rrYKCGAHCh ms1gomXicKOD6eCkaq5wpTWnYKZTyvCnFYlgqDOs8Kj+peCqE47wqt6H4KvzcPCsvmngrdNS8K6e S+CvszTwsH4t4LGcUXCyZ0pgs3wzcLRHLGC1XBVwticOYLc793C4BvBguRvZcLnm0mC7BPXwu8a0 YLzk1/C9r9DgvsS58L+PsuDApJvwwW+U4MKEffDDT3bgxGRf8MUvWODGTXxwxw864MgtXnDI+Fdg yg1AcMrYOWDLiPBw0iP0cNJg++DTdeTw1EDd4NVVxvDWIL/g1zWo8NgAoeDZFYrw2eCD4Nr+p3Db wGXg3N6JcN2pgmDevmtw34lkYOCeTXDhaUZg4n4vcONJKGDkXhFw5Vcu4OZHLfDnNxDg6CcP8OkW 8uDqBvHw6vbU4Ovm0/Ds1rbg7ca18O6/02Dvr9Jw8J+1YPGPtHDyf5dg82+WcPRfeWD1T3hw9j9b YPcvWnD4KHfg+Q88cPoIWeD6+Fjw++g74PzYOvD9yB3g/rgc8P+n/+AAl/7wAYfh4AJ34PADcP5g BGD9cAVQ4GEGQN9yBzDCYgeNGXMJEKRjCa2U9ArwhmQL4IV1DNmi5Q3AZ3YOuYTmD6mD9xCZZucR iWX4EnlI6BNpR/kUWSrpFUkp+RY5DOoXKQv6GCIpaxkI7fsaAgtsGvIKfBvh7Wwc0ex8HcHPbR6x zn0fobFtIHYA/SGBk20iVeL+I2qv7iQ1xP4lSpHuJhWm/ycqc+8n/sOAKQpV8CnepYAq6jfxK76H gSzTVHItnmmCLrM2cy9+S4MwkxhzMWdoBDJy+nQzR0oENFLcdTUnLAU2Mr51NwcOBjgb2vY45vAG Ofu89jrG0gY72572PK/uhj27gPY+j9CGP5ti9kBvsoZBhH92Qk+UhkNkYXZEL3aHRURDd0XzqQdH LV/3R9OLB0kNQfdJs20HSu0j90uciYdM1kB3TXxrh062IndPXE2HUJYEd1E8L4dSdeZ3UxwRh1RV yHdU+/OHVjWqd1blEAdYHsb3WMTyB1n+qPdapNQHW96K91yEtgddvmz3XmSYB1+eTvdgTbSHYYdr d2ItlodjZ013ZA14h2VHL3dl7VqHZycRd2fNPIdpBvN3aa0eh2rm1XdrljsHbM/x9212HQdur9P3 b1X/B3CPtfdxNeEHcm+X93MVwwd0T3n3dP7fh3Y4lnd23sGHeBh4d3i+o4d5+Fp3ep6Fh3vYPHd8 fmeHfbged35eSYd/mAB3AAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB AAEAAQABAgMBAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB AAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEA AQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB AAEAAQABAAEAAQABAAEAAQABAAEAAf//x8ABAP//ubAABP//x8ABCP//x8ABDEVEVABFU1QARVdU AEVQVAAEslgAAAAAAQWk7AEAAAACB4YfggAAAAMJZ1MDAAAABAtIhoQAAAAFDSsLhQAAAAYPDD8G AAAABxDtcocAAAAIEs6mCAAAAAkVn8qJAAAACheA/goAAAALGWIxiwAAAAwdJeoMAAAADSHa5Q0A AAAOJZ6djgAAAA8nf9EPAAAAECpQ9ZAAAAARLDIpEQAAABIuE1ySAAAAEzDnJBMAAAAUM7hIlAAA ABU2jBAVAAAAFkO3G5YAAAAXAAAAAQAAAAE= """ TZICAL_EST5EDT = """ BEGIN:VTIMEZONE TZID:US-Eastern LAST-MODIFIED:19870101T000000Z TZURL:http://zones.stds_r_us.net/tz/US-Eastern BEGIN:STANDARD DTSTART:19671029T020000 RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 TZOFFSETFROM:-0400 TZOFFSETTO:-0500 TZNAME:EST END:STANDARD BEGIN:DAYLIGHT DTSTART:19870405T020000 RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4 TZOFFSETFROM:-0500 TZOFFSETTO:-0400 TZNAME:EDT END:DAYLIGHT END:VTIMEZONE """ def testStrStart1(self): self.assertEqual(datetime(2003, 4, 6, 1, 59, tzinfo=tzstr("EST5EDT")).tzname(), "EST") self.assertEqual(datetime(2003, 4, 6, 2, 00, tzinfo=tzstr("EST5EDT")).tzname(), "EDT") def testStrEnd1(self): self.assertEqual(datetime(2003, 10, 26, 0, 59, tzinfo=tzstr("EST5EDT")).tzname(), "EDT") self.assertEqual(datetime(2003, 10, 26, 1, 00, tzinfo=tzstr("EST5EDT")).tzname(), "EST") def testStrStart2(self): s = "EST5EDT,4,0,6,7200,10,0,26,7200,3600" self.assertEqual(datetime(2003, 4, 6, 1, 59, tzinfo=tzstr(s)).tzname(), "EST") self.assertEqual(datetime(2003, 4, 6, 2, 00, tzinfo=tzstr(s)).tzname(), "EDT") def testStrEnd2(self): s = "EST5EDT,4,0,6,7200,10,0,26,7200,3600" self.assertEqual(datetime(2003, 10, 26, 0, 59, tzinfo=tzstr(s)).tzname(), "EDT") self.assertEqual(datetime(2003, 10, 26, 1, 00, tzinfo=tzstr(s)).tzname(), "EST") def testStrStart3(self): s = "EST5EDT,4,1,0,7200,10,-1,0,7200,3600" self.assertEqual(datetime(2003, 4, 6, 1, 59, tzinfo=tzstr(s)).tzname(), "EST") self.assertEqual(datetime(2003, 4, 6, 2, 00, tzinfo=tzstr(s)).tzname(), "EDT") def testStrEnd3(self): s = "EST5EDT,4,1,0,7200,10,-1,0,7200,3600" self.assertEqual(datetime(2003, 10, 26, 0, 59, tzinfo=tzstr(s)).tzname(), "EDT") self.assertEqual(datetime(2003, 10, 26, 1, 00, tzinfo=tzstr(s)).tzname(), "EST") def testStrStart4(self): s = "EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00" self.assertEqual(datetime(2003, 4, 6, 1, 59, tzinfo=tzstr(s)).tzname(), "EST") self.assertEqual(datetime(2003, 4, 6, 2, 00, tzinfo=tzstr(s)).tzname(), "EDT") def testStrEnd4(self): s = "EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00" self.assertEqual(datetime(2003, 10, 26, 0, 59, tzinfo=tzstr(s)).tzname(), "EDT") self.assertEqual(datetime(2003, 10, 26, 1, 00, tzinfo=tzstr(s)).tzname(), "EST") def testStrStart5(self): s = "EST5EDT4,95/02:00:00,298/02:00" self.assertEqual(datetime(2003, 4, 6, 1, 59, tzinfo=tzstr(s)).tzname(), "EST") self.assertEqual(datetime(2003, 4, 6, 2, 00, tzinfo=tzstr(s)).tzname(), "EDT") def testStrEnd5(self): s = "EST5EDT4,95/02:00:00,298/02" self.assertEqual(datetime(2003, 10, 26, 0, 59, tzinfo=tzstr(s)).tzname(), "EDT") self.assertEqual(datetime(2003, 10, 26, 1, 00, tzinfo=tzstr(s)).tzname(), "EST") def testStrStart6(self): s = "EST5EDT4,J96/02:00:00,J299/02:00" self.assertEqual(datetime(2003, 4, 6, 1, 59, tzinfo=tzstr(s)).tzname(), "EST") self.assertEqual(datetime(2003, 4, 6, 2, 00, tzinfo=tzstr(s)).tzname(), "EDT") def testStrEnd6(self): s = "EST5EDT4,J96/02:00:00,J299/02" self.assertEqual(datetime(2003, 10, 26, 0, 59, tzinfo=tzstr(s)).tzname(), "EDT") self.assertEqual(datetime(2003, 10, 26, 1, 00, tzinfo=tzstr(s)).tzname(), "EST") def testStrStr(self): # Test that tzstr() won't throw an error if given a str instead # of a unicode literal. self.assertEqual(datetime(2003, 4, 6, 1, 59, tzinfo=tzstr(str("EST5EDT"))).tzname(), "EST") self.assertEqual(datetime(2003, 4, 6, 2, 00, tzinfo=tzstr(str("EST5EDT"))).tzname(), "EDT") def testStrCmp1(self): self.assertEqual(tzstr("EST5EDT"), tzstr("EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00")) def testStrCmp2(self): self.assertEqual(tzstr("EST5EDT"), tzstr("EST5EDT,4,1,0,7200,10,-1,0,7200,3600")) def testRangeCmp1(self): self.assertEqual(tzstr("EST5EDT"), tzrange("EST", -18000, "EDT", -14400, relativedelta(hours=+2, month=4, day=1, weekday=SU(+1)), relativedelta(hours=+1, month=10, day=31, weekday=SU(-1)))) def testRangeCmp2(self): self.assertEqual(tzstr("EST5EDT"), tzrange("EST", -18000, "EDT")) def testFileStart1(self): tz = tzfile(BytesIO(base64.decodestring(self.TZFILE_EST5EDT))) self.assertEqual(datetime(2003, 4, 6, 1, 59, tzinfo=tz).tzname(), "EST") self.assertEqual(datetime(2003, 4, 6, 2, 00, tzinfo=tz).tzname(), "EDT") def testFileEnd1(self): tz = tzfile(BytesIO(base64.decodestring(self.TZFILE_EST5EDT))) self.assertEqual(datetime(2003, 10, 26, 0, 59, tzinfo=tz).tzname(), "EDT") self.assertEqual(datetime(2003, 10, 26, 1, 00, tzinfo=tz).tzname(), "EST") def testZoneInfoFileStart1(self): tz = zoneinfo.gettz("EST5EDT") self.assertEqual(datetime(2003, 4, 6, 1, 59, tzinfo=tz).tzname(), "EST", MISSING_TARBALL) self.assertEqual(datetime(2003, 4, 6, 2, 00, tzinfo=tz).tzname(), "EDT") def testZoneInfoFileEnd1(self): tz = zoneinfo.gettz("EST5EDT") self.assertEqual(datetime(2003, 10, 26, 0, 59, tzinfo=tz).tzname(), "EDT", MISSING_TARBALL) self.assertEqual(datetime(2003, 10, 26, 1, 00, tzinfo=tz).tzname(), "EST") def testZoneInfoOffsetSignal(self): utc = zoneinfo.gettz("UTC") nyc = zoneinfo.gettz("America/New_York") self.assertNotEqual(utc, None, MISSING_TARBALL) self.assertNotEqual(nyc, None) t0 = datetime(2007, 11, 4, 0, 30, tzinfo=nyc) t1 = t0.astimezone(utc) t2 = t1.astimezone(nyc) self.assertEqual(t0, t2) self.assertEqual(nyc.dst(t0), timedelta(hours=1)) def testICalStart1(self): tz = tzical(StringIO(self.TZICAL_EST5EDT)).get() self.assertEqual(datetime(2003, 4, 6, 1, 59, tzinfo=tz).tzname(), "EST") self.assertEqual(datetime(2003, 4, 6, 2, 00, tzinfo=tz).tzname(), "EDT") def testICalEnd1(self): tz = tzical(StringIO(self.TZICAL_EST5EDT)).get() self.assertEqual(datetime(2003, 10, 26, 0, 59, tzinfo=tz).tzname(), "EDT") self.assertEqual(datetime(2003, 10, 26, 1, 00, tzinfo=tz).tzname(), "EST") def testRoundNonFullMinutes(self): # This timezone has an offset of 5992 seconds in 1900-01-01. tz = tzfile(BytesIO(base64.decodestring(self.EUROPE_HELSINKI))) self.assertEqual(str(datetime(1900, 1, 1, 0, 0, tzinfo=tz)), "1900-01-01 00:00:00+01:40") def testLeapCountDecodesProperly(self): # This timezone has leapcnt, and failed to decode until # Eugene Oden notified about the issue. tz = tzfile(BytesIO(base64.decodestring(self.NEW_YORK))) self.assertEqual(datetime(2007, 3, 31, 20, 12).tzname(), None) def testGettz(self): # bug 892569 str(gettz('UTC')) def testBrokenIsDstHandling(self): # tzrange._isdst() was using a date() rather than a datetime(). # Issue reported by Lennart Regebro. dt = datetime(2007, 8, 6, 4, 10, tzinfo=tzutc()) self.assertEqual(dt.astimezone(tz=gettz("GMT+2")), datetime(2007, 8, 6, 6, 10, tzinfo=tzstr("GMT+2"))) def testGMTHasNoDaylight(self): # tzstr("GMT+2") improperly considered daylight saving time. # Issue reported by Lennart Regebro. dt = datetime(2007, 8, 6, 4, 10) self.assertEqual(gettz("GMT+2").dst(dt), timedelta(0)) def testGMTOffset(self): # GMT and UTC offsets have inverted signal when compared to the # usual TZ variable handling. dt = datetime(2007, 8, 6, 4, 10, tzinfo=tzutc()) self.assertEqual(dt.astimezone(tz=tzstr("GMT+2")), datetime(2007, 8, 6, 6, 10, tzinfo=tzstr("GMT+2"))) self.assertEqual(dt.astimezone(tz=gettz("UTC-2")), datetime(2007, 8, 6, 2, 10, tzinfo=tzstr("UTC-2"))) @unittest.skipUnless(sys.platform.startswith("win"), "requires Windows") def testIsdstZoneWithNoDaylightSaving(self): tz = tzwin.tzwin("UTC") dt = parse("2013-03-06 19:08:15") self.assertFalse(tz._isdst(dt)) # vim:ts=4:sw=4 python-dateutil-2.4.2/dateutil/test/__init__.py0000666000000000000000000000000012505254345017701 0ustar 00000000000000python-dateutil-2.4.2/dateutil/tz.py0000666000000000000000000010246112505254345015636 0ustar 00000000000000# -*- coding: utf-8 -*- """ This module offers timezone implementations subclassing the abstract :py:`datetime.tzinfo` type. There are classes to handle tzfile format files (usually are in :file:`/etc/localtime`, :file:`/usr/share/zoneinfo`, etc), TZ environment string (in all known formats), given ranges (with help from relative deltas), local machine timezone, fixed offset timezone, and UTC timezone. """ import datetime import struct import time import sys import os from six import string_types, PY3 try: from dateutil.tzwin import tzwin, tzwinlocal except ImportError: tzwin = tzwinlocal = None relativedelta = None parser = None rrule = None __all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange", "tzstr", "tzical", "tzwin", "tzwinlocal", "gettz"] def tzname_in_python2(myfunc): """Change unicode output into bytestrings in Python 2 tzname() API changed in Python 3. It used to return bytes, but was changed to unicode strings """ def inner_func(*args, **kwargs): if PY3: return myfunc(*args, **kwargs) else: return myfunc(*args, **kwargs).encode() return inner_func ZERO = datetime.timedelta(0) EPOCHORDINAL = datetime.datetime.utcfromtimestamp(0).toordinal() class tzutc(datetime.tzinfo): def utcoffset(self, dt): return ZERO def dst(self, dt): return ZERO @tzname_in_python2 def tzname(self, dt): return "UTC" def __eq__(self, other): return (isinstance(other, tzutc) or (isinstance(other, tzoffset) and other._offset == ZERO)) def __ne__(self, other): return not self.__eq__(other) def __repr__(self): return "%s()" % self.__class__.__name__ __reduce__ = object.__reduce__ class tzoffset(datetime.tzinfo): def __init__(self, name, offset): self._name = name self._offset = datetime.timedelta(seconds=offset) def utcoffset(self, dt): return self._offset def dst(self, dt): return ZERO @tzname_in_python2 def tzname(self, dt): return self._name def __eq__(self, other): return (isinstance(other, tzoffset) and self._offset == other._offset) def __ne__(self, other): return not self.__eq__(other) def __repr__(self): return "%s(%s, %s)" % (self.__class__.__name__, repr(self._name), self._offset.days*86400+self._offset.seconds) __reduce__ = object.__reduce__ class tzlocal(datetime.tzinfo): _std_offset = datetime.timedelta(seconds=-time.timezone) if time.daylight: _dst_offset = datetime.timedelta(seconds=-time.altzone) else: _dst_offset = _std_offset def utcoffset(self, dt): if self._isdst(dt): return self._dst_offset else: return self._std_offset def dst(self, dt): if self._isdst(dt): return self._dst_offset-self._std_offset else: return ZERO @tzname_in_python2 def tzname(self, dt): return time.tzname[self._isdst(dt)] def _isdst(self, dt): # We can't use mktime here. It is unstable when deciding if # the hour near to a change is DST or not. # # timestamp = time.mktime((dt.year, dt.month, dt.day, dt.hour, # dt.minute, dt.second, dt.weekday(), 0, -1)) # return time.localtime(timestamp).tm_isdst # # The code above yields the following result: # # >>> import tz, datetime # >>> t = tz.tzlocal() # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname() # 'BRDT' # >>> datetime.datetime(2003,2,16,0,tzinfo=t).tzname() # 'BRST' # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname() # 'BRST' # >>> datetime.datetime(2003,2,15,22,tzinfo=t).tzname() # 'BRDT' # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname() # 'BRDT' # # Here is a more stable implementation: # timestamp = ((dt.toordinal() - EPOCHORDINAL) * 86400 + dt.hour * 3600 + dt.minute * 60 + dt.second) return time.localtime(timestamp+time.timezone).tm_isdst def __eq__(self, other): if not isinstance(other, tzlocal): return False return (self._std_offset == other._std_offset and self._dst_offset == other._dst_offset) return True def __ne__(self, other): return not self.__eq__(other) def __repr__(self): return "%s()" % self.__class__.__name__ __reduce__ = object.__reduce__ class _ttinfo(object): __slots__ = ["offset", "delta", "isdst", "abbr", "isstd", "isgmt"] def __init__(self): for attr in self.__slots__: setattr(self, attr, None) def __repr__(self): l = [] for attr in self.__slots__: value = getattr(self, attr) if value is not None: l.append("%s=%s" % (attr, repr(value))) return "%s(%s)" % (self.__class__.__name__, ", ".join(l)) def __eq__(self, other): if not isinstance(other, _ttinfo): return False return (self.offset == other.offset and self.delta == other.delta and self.isdst == other.isdst and self.abbr == other.abbr and self.isstd == other.isstd and self.isgmt == other.isgmt) def __ne__(self, other): return not self.__eq__(other) def __getstate__(self): state = {} for name in self.__slots__: state[name] = getattr(self, name, None) return state def __setstate__(self, state): for name in self.__slots__: if name in state: setattr(self, name, state[name]) class tzfile(datetime.tzinfo): # http://www.twinsun.com/tz/tz-link.htm # ftp://ftp.iana.org/tz/tz*.tar.gz def __init__(self, fileobj, filename=None): file_opened_here = False if isinstance(fileobj, string_types): self._filename = fileobj fileobj = open(fileobj, 'rb') file_opened_here = True elif filename is not None: self._filename = filename elif hasattr(fileobj, "name"): self._filename = fileobj.name else: self._filename = repr(fileobj) # From tzfile(5): # # The time zone information files used by tzset(3) # begin with the magic characters "TZif" to identify # them as time zone information files, followed by # sixteen bytes reserved for future use, followed by # six four-byte values of type long, written in a # ``standard'' byte order (the high-order byte # of the value is written first). try: if fileobj.read(4).decode() != "TZif": raise ValueError("magic not found") fileobj.read(16) ( # The number of UTC/local indicators stored in the file. ttisgmtcnt, # The number of standard/wall indicators stored in the file. ttisstdcnt, # The number of leap seconds for which data is # stored in the file. leapcnt, # The number of "transition times" for which data # is stored in the file. timecnt, # The number of "local time types" for which data # is stored in the file (must not be zero). typecnt, # The number of characters of "time zone # abbreviation strings" stored in the file. charcnt, ) = struct.unpack(">6l", fileobj.read(24)) # The above header is followed by tzh_timecnt four-byte # values of type long, sorted in ascending order. # These values are written in ``standard'' byte order. # Each is used as a transition time (as returned by # time(2)) at which the rules for computing local time # change. if timecnt: self._trans_list = struct.unpack(">%dl" % timecnt, fileobj.read(timecnt*4)) else: self._trans_list = [] # Next come tzh_timecnt one-byte values of type unsigned # char; each one tells which of the different types of # ``local time'' types described in the file is associated # with the same-indexed transition time. These values # serve as indices into an array of ttinfo structures that # appears next in the file. if timecnt: self._trans_idx = struct.unpack(">%dB" % timecnt, fileobj.read(timecnt)) else: self._trans_idx = [] # Each ttinfo structure is written as a four-byte value # for tt_gmtoff of type long, in a standard byte # order, followed by a one-byte value for tt_isdst # and a one-byte value for tt_abbrind. In each # structure, tt_gmtoff gives the number of # seconds to be added to UTC, tt_isdst tells whether # tm_isdst should be set by localtime(3), and # tt_abbrind serves as an index into the array of # time zone abbreviation characters that follow the # ttinfo structure(s) in the file. ttinfo = [] for i in range(typecnt): ttinfo.append(struct.unpack(">lbb", fileobj.read(6))) abbr = fileobj.read(charcnt).decode() # Then there are tzh_leapcnt pairs of four-byte # values, written in standard byte order; the # first value of each pair gives the time (as # returned by time(2)) at which a leap second # occurs; the second gives the total number of # leap seconds to be applied after the given time. # The pairs of values are sorted in ascending order # by time. # Not used, for now # if leapcnt: # leap = struct.unpack(">%dl" % (leapcnt*2), # fileobj.read(leapcnt*8)) # Then there are tzh_ttisstdcnt standard/wall # indicators, each stored as a one-byte value; # they tell whether the transition times associated # with local time types were specified as standard # time or wall clock time, and are used when # a time zone file is used in handling POSIX-style # time zone environment variables. if ttisstdcnt: isstd = struct.unpack(">%db" % ttisstdcnt, fileobj.read(ttisstdcnt)) # Finally, there are tzh_ttisgmtcnt UTC/local # indicators, each stored as a one-byte value; # they tell whether the transition times associated # with local time types were specified as UTC or # local time, and are used when a time zone file # is used in handling POSIX-style time zone envi- # ronment variables. if ttisgmtcnt: isgmt = struct.unpack(">%db" % ttisgmtcnt, fileobj.read(ttisgmtcnt)) # ** Everything has been read ** finally: if file_opened_here: fileobj.close() # Build ttinfo list self._ttinfo_list = [] for i in range(typecnt): gmtoff, isdst, abbrind = ttinfo[i] # Round to full-minutes if that's not the case. Python's # datetime doesn't accept sub-minute timezones. Check # http://python.org/sf/1447945 for some information. gmtoff = (gmtoff+30)//60*60 tti = _ttinfo() tti.offset = gmtoff tti.delta = datetime.timedelta(seconds=gmtoff) tti.isdst = isdst tti.abbr = abbr[abbrind:abbr.find('\x00', abbrind)] tti.isstd = (ttisstdcnt > i and isstd[i] != 0) tti.isgmt = (ttisgmtcnt > i and isgmt[i] != 0) self._ttinfo_list.append(tti) # Replace ttinfo indexes for ttinfo objects. trans_idx = [] for idx in self._trans_idx: trans_idx.append(self._ttinfo_list[idx]) self._trans_idx = tuple(trans_idx) # Set standard, dst, and before ttinfos. before will be # used when a given time is before any transitions, # and will be set to the first non-dst ttinfo, or to # the first dst, if all of them are dst. self._ttinfo_std = None self._ttinfo_dst = None self._ttinfo_before = None if self._ttinfo_list: if not self._trans_list: self._ttinfo_std = self._ttinfo_first = self._ttinfo_list[0] else: for i in range(timecnt-1, -1, -1): tti = self._trans_idx[i] if not self._ttinfo_std and not tti.isdst: self._ttinfo_std = tti elif not self._ttinfo_dst and tti.isdst: self._ttinfo_dst = tti if self._ttinfo_std and self._ttinfo_dst: break else: if self._ttinfo_dst and not self._ttinfo_std: self._ttinfo_std = self._ttinfo_dst for tti in self._ttinfo_list: if not tti.isdst: self._ttinfo_before = tti break else: self._ttinfo_before = self._ttinfo_list[0] # Now fix transition times to become relative to wall time. # # I'm not sure about this. In my tests, the tz source file # is setup to wall time, and in the binary file isstd and # isgmt are off, so it should be in wall time. OTOH, it's # always in gmt time. Let me know if you have comments # about this. laststdoffset = 0 self._trans_list = list(self._trans_list) for i in range(len(self._trans_list)): tti = self._trans_idx[i] if not tti.isdst: # This is std time. self._trans_list[i] += tti.offset laststdoffset = tti.offset else: # This is dst time. Convert to std. self._trans_list[i] += laststdoffset self._trans_list = tuple(self._trans_list) def _find_ttinfo(self, dt, laststd=0): timestamp = ((dt.toordinal() - EPOCHORDINAL) * 86400 + dt.hour * 3600 + dt.minute * 60 + dt.second) idx = 0 for trans in self._trans_list: if timestamp < trans: break idx += 1 else: return self._ttinfo_std if idx == 0: return self._ttinfo_before if laststd: while idx > 0: tti = self._trans_idx[idx-1] if not tti.isdst: return tti idx -= 1 else: return self._ttinfo_std else: return self._trans_idx[idx-1] def utcoffset(self, dt): if not self._ttinfo_std: return ZERO return self._find_ttinfo(dt).delta def dst(self, dt): if not self._ttinfo_dst: return ZERO tti = self._find_ttinfo(dt) if not tti.isdst: return ZERO # The documentation says that utcoffset()-dst() must # be constant for every dt. return tti.delta-self._find_ttinfo(dt, laststd=1).delta # An alternative for that would be: # # return self._ttinfo_dst.offset-self._ttinfo_std.offset # # However, this class stores historical changes in the # dst offset, so I belive that this wouldn't be the right # way to implement this. @tzname_in_python2 def tzname(self, dt): if not self._ttinfo_std: return None return self._find_ttinfo(dt).abbr def __eq__(self, other): if not isinstance(other, tzfile): return False return (self._trans_list == other._trans_list and self._trans_idx == other._trans_idx and self._ttinfo_list == other._ttinfo_list) def __ne__(self, other): return not self.__eq__(other) def __repr__(self): return "%s(%s)" % (self.__class__.__name__, repr(self._filename)) def __reduce__(self): if not os.path.isfile(self._filename): raise ValueError("Unpickable %s class" % self.__class__.__name__) return (self.__class__, (self._filename,)) class tzrange(datetime.tzinfo): def __init__(self, stdabbr, stdoffset=None, dstabbr=None, dstoffset=None, start=None, end=None): global relativedelta if not relativedelta: from dateutil import relativedelta self._std_abbr = stdabbr self._dst_abbr = dstabbr if stdoffset is not None: self._std_offset = datetime.timedelta(seconds=stdoffset) else: self._std_offset = ZERO if dstoffset is not None: self._dst_offset = datetime.timedelta(seconds=dstoffset) elif dstabbr and stdoffset is not None: self._dst_offset = self._std_offset+datetime.timedelta(hours=+1) else: self._dst_offset = ZERO if dstabbr and start is None: self._start_delta = relativedelta.relativedelta( hours=+2, month=4, day=1, weekday=relativedelta.SU(+1)) else: self._start_delta = start if dstabbr and end is None: self._end_delta = relativedelta.relativedelta( hours=+1, month=10, day=31, weekday=relativedelta.SU(-1)) else: self._end_delta = end def utcoffset(self, dt): if self._isdst(dt): return self._dst_offset else: return self._std_offset def dst(self, dt): if self._isdst(dt): return self._dst_offset-self._std_offset else: return ZERO @tzname_in_python2 def tzname(self, dt): if self._isdst(dt): return self._dst_abbr else: return self._std_abbr def _isdst(self, dt): if not self._start_delta: return False year = datetime.datetime(dt.year, 1, 1) start = year+self._start_delta end = year+self._end_delta dt = dt.replace(tzinfo=None) if start < end: return dt >= start and dt < end else: return dt >= start or dt < end def __eq__(self, other): if not isinstance(other, tzrange): return False return (self._std_abbr == other._std_abbr and self._dst_abbr == other._dst_abbr and self._std_offset == other._std_offset and self._dst_offset == other._dst_offset and self._start_delta == other._start_delta and self._end_delta == other._end_delta) def __ne__(self, other): return not self.__eq__(other) def __repr__(self): return "%s(...)" % self.__class__.__name__ __reduce__ = object.__reduce__ class tzstr(tzrange): def __init__(self, s): global parser if not parser: from dateutil import parser self._s = s res = parser._parsetz(s) if res is None: raise ValueError("unknown string format") # Here we break the compatibility with the TZ variable handling. # GMT-3 actually *means* the timezone -3. if res.stdabbr in ("GMT", "UTC"): res.stdoffset *= -1 # We must initialize it first, since _delta() needs # _std_offset and _dst_offset set. Use False in start/end # to avoid building it two times. tzrange.__init__(self, res.stdabbr, res.stdoffset, res.dstabbr, res.dstoffset, start=False, end=False) if not res.dstabbr: self._start_delta = None self._end_delta = None else: self._start_delta = self._delta(res.start) if self._start_delta: self._end_delta = self._delta(res.end, isend=1) def _delta(self, x, isend=0): kwargs = {} if x.month is not None: kwargs["month"] = x.month if x.weekday is not None: kwargs["weekday"] = relativedelta.weekday(x.weekday, x.week) if x.week > 0: kwargs["day"] = 1 else: kwargs["day"] = 31 elif x.day: kwargs["day"] = x.day elif x.yday is not None: kwargs["yearday"] = x.yday elif x.jyday is not None: kwargs["nlyearday"] = x.jyday if not kwargs: # Default is to start on first sunday of april, and end # on last sunday of october. if not isend: kwargs["month"] = 4 kwargs["day"] = 1 kwargs["weekday"] = relativedelta.SU(+1) else: kwargs["month"] = 10 kwargs["day"] = 31 kwargs["weekday"] = relativedelta.SU(-1) if x.time is not None: kwargs["seconds"] = x.time else: # Default is 2AM. kwargs["seconds"] = 7200 if isend: # Convert to standard time, to follow the documented way # of working with the extra hour. See the documentation # of the tzinfo class. delta = self._dst_offset-self._std_offset kwargs["seconds"] -= delta.seconds+delta.days*86400 return relativedelta.relativedelta(**kwargs) def __repr__(self): return "%s(%s)" % (self.__class__.__name__, repr(self._s)) class _tzicalvtzcomp(object): def __init__(self, tzoffsetfrom, tzoffsetto, isdst, tzname=None, rrule=None): self.tzoffsetfrom = datetime.timedelta(seconds=tzoffsetfrom) self.tzoffsetto = datetime.timedelta(seconds=tzoffsetto) self.tzoffsetdiff = self.tzoffsetto-self.tzoffsetfrom self.isdst = isdst self.tzname = tzname self.rrule = rrule class _tzicalvtz(datetime.tzinfo): def __init__(self, tzid, comps=[]): self._tzid = tzid self._comps = comps self._cachedate = [] self._cachecomp = [] def _find_comp(self, dt): if len(self._comps) == 1: return self._comps[0] dt = dt.replace(tzinfo=None) try: return self._cachecomp[self._cachedate.index(dt)] except ValueError: pass lastcomp = None lastcompdt = None for comp in self._comps: if not comp.isdst: # Handle the extra hour in DST -> STD compdt = comp.rrule.before(dt-comp.tzoffsetdiff, inc=True) else: compdt = comp.rrule.before(dt, inc=True) if compdt and (not lastcompdt or lastcompdt < compdt): lastcompdt = compdt lastcomp = comp if not lastcomp: # RFC says nothing about what to do when a given # time is before the first onset date. We'll look for the # first standard component, or the first component, if # none is found. for comp in self._comps: if not comp.isdst: lastcomp = comp break else: lastcomp = comp[0] self._cachedate.insert(0, dt) self._cachecomp.insert(0, lastcomp) if len(self._cachedate) > 10: self._cachedate.pop() self._cachecomp.pop() return lastcomp def utcoffset(self, dt): return self._find_comp(dt).tzoffsetto def dst(self, dt): comp = self._find_comp(dt) if comp.isdst: return comp.tzoffsetdiff else: return ZERO @tzname_in_python2 def tzname(self, dt): return self._find_comp(dt).tzname def __repr__(self): return "" % repr(self._tzid) __reduce__ = object.__reduce__ class tzical(object): def __init__(self, fileobj): global rrule if not rrule: from dateutil import rrule if isinstance(fileobj, string_types): self._s = fileobj # ical should be encoded in UTF-8 with CRLF fileobj = open(fileobj, 'r') elif hasattr(fileobj, "name"): self._s = fileobj.name else: self._s = repr(fileobj) self._vtz = {} self._parse_rfc(fileobj.read()) def keys(self): return list(self._vtz.keys()) def get(self, tzid=None): if tzid is None: keys = list(self._vtz.keys()) if len(keys) == 0: raise ValueError("no timezones defined") elif len(keys) > 1: raise ValueError("more than one timezone available") tzid = keys[0] return self._vtz.get(tzid) def _parse_offset(self, s): s = s.strip() if not s: raise ValueError("empty offset") if s[0] in ('+', '-'): signal = (-1, +1)[s[0] == '+'] s = s[1:] else: signal = +1 if len(s) == 4: return (int(s[:2])*3600+int(s[2:])*60)*signal elif len(s) == 6: return (int(s[:2])*3600+int(s[2:4])*60+int(s[4:]))*signal else: raise ValueError("invalid offset: "+s) def _parse_rfc(self, s): lines = s.splitlines() if not lines: raise ValueError("empty string") # Unfold i = 0 while i < len(lines): line = lines[i].rstrip() if not line: del lines[i] elif i > 0 and line[0] == " ": lines[i-1] += line[1:] del lines[i] else: i += 1 tzid = None comps = [] invtz = False comptype = None for line in lines: if not line: continue name, value = line.split(':', 1) parms = name.split(';') if not parms: raise ValueError("empty property name") name = parms[0].upper() parms = parms[1:] if invtz: if name == "BEGIN": if value in ("STANDARD", "DAYLIGHT"): # Process component pass else: raise ValueError("unknown component: "+value) comptype = value founddtstart = False tzoffsetfrom = None tzoffsetto = None rrulelines = [] tzname = None elif name == "END": if value == "VTIMEZONE": if comptype: raise ValueError("component not closed: "+comptype) if not tzid: raise ValueError("mandatory TZID not found") if not comps: raise ValueError( "at least one component is needed") # Process vtimezone self._vtz[tzid] = _tzicalvtz(tzid, comps) invtz = False elif value == comptype: if not founddtstart: raise ValueError("mandatory DTSTART not found") if tzoffsetfrom is None: raise ValueError( "mandatory TZOFFSETFROM not found") if tzoffsetto is None: raise ValueError( "mandatory TZOFFSETFROM not found") # Process component rr = None if rrulelines: rr = rrule.rrulestr("\n".join(rrulelines), compatible=True, ignoretz=True, cache=True) comp = _tzicalvtzcomp(tzoffsetfrom, tzoffsetto, (comptype == "DAYLIGHT"), tzname, rr) comps.append(comp) comptype = None else: raise ValueError("invalid component end: "+value) elif comptype: if name == "DTSTART": rrulelines.append(line) founddtstart = True elif name in ("RRULE", "RDATE", "EXRULE", "EXDATE"): rrulelines.append(line) elif name == "TZOFFSETFROM": if parms: raise ValueError( "unsupported %s parm: %s " % (name, parms[0])) tzoffsetfrom = self._parse_offset(value) elif name == "TZOFFSETTO": if parms: raise ValueError( "unsupported TZOFFSETTO parm: "+parms[0]) tzoffsetto = self._parse_offset(value) elif name == "TZNAME": if parms: raise ValueError( "unsupported TZNAME parm: "+parms[0]) tzname = value elif name == "COMMENT": pass else: raise ValueError("unsupported property: "+name) else: if name == "TZID": if parms: raise ValueError( "unsupported TZID parm: "+parms[0]) tzid = value elif name in ("TZURL", "LAST-MODIFIED", "COMMENT"): pass else: raise ValueError("unsupported property: "+name) elif name == "BEGIN" and value == "VTIMEZONE": tzid = None comps = [] invtz = True def __repr__(self): return "%s(%s)" % (self.__class__.__name__, repr(self._s)) if sys.platform != "win32": TZFILES = ["/etc/localtime", "localtime"] TZPATHS = ["/usr/share/zoneinfo", "/usr/lib/zoneinfo", "/etc/zoneinfo"] else: TZFILES = [] TZPATHS = [] def gettz(name=None): tz = None if not name: try: name = os.environ["TZ"] except KeyError: pass if name is None or name == ":": for filepath in TZFILES: if not os.path.isabs(filepath): filename = filepath for path in TZPATHS: filepath = os.path.join(path, filename) if os.path.isfile(filepath): break else: continue if os.path.isfile(filepath): try: tz = tzfile(filepath) break except (IOError, OSError, ValueError): pass else: tz = tzlocal() else: if name.startswith(":"): name = name[:-1] if os.path.isabs(name): if os.path.isfile(name): tz = tzfile(name) else: tz = None else: for path in TZPATHS: filepath = os.path.join(path, name) if not os.path.isfile(filepath): filepath = filepath.replace(' ', '_') if not os.path.isfile(filepath): continue try: tz = tzfile(filepath) break except (IOError, OSError, ValueError): pass else: tz = None if tzwin is not None: try: tz = tzwin(name) except WindowsError: tz = None if not tz: from dateutil.zoneinfo import gettz tz = gettz(name) if not tz: for c in name: # name must have at least one offset to be a tzstr if c in "0123456789": try: tz = tzstr(name) except ValueError: pass break else: if name in ("GMT", "UTC"): tz = tzutc() elif name in time.tzname: tz = tzlocal() return tz # vim:ts=4:sw=4:et python-dateutil-2.4.2/dateutil/tzwin.py0000666000000000000000000001400512505254345016350 0ustar 00000000000000# This code was originally contributed by Jeffrey Harris. import datetime import struct from six.moves import winreg __all__ = ["tzwin", "tzwinlocal"] ONEWEEK = datetime.timedelta(7) TZKEYNAMENT = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones" TZKEYNAME9X = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones" TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation" def _settzkeyname(): handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) try: winreg.OpenKey(handle, TZKEYNAMENT).Close() TZKEYNAME = TZKEYNAMENT except WindowsError: TZKEYNAME = TZKEYNAME9X handle.Close() return TZKEYNAME TZKEYNAME = _settzkeyname() class tzwinbase(datetime.tzinfo): """tzinfo class based on win32's timezones available in the registry.""" def utcoffset(self, dt): if self._isdst(dt): return datetime.timedelta(minutes=self._dstoffset) else: return datetime.timedelta(minutes=self._stdoffset) def dst(self, dt): if self._isdst(dt): minutes = self._dstoffset - self._stdoffset return datetime.timedelta(minutes=minutes) else: return datetime.timedelta(0) def tzname(self, dt): if self._isdst(dt): return self._dstname else: return self._stdname def list(): """Return a list of all time zones known to the system.""" handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) tzkey = winreg.OpenKey(handle, TZKEYNAME) result = [winreg.EnumKey(tzkey, i) for i in range(winreg.QueryInfoKey(tzkey)[0])] tzkey.Close() handle.Close() return result list = staticmethod(list) def display(self): return self._display def _isdst(self, dt): if not self._dstmonth: # dstmonth == 0 signals the zone has no daylight saving time return False dston = picknthweekday(dt.year, self._dstmonth, self._dstdayofweek, self._dsthour, self._dstminute, self._dstweeknumber) dstoff = picknthweekday(dt.year, self._stdmonth, self._stddayofweek, self._stdhour, self._stdminute, self._stdweeknumber) if dston < dstoff: return dston <= dt.replace(tzinfo=None) < dstoff else: return not dstoff <= dt.replace(tzinfo=None) < dston class tzwin(tzwinbase): def __init__(self, name): self._name = name # multiple contexts only possible in 2.7 and 3.1, we still support 2.6 with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle: with winreg.OpenKey(handle, "%s\%s" % (TZKEYNAME, name)) as tzkey: keydict = valuestodict(tzkey) self._stdname = keydict["Std"].encode("iso-8859-1") self._dstname = keydict["Dlt"].encode("iso-8859-1") self._display = keydict["Display"] # See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm tup = struct.unpack("=3l16h", keydict["TZI"]) self._stdoffset = -tup[0]-tup[1] # Bias + StandardBias * -1 self._dstoffset = self._stdoffset-tup[2] # + DaylightBias * -1 # for the meaning see the win32 TIME_ZONE_INFORMATION structure docs # http://msdn.microsoft.com/en-us/library/windows/desktop/ms725481(v=vs.85).aspx (self._stdmonth, self._stddayofweek, # Sunday = 0 self._stdweeknumber, # Last = 5 self._stdhour, self._stdminute) = tup[4:9] (self._dstmonth, self._dstdayofweek, # Sunday = 0 self._dstweeknumber, # Last = 5 self._dsthour, self._dstminute) = tup[12:17] def __repr__(self): return "tzwin(%s)" % repr(self._name) def __reduce__(self): return (self.__class__, (self._name,)) class tzwinlocal(tzwinbase): def __init__(self): with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle: with winreg.OpenKey(handle, TZLOCALKEYNAME) as tzlocalkey: keydict = valuestodict(tzlocalkey) self._stdname = keydict["StandardName"].encode("iso-8859-1") self._dstname = keydict["DaylightName"].encode("iso-8859-1") try: with winreg.OpenKey( handle, "%s\%s" % (TZKEYNAME, self._stdname)) as tzkey: _keydict = valuestodict(tzkey) self._display = _keydict["Display"] except OSError: self._display = None self._stdoffset = -keydict["Bias"]-keydict["StandardBias"] self._dstoffset = self._stdoffset-keydict["DaylightBias"] # See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm tup = struct.unpack("=8h", keydict["StandardStart"]) (self._stdmonth, self._stddayofweek, # Sunday = 0 self._stdweeknumber, # Last = 5 self._stdhour, self._stdminute) = tup[1:6] tup = struct.unpack("=8h", keydict["DaylightStart"]) (self._dstmonth, self._dstdayofweek, # Sunday = 0 self._dstweeknumber, # Last = 5 self._dsthour, self._dstminute) = tup[1:6] def __reduce__(self): return (self.__class__, ()) def picknthweekday(year, month, dayofweek, hour, minute, whichweek): """dayofweek == 0 means Sunday, whichweek 5 means last instance""" first = datetime.datetime(year, month, 1, hour, minute) weekdayone = first.replace(day=((dayofweek-first.isoweekday()) % 7+1)) for n in range(whichweek): dt = weekdayone+(whichweek-n)*ONEWEEK if dt.month == month: return dt def valuestodict(key): """Convert a registry key's values to a dictionary.""" dict = {} size = winreg.QueryInfoKey(key)[1] for i in range(size): data = winreg.EnumValue(key, i) dict[data[0]] = data[1] return dict python-dateutil-2.4.2/dateutil/zoneinfo/0000777000000000000000000000000012506556557016465 5ustar 00000000000000python-dateutil-2.4.2/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz0000666000000000000000000044674312506555066023270 0ustar 000000000000006Udateutil-zoneinfo.tar] @T~(wW} ; (Ȗ2Oמsܲr)5I[\F[,K\0P%Yy4|49w{߽cRFHy)tLS#Ͼ@l2ѧ`I {` JOM ŖWrR? IgK?j@S`.Kc|"' Ų_Ki7 {J^YYfw~R8nF]SF[~_?\/eߗ+c-3NUdY^e6/;hˇJx櫛^^+g>PWt`Y9(_^oYcޤquFZ-)s}矷`ignyD4fx˪[57Tt[Tg_-na֭wQުXyܲK` 齔2_Wϲ$eJżn냄i/#QUgt{[0@ &# (H 1@ 2 `1@ :a Hd H8d 0h@a@ !0&#2a0.##ah 0:|svt؄[_1]cdrW>sҺOgZ~tI^Ŋ{IRYxҰTR.gFJ]? ߟ[>>X轰ލGlH8sZ8_Pu_P+L~j}__` x&|O[ڟݲ{+%nu{Z|eS_0~aC ά]Q]GJ9/4ʙ'ITodJ&ms.S܊nKarŌ0{1]*%/bs}x]_z3.6@r\Uߗ?X5{>EJa Uj3^j^. 1dqb,ONiLW b9mqbi9Mqgbv@aI dD. [Ⴝvv@دP Gl&\lY 6*! SO7qE&'s??''<OOO($?Y:D UqRƥп7?Տn<)[~_1ģiT[z!?(O`BW9e yvx,avXOnI̎ۚ.x6oWyV 'ڒ&Q,2G "S _T?iv~?6e-#!11)$Wũ'!N>!&=Zw``J ~'1aj)&O_;{^{: S3߻]fٳҺf^?`Ǹ2_,|-JUpX-٨R$j铗eVoS˾`/Wn9Z'*%ձW9.U`qWQYXvTþ]TَZ[%v{%FezjjxS{}3R)# F1ƍ#&߱#5s&4.j-_KZm?n=EK_&3`毮g2?ugflt i5v%7X\mikY:f(Q.C-S\#S3N 88ή/[z\=\,=+s|Bf/Wޏl9ek{+(W.X"ۺ"^qd5[O>9xhXs\C--CgdV3Kp 9l$}1Oq ?&sD#VtkᖑsHQFk􄒙ZmC[bzFZb{qUi~:=~c?^aI8u-q%.~-s\I3L^᲏JΜJ<035+nPfzǡ _ LT:yWe˔S_F? 4亍=MBF 4B/L t4t-BOqASU C5+ @- @h0#0Z @2 >ќN@5 n݌@h8#8Z@:#i;#;H:HZ:Az33@F )).0Rl`D`<+1# 18D,"S+@ 1  1D"C@" 1 Dl">qI1 8DbxD"n_@0  x&}*nFiٯ=۪Rm1TfΏM꿠k+Y^?&vgVeuW]O^w_lbG{Ԓ;'K[F*c w}ZџTeeo(խss{5)f}lS{~OlPS!S+;">a;1H੏:0ecoCQU" Q-3 Z0`79(0ꆑz{6XqϺ QaцQn5D?:ú6#CLJCL{8bú8:mqq_g#ceՑ{c1/'8w4Lx#E!yl+}vaކ1 - i=| 24td݆I}2&9阢3L70L9fL^9eAF}:grPRC,r-C]vRb 3# z@# "MQ䎌䒜6[rL)f>E9m&7崙\\rWNge9"e$tW]pcNȕpgVri f$f$ՁpwFryNy  DNۈD NӈD (H44HtaU}Іч(iшHtⴍ(iъH4(iьHt㴎(iюH4(iѐ<";J2Ÿ=9#rG44iѕ<,yD[N󈺜}9# sG44iQhQiQ<9yDuN9#sWGRr~Dieg M';+Z_\zZa=oT<~if}UDşۗ,MՓ^RC*>ǽzx{ iriɆrrÎF-Ƶ&e>aF׍fW՚Z;|;cGֳhmҶhJ -r`:9f8qT998p|yܶcZmrZG/Fk6:պu,yO o~Wmrv˽\&YwZG__ HJ-?mPbE W iLEU!G}ZQkSyڈ䑎 =~GE~l~Jƿ$OQNSMN +u]N mhri2ߕ'9M_~Yvm_f>:S0P[| >%| 惲9?yOyDO1);.<&`9σyy݂9Ο;3>!="@`$b Dރ{0Ix" ?Ld0D|`dܐ%qC Y"7dtܐ%qC Y" 7dܐ%"2!Kܼ!brC Y"(7dܐ%rCI,D\ny!K,DdF"3f$RsC Y"77dܐ%sC Y";7dܐ%sP$sC YnȒpC$@#7bII2 ʀD+ H,8`pe@ W$"D柨L?\~MdL,2ܛ"S F?E&,2SȼߔEL{% Q&Id)ȴ'k"ӎD=BDYdڝ4iG"ӮL$L,2jȴ2BD-"ӮL ȴk"~L{;#Od/0Ba<=ONO\()@hnBny`aK&Cy.@KINOK("nt~SgAڿ{bt-!=0rmb7M _v%˝-=힉v+EwsV=;HKKP@//raAy@Ӗb˧ophcmIIq1)nu9%K!{WS-Nh@H$JuoUNĄfYJ ?fHo3${W=Fronx(*o~IZ\5h)6wW.N?d0[] RRDw|̻e,eUnyX٩ V1{Ȭ5kіjMV/0Fdk;-5zU+\uVlv76"]n}LUa ?yGc[xLpѸ06@c? bab1b܀;%?FC8rƒ+DOИrƕ'4@/c 81֬4@91d -6]KqMНD3WkKIKNPDO759؟oK,oX=?)u-Vdo S"Sɳ@O<'O_A?ݖ[9;~ `7}^gRGrwK*^νfyPjK;qOL>Xw#?vmB\R!:7z?|o؊c&+59fŖXTjřݼՓYv~IS`XozN 'i;yitcҊH7y m"a 䔴V3<_%GG;XL>>-?YUOI''N' y¬s7eK\Fo^-kz|8mؿ~e;UvV+څ꧚kܻ]뭯MqYRUwQi{X v+jd,n8;O֠nviSꠄuw$MkĮإCta^oow#9b4 FFaSli30R@#1z@ hI FbTMӦ] FQbm FQb}\0 rtk Kӓ %Z~d߳/ZlkWb뮣 \tY{jھU}{uMF9sSW8ZNioaǦF-ksg{״ggxyx\~K5I*5oX@N8瀄$pؒ["Ұ4AaXJg&>J]La#ix/>˄6MhHuV>BB{sg5k 9yv4 h9#)a;$ίCb&Ǎ/ bk=fpݓ}_ {&%^rzuBm$9 G lꒃzT.i&辶i!%^SC>tjmyDmydÂYZUNh'I]? -Rh8@)fܭMeۅfrrGJ+=z}zVOԾ1>ZZߔ5XE"KJ'Eru)w֗F<,|zHfMRmƪZL/ѕkMwT8!9E]ir‡/J,_89y‚qRșrľM8|R;LNm[MkTӿ2.SPtحN^Mx@:6I?fFFF]'>(.=E^"Ko ֈ Z>}iT/ثϤ?؋sL=; |+?d!?IFK |GS |wYS9x?IH|'6A|?ׁw}b#pF< l'`^ 7.1?._ xq,<K@ N1"c1\\9\A\!\@p^r( n2~-G)\@p]\/K$"ڵF'8߁|(d0 4RofX_l @~ҲyY|`ʆ#ғ&,'rzf 4:KeTu>2!|Yjw?gi23un,+,SnTR³fafj`FŢ5[40u"H} Yg{zK  ifn8(qCJ*7)YU }Ζ[-^lvfhjփ51nO1y!涵+mO]*UJGyY+93t#ΰؗnm8wwGsx{}HvqJg;~2[7m{;;e#juF)G^jbrCbWV<ʏ5#}u|ֵʨcS.8msV1>s_ǖ ON;C};| +bN?~912!c9I$nϿL 7$SR2 p*iYm?3uNU;}=ΩקX᜾!V1js=tJrnQo~de AgV G,UʺU O)SDJ]kH)' O'믨µ0?wy{=9Gqϐ(=?!#Q:@K\DM~DOnE)tU+79\m D_F0q1T܃$Jsh=H6 @PAu I,Q'Ȉ<1F1?O#I#O$O4DODDOTpo{$%(I$% a$BJIN diW._If8rԈu%|H$;\pJq+ )b<) IdW&._ua_H/J¾,Q BD1qJRHre+I#$m\qJ+GV:._I|%dW>._IG$$@H!#!$$\4r\ldWJ._I.|%dWN._I>|% 岕d)e$9岕$dVVFW._Ib|%򕤖W[._Ir|%򕤗W_._I|%򕤘VdFe.[II|%dWj._I|%dWn -U?$?4</@>}'[_FMNFx/bZ\NwU.w\0ڜrEz롦JI޶0&zN߀#R)Ԋf?^kr(_y,?B0usJ +Te7WmTk6\A^\@fr͟N4v>HQwM vJ8 4Ҹ&UPnrQoٱ[[hxzm͌tePSA+ƈxCi[ɦJS:QowڼY+wy9:MG{اڬԻwq%Փ(N1Z\}zϳLrL~cu;2:"j#HK-!!_Tfv4W]9n2;(l]nD/cAYi>:AGlY] wĝ^rK|[8V ǝ,x0!c1$ Q&PR3 U.*iҲm?pd]T]wLzT|~cf,ѧo1>}mE/OA!CI\ST;+kebz%;l1 7}IyUR=E [tUD %h(z%zR`(D* )J&(D*@`(E W%H%hQT%fQPD YQ|Y5h][/(7Fc(G3'uPiP o7-6z-6G9+=#;hMQY[pdP뚥ط` CLy@XO(&a5N,rd=N,ȂdE , 5(V²@X ae , 8#Y$3~,<{$o`$+ Nd;CDB_S[8! Asx'4=АqBC^'< <1³8!y< y1y#y<y`ą<䍌@x%3uMТxli wpdCoaٯeQ7oI1\ڙܾȖ"[Ev{D_Owh;1YM V$E1)2NRe$7.I(Y?yBr전 [.r?F%0O>&'uOM'-Ag4zzB~D"̢=??M|WOǥݓ@nCgŒH}__u87=oWGK^Qxo]Ee˝uu0_VnE h.iKw C|֓=(!ypu{.*] G&M.no+I.7nI _Ox'+DdP;'/.)6yJAMC~Sc2>ң'tZRlÚ;cTF+㌭W3. %XisO۶<{ĥ'ݷȜ3qr/.oHo]ݏa|~nSɘXx]mc `GHs}Y㯎q4=Ǒ8GzAq<Ǒ8nGy1q<Ǒ8NGy!qt<Ǒ8.GG <֗b O[tt󮡵 M 8FGxcqdanMr%ь_åɭْ{q3\f܌-V7#v6O&J_WՖbK&?o *`=k_sk!7X7 n<۽DܒdWgɁUՠ1qyS<6Mj{a?:~ZIuyfdN@ uNWYu(7 U/{y7T{J{0Xߓͤ0d{_ZJ{{uH>5(*uدÝ |etfvkBd+R=m,u.Sq_+wFuUqs}‚#jҋ/ٓ'nQ'?,oOmYJϞ^9)M;LiR>IGl[l /,Z'BՙtKZ^/>|GVsst읯Jep/^"DkWnsʝ.^F8!O)l7q@u!V9dqNC5y"ȡ˵ӇYƸ֝vhӵ6]ޞ]Ƶi=Q7n%ꭍ[GiKm[7w蝨wKzvu/ʵk׎Cy-pK?ڣw`>Q1G3-m_ D RĬH䚗Z,/陳Z[ zbhyU #a~A0 0d HF̙ H`@ a< ȭ:2"˜@aXF2.dddhF26GW28GW2:?yJ'O)9?yJNO# )9?yJNOc)9#9 ?qJH08r$p"m#13PTܭcO%naIݬ7_)L~*}_xÃ%>y脕YOgcaOR|YRJ/KEs^.|sEnAթ]AXE,;aͳ̏ޥëOEIHMˏ5`7P_%гgQg6pMWb>CIg5~t~1^bCg~Pe+2T9dY3[t႟y*/=^gu'}s9 Aܠ'9 v_dn]]E/X3  EOYF)z 44 l=P [+hDOؿ1NFRl=`4'.zaN\<F-z}'Di v|Z|O E]?5޼x{^<;I7iy4/~hB*3;6eʀ۪}}nTzj7MF;z'~l'S?Az_ϽZ{}=K6W;>r=sh9҅oKʗwlh'g\5Tr5Uo[ rI-a*T5RS-b~^-Z9TVnR+Y~劓J raR^=I՚57(',VD)Zm߫uW-ݧ2[6%7Wjd:.7RjRٿ&_Bk"5;Mk~*e]mAAjxkw;|kCo5M ?ܫ(ڹrfϳ˵TM_Y02A4tYNI^'VBNv墖vqsmRI[&P,^M=N6}C{uƨ8m擵U}{(RYYʋ%Ub𺺤9M%U^f,ݲS E,-Ӂ[ܓ9kr~ rh]`"w@P D˼:Vr}@4]y;PPwS@a./"J?hHތDqDs q@qA\ $Yin,L0T!@HKHHKKHHHH r !/$1@ #I g4@r B8, #W, d ^ BIꀐ;F<`o_KrD&B C $YB9ԑ<2D!$@%#I& |₄! @).H* !@H,2HR 2!$@/ !Ǹ @2.H3  L3^|\3d!ی$@7!@9K!]or٠ց\6fz\RrjthBJ\j?Dͩ))Ƃ^0J㶒G+I3{Ji~x՘JkrsEc3BKLRmc̼9xFuE;W1%]1[9%]]Vpo_(ٜ] \O*3L6͈GV޽wci vM1vQ*NY.Na2Ns&$_O qOㄟ ?1~b!#1~b#'9{{+9'f2m{R4}b.'^N9'&sOlGDX͉~`7'pFb9'`;zNs&sIAJ>'*pO>/)#' ^P NI1I58'Dԃ=by'$蓚p؛‰x<ԅ}RFRNIiIm8,'DqOĉ>'L:qO H* >)#'XZ@('^蓂qO*Ɖ<Ԍ}R4NI8чѥ|/n`+-jx w/itzR7[m  3E )V5 >rmmzW֖}avwUNuwLmNvwlfڽ@{cFAH{zφpV>ge|YTyfW nXλM _ͮXGC):aNX/}f5XtEGLp߰#FC*Ff0F0Ff0FYFfT\ rtޭͫ mO+ƀkLCG,{c`Զn`sC%}Cߦhܗ{SM8eǫBtHwfKcj_&RF7XoMlOKJʟ'nzku)gQܗ/8B|(|) ~-[+r߾_G' ']_{.*/&:ᳶo7I&W sMҭgiVK !<&s?k/˩K>j~O/t?.}ba*O_,|eJI_fJo׽MZx{0IhuM|}Pu]x{#kn(qžIu7.foi-_}#{뻻;HI ~76If{gSw*Q}>~Ϭ;{ǃ?>1sjzS:$rtص}QgcOُm~17Z^m/C˻[lh!e\-=YVؽ?J'{J>3Ӫi}_-%ZLs?f/Z9:r˿PJsW&U<])aTyX[w^mj5)UobQTX {͟_Zg?S-}n(/տor72^nR ɿ}n+F[6?bԖc߫^f0UY!GIA& ;]Rr?/oJ&>%*;kTwgP~odz>`xEAg멑?G^R6؇|]W;j֭w4ؓޕ:=*E)zHv6g= زոNcxz}_/{Šo?=!;yHY>DӏRJrwjsiWNm_gۯN>>iju)SON>cPu췸>?u\7>규c4-iX*~!lJxH( H;y>O BGy>R~4#]e$mH_y>Xުt#Hoy>\ުt#Hy>` t#-S8HyEi2 ]f$m-HIy>iު#Hy>mު#Hy>qޢ=-H@OuqgwF?;B~ P"0R< b+>(FbD"v?!@ b HqD|"g5@ bq+R(A[8$Usx% %0q$&-axXK1Fq ]\ Pf30nS"1(1>"&)q @ D"y}O@R?O@R$<IO@RNy30~]F r\p!"<\|r F57)"" r\GIp!/"7+FUW)g"o"w"""""< ͝'4Ynn rgB~qʾ^u R'C+jȖyHm&mPn)"UiwmHyPUe56S;h#>NTGE꨹)5Am3I1bѣHͺq+Hc*Z:H?w^XJ8H5%ĝ Ւ^|CMf:qI9ed5{rZjz6 ȓ&I'Qi RxO޹vrwH~_óG#+kO^6ywg׏߮]><|pӶ(qKcP_z~妞n cm}o@uMߋ{=*ftA{jϴji~٪UrVC֖گSh\9式Ɨ>chMX1ޜј13sSlyف>}d&]8#pGa* N~ B&FtO1)L/>= 2{6tFom2M}J )UzE 5 6To(UPIQD5:~>嘆fha[Z!e ~44EnRCŲӎf~~GS1r-Z-^h֪PuSiAicD ?жdMC';/:}OWwtyGgɟ9Bgu֭ѽ/ O?WD' O%O .mHI.V9mHUʑ֩e1ڤҦ,^zh6m =R3l.|-}#櫨Oe3/jT ^ F/ Æ˂q=x w>u<1A5~GB4>d4k24|?;  #ډb )W3 /Nb1sbx'zFbǹqs!1sbx9X ~X r@\܁ʹZ@ e]$3J cEb31{EmF7.b9b;6b=ID:# {G1HIwDH:=$ !^p4{HfH;D=$!p{H'ߤ)+;"ma$}i # HkDz=$!?{WEoQtL1DTt^O@EFE{PFEʳ_E㭿_zcyd9J^8j5> by@}ݙ&C}H{! >$CԇDQ$KwDJOwD0JSwDJWԇD0nQ.C"E}Hè!,>$gwD0 $l!̣>${ԇDQ@C"8HC̿ mZ>97Cn>9Hz^=ys蠫12g|6'r+'-2{*krzÇrV;ʙyoZl(K 0rCRS{5wrE{\/9>i{u~rmnzEu*FJ.mg*~>V]i&{kܝKcٜp?67͗[ߒe:s6sepe'˅:)mto(y^+Zx(OИsmt(+Eeb}~ZdD4ܭ{,,VgpX 8~bae߷(X(XS~e~`;5}~5~wkXKװ;]k%XC-X`n- V}dd3vc~bkVe=w?}9~DzqŲy4DMv́";$B䷡5s9ãckmZ3FoΜ!t \$v z{.^ufuWh}ޛ'=f&7rBmq5ĠA;` !W!뵡3aYkgnGaKQ)4!`:g0e8&"@K} WN5 ׅȏ Mj -zP.ߦM%dD-6X-&IW SSf2; I4G!y8c)#0A𕗷ٽ_FJg?7<{L̂ހXy=Bz=+t v ie1dƫt l,J qx5X qOCf#3n!3v֑WՐWё;]Ef\E@flK_2cȌ Ȍo3:22s:2T ^ ZI x8H@f)"3?`> dʋpaq ] 34d19\Af}N_cL3ȌcL7Ȍ_>wtdM4dDdFqcq̸̸-]>!3}0 NQI dձ#6Bf^Cf#3~]CfMGflҐ/ёӐoO/Ȍ&;dC&=d&?dƎ"2cXH1Cd:2 lDfMCfȌhȌq"%2cWMdƻ&OdƨȌQ_O"3;iM=x?zbd3]DEELY%61:5yQlusi,/1 d,i04+yiuk_s?g DŽ:N *ĝ1[3yb>ErK//RuYg>vؓZ4CTJ4CTH$JNJX5ű"3DS yX@Իp@бt)Ry.'ߣ/-qsZsc-;YWwS;㌧YoϜkA_Woj"7 ,w9YfiyDŽ떆JsY|*?XZ[.c500yt gF۠72mbipDu^^-x4rip yl!%c(rTm[HUT[HB[Hm籅T[HWT#xl!ނ-xl!uֈ-~VT- jc eg%|\vgȥ|\~3m ? ؒ_Y>S7j yop.:$pϼ L߫wOi/OMךt3C$?ΫWxU K),WD5]4?VJK?k&mH S%dž;]f{M8W{0 T=~,PLeoȱ5Kko^_U|ʾ>˹p){0˕Hv- aD8-/+~ +:*7jRȬ$GfDfy G]Dn&Ҍ"4hDW.vVFW.vVFWF]8* ]8* Pp,@3%F%A5%Ȇ\k't;EhR{EƧWWwwCOb`P'vרa8+;pۆSuϚOcoZŝ9A r~]gQ~$%prkí/qmoɮ`toW*fHC*&!9k}/8}fqΜ7֛p 1nR`,i9h+*2.2E E_(Ţ(HP0dAJl~"`m!U i~ Q16X[{o4 9~Oנ4Fo끢ҰQV*QZ~#ݘyHoMD1Bv o~[a-\6o8ٝ(<ߜU % ~녙0ւ['o<߆@o؈o 4 XPy:h~o1*߰ ߰ ߰ ZA ]d. H -{-iZn7״&>9-ƕGY{]ǀŬgY/ ֧"t(Nl!rǵN˝gw,wc]gc*/whܳ]W/e&gXߟ/ew`{__7opo&<.6A}}A/[;7 r}/6!7ZC*;jWj#f#g\gG-FeC_Gwpi<69,G\Z"#GHǟ'&Ob'$GFk(=I;1c_f':=gj0l|[{9^?&T)'{ğSki}.OZ 3M!>?汶kё}$'YOB6JP%d (J!TP-dX *lSAՐb}*Y*Ab}* YOU,;֧JRSA5)%I%QQfR6KTo>TX KX *L٬ SA),u *MY,Qk*jSkzrPufb=9!g#]% 9_9!Z['Ǘjv楫\p*~iǁ+̟?K%Bxz+! 8&.nrNLzv>[;\ԜܴjZ޺*Dldeʊz 4z%xPZNSN5|7YI9N-r%9۶vΚ]vI3Z\R$R6QMrbToMN69^nL7g\ܗp26cޤN;_eO1znRיL'u6h7gדs|]<}3t{vF%=V5)cS\ ,RX}}1oyj539Yd:uQڏzo|.ѣHuP (Mݏ%  $H2f @` fHh >yBf'i ByBf@"8!H%G< ̓~y'~O xf ̓t JbOL,AT* TpL,ql@G}#^l0?6&GQ$=Cph0H6$HcG*Gp$Iu3iiIsUL _̸˱<_]r02]KD6]r0⟍qeIwiqiIwƸˈ$4aܥK]0҈ ¸KTU]栥لqT0r>-Us6]堥v6]堥qIhgcemZ>w ]'m¸K$5a%9.wr0r6]5|wq&tM]!qNvkЧ41Qab&fS.Eq%bpϑZg`ju6R'((-AYyfOwWzBMqbxin^nlPS ^_ImlB?x򩊃>*\m =tgŁjBφ}xPN> uQKQGM!".)zwoYHC'EJ@݋Y?/SXVe[ Ж 7hK C>H#4x%^ʶ=bhԳ?]zZK9^z9Ӹ\C'= 6gG4oy+ٸ.ö(bѯ1Mϲͻ}muSf]׶?O,?Zv5|[=|aDѸ t4~6=Umq\n=#ztPvۤ~,T'iǵ8T4X>+}pf^0_P~.p޿ѷKuKN6ڥkROޔ^V6c=z o]m ZDCk}}iR5pQKK ϰnюSvރ t"Aھ!N\T@ (RI$# AH ` ' $'HP HARs*mN@$JRp,HY* Ђ.͟$ Qyi%aGIQ0yi%eGIQp4͟$ M%+M^T+I^q8q;M%7߇& 7Y<ɆIQMyU̓K){ղ9c9,_޳+3VnN2sx[}W=>bgW9R}JXe;,5]QkZfZ4:FXֽRN FjFǩ9#7ԓo]ظ{IM76]uz)wb[~غ;.)|% j䶼aP56z p\{ME[Vںch۾6}=/;ᅤW{?yo"jyjS4mۚP g4;^GsR`dQ&_Ĭ YԬ/bV -jVU;Ԣf2Y1M-jV,勚SfpYљ/jVp|Q _ԬpTZԬ@}gV>3+֪E {f=YxϬ@}gVf!m0 ږh?xɑ&S{6XioKb(m`aG#BZU/!l˓0IDrɺM-e=ܒo?]sP0EQr&6"˱ nIol>-Co'Шж|ow/O?7/R6EN~^_zy "OTt³yxxY< dz79~sƼ ;};2?z;ꗜ_x>W/gg_R5I`xuү户oE0w.9ܴVLz;YjE/3줪b*RygTѠt*blEbmTYIvW6.k̋e^J kF3>Lv1u{u+0]#?{Gt8|[jpg"_i LM=3f?ЛocZ;mX_y}2QX[1}["=O HJm7ÇI;I~:4rl`41#WC]Og%vT}dZbׅϥ9hҘabl)ܷ~eP8[h#F~Z8a?+=yV#v}/~\O?L9zl)guHJ3[LH$%'NMY(M-&%IZ#K3V-bTc:<&񫇏Cz8?bD:myqh6;md@1‘y84@L8G׀tj/MΊtJ-[ ( &K,`D+蚢<+Iג ZDd dd d A%VlҬ2NB:&K A!Le0Y"Xd`%|tLk@'$"&K^DFd&K1Y┄5 &K1Y*,`:z-1, Vd1:&TGI &b:A@L&ĊuLણoDL&H0 b20D_' ~LPB1X'ȉ^4X'^p>E@ScTG^~Sue7\GzE7LDl ~pA`5qv1zZK;/ϟA_^V z5 5z ^ z8\π;$o6#^q"zp?3l/s lb'M(J6H61:3E ɣb"vyyZ?K }TCD'q^ڌ5ԏ{#p[HfqJZ9Cm0`R2YHσ ]E7X2)b8 K"^A+Q}? J9kLG>9`D_o 323ҋ'Eq @/02#lıi8شG'Bϊ& 8a^DkbD)O}xeRjG8-"Sجfc 8< Zz҈5iun +5^bdԘww(t1YvO]-1Yp <{g9^%GFN 1F+w\hMz}[c>%uHBh汽 Y#=6\ H#Y )9>2@9hRPNu9s|5x W#yxleGL CC̑FlDDJavp>Rx9E8"GV.E~^4a`֤V#ġ3YqB1[ A8.&Y:-'Y$%&'ފeHIɯK#KG*S/eƎԩSG >..l.n.\)A ;ANvA® ;JA- $hH  A;@$h <@[@PI$hD{@$hH&Q A(X-$hD@$h"<@AF$h&<@;)E4$h)HT4Kޖ,%YE$ hFh4Kh ,#YYB$RF,CנhhɍD,ُɍD,9ɍ,F@4K1ha,ǠYEf@ 4KHhl,,Y$Y zYɍE0:&74A䪄f`z,hc, @4K0he,ʠYrEd1&7Ad3&7Bd&74B$^BdfIfk2%%4Ke4KHEc7)["ۻW/T)7ƴf<Rwk\EY[^L5[+lCoņ+6 \<7zex9T^YZsSɚSv朵Fly[k>Gl$Dmx mu׈-{ 8%x:ҽvO\r}Xp2Eiu]֌ћŮ3gB_EBGU_XZw"ASP]jP{=p@OP"?R={_8JyGC"ٙ/҈WGRC#!#t~7P$U$C $Q!$$ߊH 7 H  B 9 drrr-6#rrr[C9GD$ @z H H H #_GyJ@JG. cP[*"JGyXGBT !cPk$Xz KAͭ/_Ł?wޡC, g.wtY[X}k[-ޫ_Y"j;gj7j5/%i6䓗fJB~$D BO@`#~ >IщjMfOFcOOO >1H ᓃOSs(zǍK|F{?oy DGFF=iиn?%?oz {5gs|-Yf3g3*[& WvR \R\ke![fw2\8[,WRvJl 9v/%NfkFpٗ:u+뵪ֻ\\3HSs+*+?oghJ&/qM=knC)-9봹ekuK s-QJ̆&)<R< ^n){|jr>?0sk﹎NvrJ.˗s3w(1w[hܳ4_oTC+ù>6=$2>T >/a t73JPN+s9%!1 0$,7tMUð=?FFQ)s8! :g2[YJ_yD5GVlD~l2adImMd2W境 I^u 1*7!6p39[%>wsB~ҜxmejʧiJRiJW3(ʟzuWy*U|_yy܁s'2L#?ҟ,ަ;"S>wy n]mppCi}wѢG2,޼05b9< 9nhQYkY"Hwa\ b夜(( (*> " (.R #:"Z Jem-` @pHpH8@,R /)@ju8@t ޑnf 5S0֔wzGOAH;zGk \;r׀ޑ81wC#wyCi3zGD#;ޑfD)y͌^Ab𚛂ޑW#aw]ax#7#s^knFNpBr#١w3zG)hF4#g Yʡwd#S91wd8ޑf$(YeFP#騠w$Œ5G#t VfS;g@Hᵖ􎜥kE;#k8;mF*#ߚ;2KAٌޑ#o;⯠w$֌V zG# ^kɡwd]bxۀޑ@#*ޑ;lka|ww3zG zGN;ޑOU;# zG^1wGa_PV\9~1L4g Ŝ''; b`ōǛap,7:.rr|T_(Ypf/VOɬ]CʮUoz#'+PDu?uu?2u?{?GYl=@CY_vw>ѬǨzXMCLoSxJ0OPb@'mt A%A*ԠX9 `= +)RREf:t. R k F'5K^Z쿀1&ӛ [cx"S-zH=x7xm?Tt(~%ӭHt&PS8'Iz߸ѡX?}CGSwbaK/\5Q6y iHw˳8iK~6ꀱg+8~wހ-YxXȉ a^ ?Evos7y㾶 ],_To~WXg/87UN-RΫ,..3Wc/CVz5 }]7 Q'PTFQg@>$:>,3OmyQ]"Mᓟ{zpqZS>e/+[*+޽):eJ'r+'-2{*krzÇrV;ʙyoZl(K 0rCRS{5wrE{\/9>i{u~rmnzEu*FJ.mg*Zd>V]i&{kܝKcٜpg\4zkJs/Gj+ v.;n4\qR[q^Tnl5m ׬'lsÜS)l+YG7)3eJ-69N1,&F͹Mn*r+ȞN%ٻPΏl9\_uNv[M}J:s-0]G[Ecz:뛱3[pcE{lo{_9p_Fq2 :tP]r(s!Wِ}ܐ v\nX6vL97*%9ȣ],ʘ)rJ%w~eA88V@O1O83x_.*5v;ɫ 9; ͫX:+J⭙ԔTeZX9)y2=ON9Y\[+/oAs8Yޗ[SɎL8l v A(EzRhF)Z*)UmM[hV0Kf1k^ʘ^oɘ5E|/Q|! { 1߫^2{p8?^9lb]aW.-oqB8^9 p,qE.dಒeL[Ƭ $ @aT'è?kQ,"hJ:QiT*Fu~1sKI:8|J׀9bT:HsIފ1#9`Tg4QM Fue,T0#ՉS0MƨFuՉk@nsչ$cTLJɩ$hQYp1?:bT甂Q 2FuGƨ`Tg"V$Fu^ފ4 {+S:XlQ03^ƨ; Fuՙ`TQ!t 'Fu:(չ)Qﰷ"9M0s{+S:,Fur9lc1qYbTs:,Fu#cTǢ`TgQ Fu`oEraTV$搓 :~ Fud׃ :FurN-ht Fu>g1sèj쭘 'Fu~R0{+ :3e꠾IQW803YNKy1>[kg=ws4x?$?O %}]<\yBDŽGF?xwu(-QOoJtSKJ5*;z>ΊNl4TݳS;pgqgNkЂܹ_of 3ܬp}m#o)w+r3]i[DŽs?4z%иIknrNy_37 %\(޿7ڝ9ohw7@ct5y Ґ5Hy HQ=@h@߸D cT4\E.䒁.5x壒\BjH%%R×\VjKK KryaI.15,e%԰$JrɩAI.;SÒ\~jX[@ )r!En5;>p[!En 5vD%UT3eԠ!2QC>*- ԐyҖ|5/ễX]"a%l8D(hZ6_}jPG8 zt8}_ 9y? z|6*^+WI@R'zg^W|vU!zU5rgq!lx>MfJB]WJ =Ҫ3q֫>OdW2;MY˳ gN7jA] .(o2f1ެ|{/2 m5 wkss/3:&\07T[8FL16rڲ2y(P*P/,HAes3z #gF6͈lUdqbPI._," eͭ7"Jp͙ bZ͵7"0 ߇ l.`/4rqa2a2ri]p[ԚtU_LdDty`[aOTD\jgyc#"'ŕp/9p/XuG̀(c-fz|?=#4W/8 0x6& %M7wbK~L?6tbtL0nzYUAl?!?r=rRSsN;ϒ,ucE:M[J{XأL >Vm.z/g/g3D{(n/;v*th( :&HibG&;=ddlׇ> Z>U2\>B$]VKc"avpNbqRSC1`o)QkiP>1zVc{I01k9zl)guHJ3[LH$%'NMY(M-&%IZ#K3Vgā8†:q"<>( ˰p_S]&{:c+%@ž $-odQ cCǐF1d=dA^ $I d_ 'Hأ aJ;ٯT= -dҋN/80c$ix {A $uAž JAH*.} :  :+} :*sD@QIt $H)W A@~YzFtKπЩШ nG~ILm,~DH%ݝ}y`+]gxI#dU=VmU򽵀/DϜ'm`c&`7rL`B]"?S`:  w9`b^ >x]j@v$ ;b[?iv;IfhvȶLs6s fK:q5ٷe Lcz_q…EKCGޅ A]X->5*lN$->A@aho G#hI-?X[wY!JJO%E%)j96?D={!قG:}==XSXP^$ 0v,]~[O3w-Ur#IjY ;:Ra#3bQ9OfFX"ch fHNsb;Ǿ4@hqCwwy4z}¦[i>qRubO$qz‰P&}ݙiIFI7^Փ_*x+Cg)ZT8{A9O(5?K_SG/3B٫襯 {Ha2"K"KdO>UB/}^zG߈uHȞG/} ^:zދA/}@/^:z1:BI&/3DB/=A/==B/=}B/}^zn01wgK}A()ڈ}AK_ty>{)t<գXԜB +~zxyhTl?iͣIJ>( U__ /bcq߸cF CX '1ѣG&>owwus+g_%j_^>*_;#?^;j^hЇƠ;o8:$ ;aYQ3#yȰi7h2j ·4_G4a87F~TqqbGtebSYk:Í s[SS"i1)?=1y ?cFŏCp *k**j9!l9 ێzK\XbGs=^W1cs=a '=1c-1\#zQψm/r>mEEi1""\IװC[Cf! zlS1U r#Q*_tԘgC\݋2X?$+8fdJW)i>>C=Ewr&hRw.ƽWji/R;hB83/Rg#}sɍF./6~|zF?+._>-zuϟI+_+\1=WuFx.Ͷ}[-"߫nW޾[sEl=P]M#]rD++|9<m*y[VOhSv!Bj q,D1oRb{fd;޿V'W}]N|Jgz6wzWsgw,Bx.Y|AiMƯPznך~MmnTsj:nm}U%BjI3Z\R$R6QMrbToMN69^nL7g\ܗp26cޤN;_eO1znRיL'u6h7g|]IeiBcn|@Z|.o|q@BР;` 6Zc6$]u}ڰ$uuڈȰyڨ*BGDt ᾍ+}|6Bd |G0ŭ5ӡjZT>5zש@ HA* ⰬeⰬiˊqXe`e8,ò8,,]tⰬ˪,(CRI ekVbqX^Ⱜ˚㰬˚㰬 qXV_eEXFz>*,epXVuB5<\Ӎ8m0m|'MS4p 0v1؎6:l"`=^`{5 W1N`t G cq@H h H8"@1 ihKіNC[zv m.ЖG'hKhK/ЖvVіtK]ЖjB[Q?ЖjB[aқMhK-ȄhKA[l-$і^O6sЖdB[z|ަgJન]rAW%;nTpH !.0""L)K+ Dz0kOBSLe\JsY{J`~{s96s#9~C\'0q.95LoKWwp.ĹsլK8ʹt}s\isO8sSôȹt&0=r.s:Ks.=Lsisw%ΥZ9~F\s9irh>WΥYΥ?r.B\OisΥ8s.ĩsU:#8isg8c͹4jΥ8es.xũs6&~9(vp?%.99.y{f{ЗnEgGgaHaMA)2&%w?|:on~3>\8sɹJG}g.]: _Z0W۰*=>9]朠Mk'miN:_жzڵ .yAkAtv⩇NT<E;⥪ڥ+"+LjWr'U?f~iC9MMw.k?_\*Բj6ZJOnin1£Z]FQz6-@|Z1e2&_M!N`N+@|W;yY\KJG| 4@'P|%P@ lNG h|O ^.H~e An aQk;5 }t4>bCFa4]#pM\>\459C:b3FZbq ]#=]#M8G|}-G5=h}D3XH~zcQ4G'_5k}׸h}\t>\FZd1GOH#kK5Ҧ7]{=$tt38wk`{qN Ř4֞9[^ɏ:#קo.s|ј;?n/~q^<K O{[I[W?jY9P/5M[>n5W.9[cYWjcݮ=Os_ʞMdžYsE웦86Go־dR]{vˎ2]s5f|-nп_l?k쯸Xڟgy3KJyY#C3c%=|8td]xSұЖU=*'>,s8WrS_Oo[mِ#]G6Z%rFHr5-ӥ rŵJK2ן+ό3UBj}=[U|ȨSYQCRrç%ݧ:wIu)[\SMF)} /o1wyIoR]ã#G{Gnv2Po5Cn)H] yM*gא +ohɒ˺AA~޶hmv:,~ݒB0m1Sl*wB.,6B[Cs5:+G]N7ԻΟ)w"u0B1lSJK5۽'[zA_]w7oQ?Y}ع}3c4yh|w)-M N=*K; e.H KÏFimuNn$.ѓhHNHHSɩAsԼrZ4)-m9r?i|m 剙uIɯXho|X^Wϴu-$.Ck!rwtݩڅaDDGe>Q{'#e@ 1"IyDɒR0);ҤIY"'e@%bD"ݲ@P1"bDbň #,ݵ@δҥK(/E `J)+"ȘR dJ)+bșR hJ)+ȚR lJjʐVM]չjUSufp\5ܩZ ޣf\55U⪩2WMJ\5]檩WMy\5T⪩tWM4jUS :R0IpW:WM\5F窩WMH\5G檩WMjjUSsdJj*M檩WMjjUSmesTUSmtjmpT *йj*઩:WMWMK\5u])>I*u&*z[*WQ=-s[:WQ\E5M* zB*8t 5UTtUQ$UTWQk(¤UT%:%s.j9]dUTo,1x1lUT[ v^ҹ]: %WQmMs;2WQ\E5C*Hd +]ʋoWQ]x-ŷ/57x-K77^t͇ˋo\E* ȼfy ̋oU'f^|8A[*w^D ݏg,L~4/"D/"/"UT$]gagw x '~v?x 9WQ=*:s ꡳJ*sd.]gA0я΂p`-wUTue{(&UTWQ~0^Q`xE/`ƒ+ v?(D` a„ݏpFbr*;P(ePSU8rۜ<~?=bƌ{o~_~>_Jל_;>,,LqjSH@95gLԜ$Q{X%OqMJQ뿁޿[ tdTߌwt[qG䷟:/{V)-X~f\92Vڤ=]|U-k<*J$g1k֕Xmoz֖ƆY+tyƤWM&#R-^*ֳ[/?.}fm%Z>)ւq?Nk\W_̫k6[?ɸo\PƺEƝtI˿ \I˿E7`N 395r+/y9]$^y9]#r0./_y9ؠk`^&y9:/5x9x ^~E/<9or[<ǩ8"X&F/L s]]&21e߻8vpsN>8x;+ſ rB|]Gg%Q$Q}9H]s ϙk> ɐ. L>3r*`'5uqy/K2i/iTܽ<8ʍMPo+{h#-z[] H^*tO?{˾0Q^l/oH稰;Qq>K%Vu3?VIR5aZɖᓬ<)Yk4hYVZͫ wjiiTVVo ULme?e b#]T?dhUt}Q;5;)ͳ6h-rW[f,]zMzJ3϶g:׻kY ,^U ՟֟|Vk[V|L)7O$:LXCr9kbhfsx1|;.]oԺ}vcM?5bӚuN[gxWj]{eR#5עQ߇[S?o8`wA3muH/OnƝ~YWwh e K?#1t4G1'jĤ@<8:99!~՜BL)xDK RMEKiYj_VgמMQ'LZM̌R' M~-HsK{re{AAa]s@LM5\bwz]'0! LuG$B'kΓ |uJJGsEϕRhd~ϗT ~ @'PrpbD+u1]QNS.م?Ŝspri:),pbD7)ܺQi .Fs2iJiDy kdNW:R+dJW(]ft؁ӕ*+̜lkd NW\7s!n UnLAYte*+ 5NWUNW8]#p2y8]5sJgF&lOt7tZFteIӕUNWvhrʓ*+͜&rxnكFӕ4ӕ5NWsMOOjv^-+s>/ꑍsϩ? ?ۣKrϺ>nQ,>\x$6nJ=C}7*<0qQWk3ՠqBC{V'\6ԶIܱqx>9MtSydH {DSX I|R ˆ7KQG%p( "Td7LfR P*‛JEpcn.q "F 3")%?|Mw;2f|oyu/{xNV+kS+ 8a>~` ӕcǣMrg6g L\fJ.]V*RzVrU)kγ}_)/m.\l 6O).4UZ O*Ϝ*TI3UM 5U,.pnk{ D,3D.{l$(&LO@Dgȧp 3S8n0gžB{!gϐ9=C>=C>={S8!+S8)O@`O@`̧p W^a]d d d y ӰW(+|ZaO@aO@bO@&>B>K.+*L^!^!^! ^<{^D{/+)lyp2W8S`-kd< +LWh+oc^a;6\+g҆L.qd+.qْK+|`p{+`W^a^"{e +l%`Z ]K+ 7qiC^as66+t3W҆& 1+mbp8CWK+%+mc^X{-d WXQ6okNqx\gS"g2cѕפ6&{M;>.0C2Bs^1_$̈Fsmݾ!;ݚ lXXΩWmȬXˬX7Y—؊]>](.qc%t sm{Net_ wȸQť|NUߩ& S;NC?bxBbL|a9,N_5e5ODIw#eV2O7~&f;\)E mz%!TC[xҶjbf(=duߝ'ٸZw]m8^}_hFo)G%OPg\IOGtENgkˌң+Z&͚ g?;:$%K{7˕/[殽T{gId;pTD5cQ ٸǏo{;`3g}_lnvD*,-gV:v3k5Lb\LE/r3cfni|sf>me7+̅v3f>`758%v3CfΕt.nf̦Vv3IfޭFmV38w6jchAyJ2 Cb{ue/~13Y-{XPYޙtA0mѳ ?|vC2ga+!޽"RXnDǎE2 ~5Zd_d] IKKLH+w;_/uptG_9k:rY5];朹WpWM~uZ,;Ȁu?)9eؖġq>޷t&M\mK@E(OFLu@C'oB^عףʽ z&q8½{=rȽ[ףʽ[۹c{=Vpc)ͥLn0z|F 7W[M/07#>Cp/;=bGōt5;xfǍ=:Z>ɛjer57В7=knJ4lO\Bܳg9+ʒ2S,מ{N|Kݴzj'{P5_4Re2&tc~#geǙ7{h-S2m`?kg7370ß23/0w03ûٙG--4f\2^3×јYCV2χ-eC ~͠u~N'c/ߦ30קPg #G8=q(z% 70sAY)Uxg>.%Kl*rk |E*6o֖+}ŝ^]K7o3_蜲~/t]w~~N?(-/-9gmQNe+/]Mm+Cl[}fkeҒM)F)*QFՄ(SAF íjx2h(,wTDMڇl<(]~VoYLuܠ9SF9]M6y4bxy5y7wrw]:{FS>{E [9/\ړvuudO4]&]n:Nwj7(uR%KܭRa*^2_ML۬t* 4o&qS=;ճS=?_KTlU Mx"psOq?99>3Ryݢ=g$/EyWj+Zݜ7/O!xyosAn|fhc9L8kW5u]gOTׯ}Hڔ\󣾥IckliZ']wH_.q#Y9Cd;kB)˴{>]5߬|kj:rpI-/krhͦg$(؎L|l;:g۱.U:{ol!:tzOo ݶp6. hIӲ"]PkMx2ZMDh"DF|ZaħF|z bħ$F|Ҳy1#0h"GM02h"A)HaD ##8b0##80QGEMqDaQGewcFM+{u:"J:_?rwLIZ nbrss?11 Igc?IKLIHꔔ4$n?ߟ\)~z7^0>~ F[@A̢T^Վ"]tyg|~C [>{\yp!q#3ݦ)ΚmK#e'k?-2]O{^Dv_SK#߅Rj~ S8Z<~W8Y]<|Kmx}C2¾%eB%/ g*b1BՄdZ>BHV#bkJ+H /1 CeqvBg/1zU.By:wBuˆԑ+^YM|w4:W eLoĔ ZkTBybZaBb.OOj!LxIuⲊ¤_4w-{A6y,¼* |3\<ˊ[Ǿm7f0aEID7u#u5@`u5@"i \}U^:W׹^y'oZy˗O׈`^ #"##"##"##"d>""h6d6b jd2bȈV`#\ FlszF`#vFY.x`#vFl ,FM`# hQ؈];Ia#vF;Ve#F"6bS( AtLD؈6bl~NNl~ {Ee#*K6bT6bS؈UU6bG5dȀFFF,0ndÑ ,G.0vd GF,0~4bD$#!Ɉf$#ؑX`H2b%Ɉ$#ؒX`L2b5Ɉ$#6 AɈ%#)U1" JF,0,dӒ lKF,0.dN#}Ɉ}D&3lL,02E`e2`); MF,4Hdk2bёddEݽ͏6 1??7>Xn&gVm|o]0t煦vmиmǵ`rYWZdYS2qog^#p|Usmoj%+ZvSe^c/%6J?a)cr>졳ZvkWmju{/k7yFk-j`qVgc5,"N.iTҠUsÐ%F5MJ59o-vMf߼ko[N=:wkPlaH vߘ,]-2-ejAJ̭N4~n0[|mB0lZWU_BWXB.ZhW,gKΥZ?M2wmiҺ-om龭u=bG>{ ֫ϖȇr-}FjإT_K)]l{^`'𙛂QV7^2uo܈ ?n.o`9v( #k87C#s81pLjC#{*O"?tEA *~hPU#B#Ýv26:c|0"8pkEFF0vvvvvvvvvqaHHH I`III J`JJJ K`KKK L`LLL M`MM N`NNN O`O@D;D;Pv@$ځH%hZ @D;Pv @$ځ.Heh A胦w@#$ځJHhJ Z( ChD;P vH9$ځvh!C(D;v"@G4%hZ2y@[ޅ_Q w]/~_|+x] ':JWuu\yosS']f8\L+)֒K03yivyaO֞錓R.Y + !%z=ӥ2aZpB‚SSnh4ZB?"&ls)ŸLs.O9|p h@n͵p;ؐT'nbO;{U-7;mSLE&?7+=zΙ=_yNlvMk>Gma_mnQu٫ٖn]y\| "~7b=saj}gui-/a_7[ݣv~b̝.CBjhЏ_î'v?Mjv 4VFh}*SljmFv4G>d]9z5]~ f~S8e䝥ƌxIRYUv ^kGWK6/iNxc8!U5̣yZkk(&%SLi_KٷLLuTKݴ]w-CKธ~:K}r CFߙlEq1fSqؼ鶛wWݗͻy.""!@E@ep:Gi"@ EBNHi "@%ELNJfbD-E7cZPLi "@3zhJD6KZ"""@ܢW74)9n̐1cc 0;+{@vèٺը=Rͯz_ԇP)SyBReJU%?_ }^M"# E ۆZ=&j;)?)Mox}Xi|eŢJW+Nۮx|c* >ɫv^s:o >iBBPW1BJ6F+m[ZylzPZx*QJtB7U?5* P+ WM ~KAWxRcaJlׅv)O[QsaדteHemQϴS-$ = O<"+"lR+!%%ͱEH?O+<:e뛔LJ_L~qaLn3a&kR?SP7?(-dq\Xx&40ɰMF5o21N90ɰN5y2aܓa cRfRfRfRfRf Rd 2dTFHN(VȰ2̐2ܐ222 22,mH j("ȰqIG)2Et*hF rIkE2q*V!6ʤ6]#FINJ J0K pKK K 0L pL Lx&e*U&q\eJU&\eҙ>W4k2qkRf̀H/2n e@ 8UW"* Rd;$"rWDRD.* pIoFn*F*G("א"i8LC "p)3!RfÈH'2^"eU& ~r{q۟?4.'._߰99ib? K8_矐2[|oqr7>m֍ PoYKMMP`q3LDCbMR1j鞮JղSJ:/K7Wj>XuHZwE\vATmsF͍|!6~C#wPkvRgmZmǚ~jĀ5뜶j϶ZԺjop-2ʤF^kEQ3G-63?,{fq3Yb3D)-fꐖ_jCVc4-.4^;L6WFipSGch&JcN*IQfyqt@MsrCS9%bRVCSӲ/X=N@N4EZ SK?{/]}|/(⁓,Z,R":E6UԠu@޿bh_ܺۍ9AEK7^7)q ׹]V-h&TfO7 "H7** eQ'Nk)N~IVTTez|JhP˾(ZeHlU@p (eH`5o$ J\*D` B$JD("Dk%}#0˾X(9 %Z-.F¡ H"'Z\ĉ}#qeHHhq7'ZӿʼntDEV0}#aQE, /@`hQDF%Z@h4QF%Z@phQΕlHvq%E >J(R$H!%Z@hMJ6$EJb ɑ- HJ$)D Ȓ- LJ4)J6$O; IRdC2D - UJX)rD - YJh)D:W!R ȗbW! S"D Ș dCRDkW!9S5+ِMԙ)_>L{CZsD}o_y+WN+oGSwIN72nTzwI8@o9b7^o!\tZF|u3t{՚ޞvۢމGlﶸ/4zu=Ai>G휺c);1` wnjT2`MwwԎM Ux`O?vl얕}d֮27^VÞ1MU w-~ jm13?l˅Kh7#4a82>LpW]J$kþ +[}'sXmٚ} /%QayYQчfFAчÎL]xհEc5rR9x+]>Y@{<{j٧g6d]>X aD_Sw}_*yD_)}rܫǛd_m{=\v~f܂Nfu#ĸ%aѥX.k^VN00+b1:!]l7{k+-I^]y5Jovj}gWpХG[Ap[jVOѵ熹&O._]o0q쮋 mgņϾظtI=u*GEsw0NɽøkChPq 0p׆ܵ28P5C ڐ]> *]㜄%87a #QqJF+d9Ds;!tU2\F0ц7*'h7.A8q *5{lo-kޚ>܋loc{Kf{lzEVb.[=޲沽O.[ճުF8w'[%[ T`^%[ V`~e[k>]<˶m-+f_d[K:ʶ؋lku<ʶ)lk5:ʶVJ Ze¶VXR*˶V-ƹm\?y,Z5.rt7Z/5\?~lk=G85q~g[+qg[(ZSR˶̱lkU%mqz(Z}/%e[ ]6`[+:mJlkNa[-m z>֪Eר@z4ElkJר'ZNuZZcRsz?aIb<?|G%$KLy DNcp/Mj8ט5iy'T3/m7]vF1F/5n2^邂7ߧ[8NZÓ S]_'*%ޚT:bN }R+˶:<=h=zrLǘRj3ԼRZִWݬ3IO>j041SNF<^w((0ȁ>ď;>qt>*8~0ˆ#%80ˆ #-/808Èc #780Èc#?018Ĉc#G8&1Ĉc#O8FGabı+?0f1⸥c#_018c#g81ƈc7Fqcı;F`S?EFEFFFFFFF =| F F F FFEFEFEFFFFFFFFFFFE F!E"F#E$F%F&F'F(F)F*cwI󿟏79?(sYԷ?c\7{U77\fKӍrTJ.R9q\^X$ޖ]Cr}onxiz%OOgU Fí*x>dh),wڨU!yWӒSr˿-\.&R>G×7lYt{#7;7Ϛ!ȉZf$ȏ &uͳkHO74u@hndIe݌cg am 3ru6Y!%iadc+r)+U $\XlacM)b@w:Գk1{e9Oy}%#6=z9obߢ޳iY#As=t*9fiH9R[%ŝ'{Tw\֕+>*H\'7Y4WS}SHyiRZr~'sJ&3J˓_3I辽|ɖW&-/L&DqrSտi>;0cIj*)Zbx$,IRVd0"(?aD(,Arvz~L1"T1"\)d1"l1"t1"|1"1")OY™Rz4kmoqsuwy{3dKeҲҲ2`iyEbin< ln̢k YbeeҦt,-,-W,-'K,-_YZH,-SeXbհ(e@=,-,-g,--K2KKtKfiyLbiYYfi,1XZnc r2`irrrҲrrrҲ2Mfi(ee@},-/ diWbi`iyNginYZ2>YZ2>&YZn2XZdi9%f铥%i%锥%ie8]# q0|lLHC%:OJNJLI*kw?qo|ۉa9[^ɏ:o,J>K,4i&D{ =ŝO?]򇫽^?z$YR_>"r*z@uyGE62qUc=ҺR?v?=~Cڲ0kn_4ؘiDcsD%v+Z}YzvAr|5gQ+A}o_oۿޑkzھzcoe{)ҁ%S򡙱]>Y:2K.ttPe{OWXhK*MO9g+c9Ʃ觷6lȑ.# VKfO+]H#_.&ܾtɫ|8Jm+W.GWѯ6g3ݍ 0.c9$5ory߳oS!oy/(XM@]Fuv+|( t;yfih̛5c i&"mR͚@h H5 i&$m͚@0i&&E Nڬ I5@i&(m"͚@Y6kfM Vڬ J5`i&,m͚@Y6kRM@i&0m"M@ƴI6i)&M fڤ L4i&4mM@ִI6iiSBV@T`DV;m;X=\`HHi&=oƜ)f̩o!fX7cvy3fg7czɼGhWmW7c4x3f *5N]D^9xѹk1s$.#fo\%f̹oƜ#ft7cɼě1ɼsΛ1ʼΛ1 ތF͘ ތYG͘o,GNNNqr:WN\;WNr yaq_ġIcMx>;Ϲ?YEuՕ}sR+_(sNZ<)|#v ww%pQUrqEdQA +2{2wMfe6Y4erP[ ?Ç9 |{ι#˖+G6CP0)uSOtRR=_9;]xƽ%?lں{?;x~o!ş^<^r9ˢ^@az-lr{|FͲś?O:_}(Uwgg?|~uYݏt~_}_u9^C mj+O]g6++FlpQװ&Q=:&DNj}E-+զ)^^QqU+u(~:ΥD]%L.|YiŎۺ?C5Ѝ^yA4d}O\EHy^VըJ}h}ku11gڝo/]ďyUm.Nsj|1)GT:n&t$vU+'[~uOѵbωזYbt}jW^_%|55z~RIQUx:=ekAh]wŬ:Ӌ8$nhGjuʩ9%jnJ/ԑ?OTF{_}2fK mqg7Q#C ySA,:=NI,|R#%J>,Q;4'/QYSՔ:wL9IY}:wN)`1B~}_dW/W8U~z}7N -Utw9oANCOW.r@x"UޱQ  U')O>nW9(r@?A}TmȐȔ?!R@K=0/7V9?p@~3##s@ wg ྑsHdio$5܇Y*;<,=D6瀞F"s@M'8?p@}#ɞ澑sHd a]*/r@LသF=Y}:L0cp@?3}{3;$qw&Q8oݙ`ff[_.M0p@3Oܝ fqw&8_ݙ`&~wgܝ f&_L0Cq@?3L}7*-;\}[{ܝ f2 a6>Z]Δxٍ@+rПq@4c=g?8 r@xِz+ΊS9ok;M?LNr_b~14-#?75=#;5=k|E?=/`yu9j`~{^if{(og81.[rZL1|ܻL Yp]EP_S0ow3}DYÁHs3G)C|ВG‘#P$رG<< q?#33ÔV _@_?Ū}cc58|qކ>aHm)aweMMwl꾰}^8$oKi,2ttHش&m^q@'4NWqw\n; 잟m?:.;v~6KlW|nTn',#9Mu&I}(FpwpoimA]QprW(1Pr\*h-6eu_D$l+r -J{[eA|}PeZPud*hDiѠтA5m ,JU61wRMx9?n+˩ z}^@g VkAW)ȍZ-79U>5olu'{\7Y쓖:$wYpo9Lm!|.蠯Ai6 !ߖC7 6gqܺ|9"e>K(GEv4E]^f-ze:bV c;K;?f~ԡgZUSǐP+ε ]n;ȉL'@%mG }qm='n1;e1h6)#9yHS_rPSt9ͽ)vrFS{MN# mp'hlYA#|lp"۰j{@Le=//4Rέ9ٔeg |e^{[e.>l\{[&\Cf>9M8܍8 8m,p,p%]:M]:B@7ew9Vp ˼b']wW8U5s 9e!Usk9!sIpᮿnnu]^Atn odwy\y.+6{g7q;RWoⰷaoU2ܽbⰷaA^ x/L3qal{G8],paG2#M.9awao(i:+{x/L[0q#{6{={q˸)W2nqjwa^{qS[8ea/@{8rU氷Iω4'8?-:_jmv9P2۪:or>Z/E_];>5vLd-scG5o~몮'N҅C7OJ)k3Gg:k^ma9"u5{9*ru;}l]1GbV/ҷ;S;Cs\5~su='C̝rεc pӜp\M؁dU&z.v @YsʪU[fVu7YտcU?OϪzYϪU}_=(H=dX#꾵}CY O}s=U.fYݟѱgu1=\`u?CսI~tfV* fVFսgu﫲VŬUV)zVTVm\zZ@6=t\jdfu?Gbu?Gz.3UV+̬GsQ HUl.jYE- -V=u?4UY/0﮲dfUO3s1MeUCHp=ͬsQ ȎU=.j鱪XcUAϪ~UUϪ~U93*fVTVf34.jY&zO.jyeU?LŬ&:V] U=Y[$XVWqQ U 3z3|Yտg"g"!̪>X#'I-A FЅK[jsUH5b`fF "U`y[h/mkXVYr^4@Uay;EfyJay!kWX-IF xm ?,o,o+ ,o&8+pv^-^byUay#}[ay$m!MUX7x xkx&ȟ~(@= ǯDԑk[OA~~v_Tg^JHwv)}3[esu"P W 0 ).+V$Lfin@s3x/4/ydrNưbo>v?!/wMZ1nn/{G!{&m׈G"? )aRx3)7w^AN?|pVfvd9^"?ʻ߿)G>/IC uJ*;x.@2'])sa:~ Q~^{wQ'2CyI?;(C'-]3k6N+뵞#QhPo0 Q>{+ɍ|fE̞B٭ ٧Ww7q" nr`vBAT|rM[!Bߪ 6lH]$ʭVɑQn36BtBBӯ1_M}2\]CG١\+|PB%<4O*ɋ{z{OAbX‚7Eѣgv.ZFHс-[z0}4Td鍂7=+ɮhC^T|=@z/UxT!PPB@ U-T!ٓ5T!)3"*"Bh +-.Ud\)tQF@UiT!QGIȣ *@T!$RgTp3"*Ny\T!J _Y'|!aY>E>~)?}S~[H4U3»&]pv9Nx`Q>4%ǿ_ÝO[P׬:㔤U'WG$}'ҺeK.6|>}ù~%C78m^q̖?Ӷ8tƲ`ag>0C3ƝI%ΦYc~\;Z4nk}UwTaVEgOҪUMӞr==}V=S~zTn['CyI]ƒV^Z%]R]yTO_N[kPGa3Q֚{RJkMX"y[_j4yG %%xɧݧob]?0V pzA ^^]{^k~'Z˭W:hlRa>Zظg{)u^hY+Eݺ"]>Ik{4D*MkM]YZǥ#*jwRSMl %4HR炔Xzx*u=5YK:J#]uIRϥf׋iR/k}b$)=[K)m(xk.Jt\^K)HJ꼖4`@-iЬ`刔9-˴U2IfiHPҰ曵l]4[Z(xFK_FZQF/ +VSl4>V(Xt7`uiY*5NVSwOtʣQo>, _>$>4>D>T>dl@-—>>i>>io7vĩ>ԩ>,@[) 4A"2>8(;qP@̢qP6I㠌 !UpP݄28(nBHd>8(kh&lN8( qP݄Z8(nBH1nBH5pP,e@={ @Cm{#ʖ=e"=qP]g8(3HWeA,8({1*E:wAZ㠬?7 ʀ((l/#qP,eu鏃2:4H]:tHAQ:Hq wAzDifrJ ]ED-'(-(JqEJE-+πb)4--Z\HhzB)-0ZbHd"-EjF)-4ZjHh"mS-7ZpHhI9EJ' -;ZxHh"ݣEG_NU/_Rpy?s1yž_ryċy״k2TOo\a/|Mww2ܝ7PfA;sYj &sҽ,5$k 8 6|P%g n:/ %uJ5Ҡ}`Ҩ sK?[ܿ~[q}GMZlg175ݷY/O12JЯXC@H}` BeCex笾-!҇.hi5`>lgՖ~m_D>5ud2}O9MYv{ seZ:]a6qbyйPsBC.! J,U/d-?FV\ezBּW*W?U)M)>q!T=S_<{Ԧ&dUh< Yp:܆αסD?%(nD~t%|;(e!V8"mEvC7#F ܍4K|*i4p=h~IFk;IKFd;=IF7e;]Ix趤ўx辤MI+FwWsܚ4ZyAM \9YpuhIۓF'O @ `@ @4@44@,@,@44@44@44@`Dm.LE8HH^}s-wY`:49]o_WςהXZN\ηu+?{s>ƬA0?P?#F<+ + 43Qw?oW^ ?ah֠Aŀ__"?`D]m1i%*S%11)1bb q*3헿t'(>tt$E+]jW6V땤jmĤ*ݮz]Qzp{U^kΉ_-J}c*}֋zRR.P]*JڙYJJΑʀ# =A;*;YuqHqh071;8 xWSrlJו\6q䅯Q1]2nBe -:RS+븺$Dp>e k40pm@#x6$Ey6(E0da`)z]A7hS>7hS>7hP@pTY*cP*B|: UXYCpPe,Dp"#Q*+PTY" Ep.Q*,hTag 8UXGp>R*,Tag$n 8%YpLs 8)UXQJpXR*,NpW|q#{@3Ox=]٧eg |2}2IBd";tkw^yI??q>~?8qYi#SXh?8yq?!?Zڹ>^u:VbY e˗E =C{|_['*^L[GOIKkM]Zn۫T=T|MZO}(RZTMWsݦ+b1ψACź=!Qbbb 5Ewq@u2皟 Pf~4❽IѧZF (wzM p}S ]f*Gr#Ė}Ѝ-VÕ9CJ=N8cQ±?jAK%~WN 0E_}c hz 0" P5')+-/13Z# P~B7'~B;'CCD0DP~CG7DM>4#DwAEEEȢEآEEEE(E8Ӡ"""""""""""E@T@gH h"5Ez@Al4-Z Hh:"} A4B- ZH+hZ"EA4|=cy ^/e${?\lSR3v񟯟={e1;ut4ꑭ=W,# >g_S/xF_5N߮]pQ_}Z*Fٕ7N*5^u+Cuٔc<:76=RgL nnږzq-[޵4hiYcq/u>~xbɶi24]5sI>%elDK%N%إ!-ŦۖB2~pj-C # ,dv 뫤 m@1Tޙx I VS›/]/NwN!6)Ć>0da(ĆFBls,bY8NpcCl*!6oQۓ Q] (z?*8ZYy0ּEGC-ZphmкCD >l 8no¡+8jкY K(O6H$5XeI[=<36~VύZwZDkrZ'HEOJ7#/CP h$:LF"5߷UjQo귥/KLZMȖfw kZ븡ZDH?-iE׭E85b>;+J~}zIyCjl!iuocgNk.O?#%%ySK%u]9Y7Dfk{lCϜԫ.! x6x;ZIZVt4!M>S/F̨eWrrwr+4rF Z4 Ҙ׮Jc:ir< ԧ$pa%J>05N3l9#kIݖf[p]JԃE& nLt L-4ZtkhѽѢE7\&Wpw\ir\&Wp\4G( p A* d"G$ PIB,@&Y Mb$>K!D,&YM'dR4"Ȟ4b4j4h]g hg hg i(R:@HR`Ih>;I0% P5Y_l) %6R Lj1ggW)O & Ц<5,@sW)_ p|5@{W)_ |5P嫁(_ T@jW%Ph@ z Ayj @>g ePc> WP Wl B kg PTCs|vR?3z(_ Cj Wh n|}2>L䔑CKkY)\$:/wvtK5w}eۛkxI_Pmf] .I?%-%=U3TN^-ߑxΑL+Z_y]~qxGsm^ZӅ5aZz;Oj%%$R)R -R󛇵J-٬,ڦ~jb-g4CT-|a.xLxpz|/y]m< g\ra 2ǘ ͜ex[vxSƜ e'q}L $2O,;g,XB+d_Hϐ4GC/DGucAYg >F#FȇOB#!~G|M>< }7|x"g$I:I蛜uG})XEI\Ów߈-0˲ _悾/ >_W?$;yx|}1b'Q_ugjk1\ڿz_Vkp\밻y˦ˇ[]Mj3U7n&zq.! 9sl%z.\i9l+FF}ɧ w4/T; \jZ-e|$Pk~+Njp3)dqQZM!qI2ސ'VZdzK^:З,WSy*l{A(x.= p5:am r^󎪂g?Sn}"eK| 9O#%z9_.EV.2p^݁ M\dp!+O8nB> .OD-FWyڟoi#O3kgg$Qys?;7RCG[Bwe pMYﴱ46ba1d1tc cao|o4h (X[R9P@Sl`؇`/ h\^qLT+xMOl===vK*~A<>?!x!UWn=%5.qWVZG:^g7TX' ?W)<sV5_ƿ>gӋ{ )0 ='矘['XfeK3GwbuټPOs5Z1nPVgV/V?Ykд֨yݱ~-W8gaz9)Be1do>e_c`}^NJ.ߐO[Ȓ[_%,Y >)zPUK0 !'{Q#Gz}$GqT&D V3B+jP9 qk)< j+i?i  0")`TXSDXSBs>i %nG5QcMpXS8QdMp4YSpG4,}`t)8Fxi0SN8Fx#(F/8އ{>7G ,xrOdsdc(8~|F!8>EIhћТG YB#lz𿟽[ZKoK=L6n>ڷ/Hrc~}__ żO| ɻW#OSXu P㦃YRpy$˦'h:I;Shu]H(D6Z(q(G#D_8$Ĕ\-@hwU!v\!nJ9~8W`>\uiPWsPo9!ܥANl"'^''U *%w,t_wJS-)ZsT=gE|"$VLkRy!5T]">+&I3vi#\/O<[\%dWM݅! CBvԏp]Fαr+ Z #/|.2F~Oyq#_"ZV*U?@Q7 xŁVcz1*|M'@0E@"FG G@'EEГE@$$H@B>$| IEZ$ | iE@A-Z$H&hP"- >`"(hd$| м ă$@@@" D$#HH@RBĄ -$QzI<6?'%+Q0? mc)tc_IO؏rK {1txPL *I yt~ڂEakkK[6-qLIڙ+^Ƴ'= Y/& 0f 8R8p4hGuԧ֬im됎UG(ux@ivĘc[Vj̵qy6zgKcWǍ%m,}@tՖe^^_cGhEEEEĠEԠEEE$4Qπ(:PEG=g@ eBEQ 4R;G` B)R{w*"Z)R K/_@0b |)DS @MM '@SQ3?X7GTنc>Yc>هc>YcI؋Fc>ىc>YA>يc>Yc`.W[90ީq`c>ٌc>Yc>ٍ%80na@@w.# R`; 90G)0flAX[edMGkO0r`<[8ȁ(h_~pf.ٟ 1,}`jqz"S8ycKA&ysZlM < Y6>g}Ǚ|%f&*Rh pYj"yr7Йjx%,lHIV"֑J#9AC%5BK7DdS1jדnغo"s^/]mDžd:ڤuum}[ԖzRZS'2vlWnx:LٲL}نt6{.6rnt#^~)Fb:zeӘٕԱSƍ֛ )Uhhhh42:"B"J"R"Z"b"j"r"z"P_ /WlW_fڐ̴w?{'SE(cgOyMwӧ*ɟns P1ޤ.׍I6N8 :MhX{Rͣ౷!Ȇ|ET80qH|S J2.01Z|d፣7DK|_8ӈɇ޼1ьI>/IO˒_XS\#lg ,6=+>9?q֠})]d}S\=v.,<|ȰXp+XݷaUh| /PpUh5R*_|<bPȸ؄?F¥7< =)hP L 7z"*~/?~+wM ,;D7uZncC)T'(uǢ5wF0oZ,[U-''xXXp~Ç}E{bBo_z7w3o&WoZl,/qK6-kN¥sMyWZ3]|Nzw{n|vF[GM!YmYM^b iM%Ƨ%c*J%=_ e*|b*۠\Mfg*V6U4L4;1S+ۅZj'ldrR_I.zPwZ-[ly[ LE&k=4ӊqgՌ< pccFK[}n p&Vc *tKc:Z ;:K-O,hl5)la`sχ.1Fz-1R&W;K1X| cRܧ#66N[j옰Y\^VC?՚xTuY}3+܇}+3rFC}W[VC?}Y}K+O2rƜ Okjw܇^3R5܇V0`Gn'KۆTmEۆ\mFۆdmFۅlhphtԹ AHAIAJ. hK0Z1Z2Z3M4& MK. @G`G;<}!t P HJH@2s\*BR|B@ $IPr=ոԂAHr>@T2 HddB%# *<B #@0T2dC%# H:dx"EdD%% $*))QIi$ @r"Г=#IQI # +*)aEҢR , -Z$5g"EC$G -o{Fң/o{FVU$HK-ɐJY@Tz=#1R) ȑJYsx3$Fg$K*eumHT#o{F%{=#2R Jy3+}$Y*eR)=o{F¥R.x3/g$a*aUmHTBRzLx34e+sa󿞨!9-1/ >= AB`@ v!ȿ/}m{/ʟ՛b8ӺlOx_#'%퀑$RPA[.-[#o+^Kxÿo_~v#K}.ח Fy&:/(߭p[ .+#'g>п!?}jvg/ulj >xi_Y?P '>/.̺$\46F6zRnaΦ+-WeVEv^pfiӍ>nhnXvZ;fkkg%~[~\duuO#n=RZ@:CPg ߤ "OP>Kx5}_D*TdrE2ߢ{D0rLE@3?؜&& G('%H'h''''' @J X0)%`RJ2)%`RJT "FnڃQAEܴi.!P1؄y(TVb^nރBy>SY"b`6ơyCE=` * QQzF\{,Z" LEEs|12V0 Wԣ{dBE=`BE=`BE=`8*Q1,q?@dBE=`BE=`BE=`C*#RQXzTv0$% - 5"sRQؓzT0)M U ] eLKbؖyduKطPQXPQؘzx +SQYoSsѲ/k5DmrfV1 'S6鑑/-t"nޖl|7'" w-eQ ,UD1."/>+( 7I (I%x$ 7ofDy}?7}fk^sgciϻj?{O֞zL_ղg왠M&롙 !ng왡b%ǹ!a*?|} +OڧgnK?͕5>x+hm>~*|áf |7J/k ϟY;|ң󝃏-=s;>8!#Dλ{SO-[z ?y~<77x*n8w.ns{6ؖ =.Wҫz\խ }zq?WK(߸qd[G,;[—]apo^Œz9.-YjC^?1G頒e(3!4\}tW1ݣʜU-敁3r+>8-6)G‚RNC0$ -II^0Ĕ9aFC-9Yv8daѢ E7} hТKE@݃2u"hMТEwA.C-YpB$uH+w" .ER܊.I]p/b$uHꂫw# .G-I\p?$u I+JpGY .I;+-񁮉pOEѢEWEJ.K)Rp[5uɂf) ݣ+EwF.Nm:#NJ~yޞ+T'HyוqϪ58:\^pCqJ~:=2{!s2{.p%W/l&pz׃7+S//z=T,Cs5! g9Wm5q9,{jHkO֑}(>.Ֆīϙ('d;i+ q`OyAWE됐zYZܿLeskS?n3v3XA%v=!vw:DtC)t;DRN'CDEK_ՀSr5˔\ 4q55j)WMqՀsnd)ݣsn9G>Fvs1@h0gh#&(7܈7#>87R'Pn¹7Pnù}t8ZGzQ}vTBTN~jk 5зx Ӻ47t!B?8k5.tu"mP#NEEqLEF_ƔNU۝0+ո]t3 _9PcRYC9GN0L NMI\ ho?2]g|2xТEG "?mxE~wE~"?De沈wSYW&N"'EqE>E6EZEw:{u,ѱXk:y=+pp(.㊸;. " {GA@KymJߺf~jSa.[-:if~ga^Bf1΀239sy9lϔ`?<(%~;$A)i$('m܂R2JJ(*% R2JK(.%ܠdĴxȔ 2S2ɣ]PiövA [wJNq N{x *=(|XC+>R?U_3o+k#~3~Zk6%/aI7EZƶMI͚I+N\g+&nl>Ű{j 5@EmW;E /=?t\ȅ ڤ`Ť SHjnS^J@*LƋrٶ˕rZG=em}RÍ *.e4[1TIk,ToRgfūlGkgjc3[ת|\pSIz :[6ԩ%4-)R|~}*7b1咽r9Sޖde],oF+7=9*`C޹rΎ>jhaYa=&rk^VR)9wG&H#jK]jtgX-buqKIOR)tx~k<Fz$K-z /k/0'vdyKvi/Q"D H@"5D :P[@(A%JD : A=hE D4Uh|@ڶmk@ڮAhCuh}h/ JD"4t Q>tnD۶qQmڶZ#jv- I@/Z(@0HD4CFln@F۹n((G< vs[D= HQHkwnȁ{o:RޒPv$1)pWe؏9`$sY}F88`熾+=="/1"0/P"SL; 3EZ9`OZRsnLj̀9pP<М)pdG%$ .xEo@h"g]vKP&v[vk.x=AAH]$ބv.rUBB&UBCv.xECy(PCHD$mv,v;.j^vS .Wu'WuL]V6~p]՘n.@w/2+ȕdw?VT_I7O6a/:Tvzv/MQN{kM kDکȆ}M)><1|(ES/%@xS}Fby'#WorN? د:Ȝr^ 5̵qyT0p֓R4CmuTF{x<6QD7XA[?JnFz7u382yT`@_.?x7tQ{5鋯[lV>\xqG >ڏ#|f͊}XD'o6uNNE8]Lgo+PΞn{X~1/;dޓkn 2CH)!JcWl?W:,6aw'.%RȘr疣zut)K0GAB̷?˱{j ]7wU!~#JݳQ ;9z$Vz{~$Ʒ{W{]Ӳ$ou_G߃FD*OvL41dae IuBF=[^R7[N9}FIGN۞8*/]ܑ5u,ؑӼui= 2c݅cA|TW2ߏ~w<ʓ{I0`[1+rÊ z jc;wN_Qt0ϺF# G '1`[$NIb8.0l s>0l sN0l s^,Ra.R90IcJ,a5ވ4n5Lj4Vc%$OiL!pFb_i\& 154C>=4D #1E$aL1M0>E #1/Ka"pF.fca0Lje0VLji0ҿGlc=F#pmd'w1b3G dB aMa̦Lj0Lj0cJf!f2L;aLj0&;RfcTbWI2#ca#2ø#24=Feq,&hÏ l?w^]H/%[X59+/XЩ'Zp>:5Gu3SEV\g԰Tcvqk\J=IEx͞/o[x KM@kͫt^T}#,T{ol(YYU;ToR g$mRyRøFWH[&5)5-Sjf:|s߃=,ͷ[do՜p{):{v]RߠV-) L+&}L ;.vJ }zlףvK|dt=l{ǩMoHF#)R&ȡRҭэ:KXRVRד)nu%)~#?7?Rk0fͳ aMr+^穲$*zݚ@Xqq=X??\?#Nw\J  *./IMS5.2|Vd '"3EfWf&y)#;h73f+$3чLrL00܉%X3ɍf[($GпC  =Y'q#Ӎ%Wuo3]lgdd3fO/P\_or ״jkVTkx-Xy p?TK,uwԛRbKvd]K- nXwX8ai}ҴVO :v]S_}s}/e.rK&/"[E7z}~%}%vKד,q{[$Ɲ}ҥ4d: 6vu;{m3 n]B:XdNh9T9["pH-p9ΩY8jᐮCDC:CX8jC:CMgy;y2qHD!K:8{B!]C~:X8˨pqeT<NOC:sgPJ @19;Gty:>q(w¡;:vX8{Qǡ7|FP>"s( Q9fPC9n !H=7>wIci3Jz_w@?$&7bSoHZ=OIwZ=k@(GY?kjA >nxL> z.ԭJ&_ͼ|'D]/n/A^hO^ASE16o["oKx,?,l:o5W=WFݐm!^֚Wνau䀵F:rUz3.r vʍگxKn=OnZnSj]=8|['K5鎖K:ZIp&Cu |䀰r`O9(}/vQ.=vPn9GGv;:,=۰qGWʝrÞ;7.;8c}'kqĸ?5RV#G'S H:$wέr^t"b_Fxo:ȼWE?wA S2%&0]BP1<~8aRj #s&_9IxB;  ?(q>7o|wU103f U9g1eŹ҆e, ,B\? +Tz=~|y|ܯIvNhfNw[aץ*솪 >6ToEQubiSZjoM7NY9^wp[clڑu(okܲyo)]ԏ}ۧkI-ch5"MlIc0[Ͳ'sFj"G Q>lvVGoFLi+vRZL;7V.u3; iK{KC'\GNcň)Ǹ;*xpPvr{f(>mf9.PMh3 6(h3 50@S#! B qk`0ĭ0606hHĭ1EZ<7-G"n 6ָI:I06~,$ 95CȜEbNEnȜZ9a2c4PԲ1*I0Vp$-qj`ĩL1qj`ĩ1M59>0lSgN[9"sjr #{ A0 I/slfzb)_/=jaOx>ʚwIF4`=hcK]Wߩ~[iT}/ ,U o*%5oﮪIVB!E?/7>R(Zo{7Vs[Oo=VKJ me ˶n g j =^NnoG;,pYq5}6#vnPڥd}t 㭱v=9zc= a/U@v}"51:VpZɞ5=b={9zS϶eeme=K=ܗ ͷ= o5JO Ns-(~o"PvOEw}Xdbs(. V98ePȡ@CVR r(kPCVXyC߭ \r(`r(ʡu=(@8`Cw/%%pEgG@sI]Wz}fXjxY/]:꣭ ȱm|txLG&޿LG-g\o?5߶Tg:6K|cg:&Kt#Y;n3 PR`{kR폶:, >jJ.JRĔNA?ꩱa\2_e5e5n,4 摤 [ a jb{o" ofn7r?Gxw{SZ=/cWU[ع_2shae[ @hk+|Vh; $> r?ֱS_-|aTBRE 6>4zz[Kԃ$> J|NN*ҿk'F+܍7fdbHH- !󿝌)Ogfd3!ߵ#H7l J.+9͸񼰡qK4XmE#7 n258PUJM0lXY"7Q DT௲`ľ&Y'_=J|q*NHՖ؀7-C7pF^Q' T u_WCv_'<Ø]|_+f2\_?nYIY]q$&B$:f?F5]-=;0/XYg;A&;"T=jBbx]tp;r?Uf~4I?g6&&UT  zI_OH23K.0 vYxqL?/V p= Vו]zb='<`W/ И7/?LԴ! ių;Z\TbX$Eh4u<:=k*Au)~W]C=+mS9"tXzmd OۀajPVVSڽT3UByjAO+PԪ{3x?,T&o݋X‰Z\~`tK\Tf9g sH1g@eTCʟ2$qj2g@3!0gP2&jϻ9?ʘnH*\\K{~_ejH34|XK5@&e4e>:CR꓈N7/|{[ۃ~> !+>rDKi'gc]翆'j }Ƚy'Mw_ǩzf{Ϟm#rwz~/?{~TG\bIm6Im-V%q+]T'h@h9׭2n'kF;6O3@J,P?%~>$3BJP?3a=|vgHX>>3RR!7̌6çk3Kf]]yM䩋7tSO;-5Dِ`1g 85se7U+}:Y{GRςfc5>k}s*e}s*e;h;P;,T$oMΩ2$O3äW&s։/20w{s׉wyrև;<9Ýc1Op'g}sׇz<2wNz *1e,xR*|2*y?\S)^|^l;%JTUC~ȫ֪fڱy͎worAVϻV]&3$Fg(0"Ͽc}&9냪5 .Vh}B=>bC}P)a^y;N>P"Y)u^ߚ슉9=4v{ [t^e'L_m|g 23^-$ ]`^>41x>V5ּKAƇj 7 Pf@p@mI& J4 M@03M@0s!AfM%J4!hF`J(ќPIB %J43hj(PɡDC% J4Ch( $IYriDD&%*J4Wh(lQD%jL%1J4eh(ѤˀYFM%9J4uh(Q٣DG插 @ܳ= >2!cƖLw]rG&rŶiT + ^Й ? }IB|:%&>_0?Cн)Ot{`SsJ~N/fpr?|ę2ouC``RkXOʗWkw_Q+#ʱ3736*7ԍ,2ivg,0Z tpf/pI`y&LR\:4/K& /M˧CS 2N[)KۡBw]:I#r2RI2?8(0؉ t1֟ޅ#ƌpB\M}v>Rc׹<֑$(\Fz \R>+\>f:|ohf>(pO?!L/S nrsۯYvX9ϫ-LsDbOS@ssTb}J>?O4?~:R{h߾!&hp |cv (7u j#imr3n͓SΜ4~BN۱`#DB; `3$n|!w!"|["w)"|"w1"Έ|[# l$`{D6H;!`D=6I;%`D?bϵ '[&j2-63t2fgO 3+G3jtݑǤx*n-9X2WtC+/B7׵0Yx }zn. 7ƽ/n)2Y.8ӸVg3w0&r J/8ȑY<lPP:;: C/ MOZ2 /_w]4&]<ԹJ4Sw =hu>QR q\{ͫ ^VJmI;*k'>0|Ja>x|rkEAQ V'\"?dWppR,3ȭIO[\dO8ǁ,:x ୔888&tJ!B0{J)LP ˕„.ݵ"xX"qg&vn BabPe+D+&ME1='{LXwEG/<[t{M4GͫJ^6ȵ.{oW So֛Y_?QT#. NTخ6^6-S36٩Zt Z1-[!2@l/DAVU D R /_z7C9Զʈ6P9+vXzA q~1|JQ19O9lڹbTLK~bt 5|nPc=v=ƭn.&<& hV[ rnNoLsl"ߙ~ 2v2x_$"7%aPe]eNe36V-tHDD#DD"0?ReUeDeߒ$**"=Fed@eVdAd=o²_g3T9*KPh**"oH=7\ON7f'ge z w*I_)۹ה6gawnY;uM],\mc//dO1r ~6 gl=ق&قm׻BB7#O[ZCe>3tawʠO1Euks Ql]J2D7Zf>n{[Ѷ= %ƻx0byۚpByɛlAD w1pUegnUXlUkkM6EwP;t(3bPm23P<cgw ;8;ȼhacfl;}_W׳Xb_!__د{ٯدIEzMEUzE;uGv $k?_%(:uAg~HQ jCGj=Ҙ6M1 !AAx#g?<$"?\OIπϿϼg~?iWL4Յ3˨"~VgP4v,(SN]qUԋoU;Ϛw{YK5)uCv.nZ0Aays>lj,׳ڟc=dfhoɞ]:XO7iXwl`k>s{;_?l^c/zPk|Ŋ3jDbvZ9G\Z-Pc誹F5ݏk^c:R[7YQ[w*mohO|Ah m?TۨC/m㖝Mio)s'sfCͷW7XsEmjj썪.i7蟵F %bP`\1$1{z(6VmQh_ò!j؇Ԏ"Ԉ)mN]JIb&ߪQ.wэϟcݷĮ'qW{6 \v 3'=N92Y2RMV{iEEE.y͕Ak~O$OO~ _6hTU19l:\VM)g_QSgiyԑ;FLajƲ^bfj}U$A^SWO̎*Tımc߈Qsmq- >T'է-Q'=?IňT{*s=}yϯ{"l f nᦹq]X¸t9Jc _ N -tsʉa*O/$Q":DD(%%(5Q"rDDJ(I@R(QQ"DdE%,JDZ(qIDE%0JDb h%2dF%4JDj(IjU@n M%8cy DGN%;I@x$Q"ړħ֢$z!L&Ԓ dO@mEP[^xj'f0L)hx f0נL9hxf0< ^oB< `B3 &.4 < `/C3 &64 <J:(D=JB(4\F(#DNs %z)PB %z-"sދ`$l јPحeBa UZ&2 E˄h- CL(2Й$z?"2U HkӂWd"D9#* U&6L$RykD&>PxYdBu \IPÇ 20VeB!RdB/WeBW%B- qPhK=/ }PVeBȄ.>\ޘ 3" [XgfBaȄ * 3E&c(kDύ7J༱M=9J$DN<;J(ãD/==J(D=?J(1@QJPb4#%F(1B@QJPb#5ȁM@#EH%F@D Y肤`# mĈ%F(1@ч ?;56 ~:*Q7NYr%`d_A3Bk=Ѿ'y/<$OG 9 ~R*c_k*٪WWV=^#w[f^ 4_k]77׆:nbUpAs驆Xhkޤd[s>͓m>|=m_"5b8g]  ~SaEO_J? A>uCHP=3'm۱j] 0񅏥m%RĔцNݧII ۤJQ Q٣[E6[!`{O?7$L$u[W}`dx鿆H=s4.%`HvZ4紛oN>v&Ny>h`gCۤeq V&iXJ!)9!x)ݐV!m(8ӐrKOOFhx{-.mt{V泶1س۲/ 5~5%iܕx7&|0~ғESK^h+/2!r"rQ?X]0C krg j<GOq/h?Ž`P @q/XŽ`UP Aq/XŽ`!P B]`5P Cq/XŽ`E%Q Dq/XŽ`UeQ Eq/XŽ`ekx,$XŽ`yQ Hq/X!Ž`5PER Vq}^ =Fkww5=FWhfcf{1Z5ǽG渷=F+,$X;Ż`$)' Oq/ ŽP h@^@{(t^@ c@ bԠx$Ž P HBq/ Ž04 Ž,P Cq/ Ž4V LD?&"ɽDro`"Q hDq/ Žcy޼Eqoo`"RQ[70HjQ E+WD0^(}ާ^(top0bF~fi0f+"u2x{"?-+5q0mՄ`ZjF߫5SkUk^ڤz|[Yu#b z3UħE,abDQ(q`Iiy/ѧtstP k3{}A"A-$= *FƉ?ޣr@''铜?]@H`WW_Υc林kdOK=o8w ЛD: G.7bA>-aUx3~쿋19+x- ,RvA1jI_W׍W14|P(c]*+spiysb+{4!3$%g< B5 _Z|fFʸdcxuO0&%e`"\_ v^٘Ch¦s_n[r'$l l v[*AyGe['ߌv5 xDZGsaKޭ}YǾ{Osqzvؖ]V?qlǁ}&&js_Z[t_zbsst>HwE-_ζ؟IcfhdP˩N-g|;[Zs{;rM'?~p\}6.,(:DStUtb\bGpL>86-w:ʿ-3 7odPrͽ K:(K>w$vhwi4ҼkkS2M̻Wv29Wwfcok?Yl?K?UZ|b>PϣF&_^}I{=r 5>=/Pբ׫kh+o5uD{tFi>[\ sϟ2ª=N2_xnr'O#h/Nl<$Bs37R5|~iK]WVgκ w/@_!^QN|Yl^x(D'yH[붷`QnW?n+:i˺ݛ[C7i]ʿk}RmZI:i>EWoKz)Ҡaj}K#_c,M<5=I߬kv-lt-gameYkXۨ&P߸%YkZ]-!GC>w86z"_Cx]rv'u2hsħοfJ]cru=m]w}2?VV D>◪+ J!^ýV=p/$pvAYh7ĥ&N8}Xra)臥`$JT AuT~P A5`Nw#qȨ&邪 B. /6 C.qF*ÝQ" E\.IP1tA͈U#Nԍ8]P9tA#Nԏ'7@C5$NT8]PGtA%$ Փ$(q邪{' jK..q  O6AU&Nԙ8]PitAIj}7&(QI N.L3=JS7nRwm>aׄFi >)4+4cj1B(-]~wҦnv/Ǿwq;x8Ǻ}q;q8GvَqT{֋юq4{/o_b"tݙ#Ľ?\q!B' C4(͔D]-SS+}ާtE0p^aM`\ }ƌ& Iz ^]G+)nCz)':+i[*TF.(ԫJ)UeY1mپZW8{X-`0(=<9;SyICI#? ~ ZϳYe)39kN^pIdjLIDݐۡd$'E}hGmvBh'l /+h[HD;# $`w(G; 񆶈7GM ohxC$ 67SD{% 6KI-I]JN aJ)[왒;iJ)ۦ웒;qJI $<%u`$);dzN(;J(l;J'(KcR n;JG(,;JW(l! B` %wsCHs(! +%w_"Qr>+. oO(P"ND"{A/,[$P"~D C8 %76! 1osxC7>$uEDX%b"JE(Q"FDb%/񆘉7M 7j"~87R! 1o([Q"DE8+<v Dƕg<ҘV _Z2ϕ݉4w 75No.M*4Ҕ644o LΘ<ֹ羥qJ Mz5T9M.3y5H7y4j%j9}xS-M-6TS吩aEFi5N4=ip:ϧi|=j|w'i1w偗Mf43~7&;J7h` 4g /5zՄ;gj MM폜tXzVaO4j+5:E=Թ STTS=5эRLѿv&;'js&nOi @S)KƔӚzk+ܸdz)S߯}Oj.?4wfifH)uYИa隤f5^ sM)niROęw0ʔN=ȅLf 'SIQQ3 MVC3 MMNk4c.2Epgt znpwON iON/?Ag83pg4O4!|Vt2>.,kSi,P?+y 8z|nVbˀ./S%wm!avA"vz}`_O+ZգUs_o `c66pv:);]P9tʑT.`4WşZM|x[>g@ R`-dجeg6G+G|e֏Z]#i*sjӟ)ŀuY98 ؊#.) B{7 ,;'IIsD/(04W; Oڮ7& 7=#i)Hf\JY#A>uzM \'_넴+Vp""|B, / :ZgTX #^o:GgPӅ:O5W2PnSJݗ&oY?^s6gkOxSzl3a_f(u!5_Czmi 9_ݠyNNx+E+퇽ޡ;RDZ*<﷕?X˾9]?t[#'jnsT]N5h{0g6W[[1cץL{[ߕS˒99ٝO \Cӌʐws/+19V60'~G%ηu ä.G\op_czH\G˿JI!IJ3 JJ9(i v猭J$g?o)/LI?3FEBs[z)ϯbIzqV|ė{JR[M,Cy2Wmv>?eU>wz4rږ-Zvx{Dx{l{<=j=xw{\Q=Kg8Cax>cszw?eY@AXAAABXB"La؍^#c=6v"F5tv521!=n4vs52c:F{|>¥g={|=Xvv=r=6W=r=P=vOg&{Ԧ{=Vbv{Vbx*cqs:9w(7{4*av ǁ9;*[{&{1Bb*ݣtvGtvJϧ{ظĘX6 ?[XWX:+iob&8 Q_^1cb[D}%8tA6~7ťgAt_r^aby?9#~ۥ<̮?jsWGËۋGf/Tu}2^OŴB\\)X9_J_媕>$Wc{~\Q=SmXsjb /'봌l"֭/+78YUnyYцqrfidm*Y7A lU 9G .ܘ$՘-}#66NnԹr˥CV[[ȭ̫)#\lMn[CPg9 r:߉.$FL<_rCb;7gW.{$%3^7]' M{w$]۸oܿJGWg/>OyIOq8hoICfCN1/^D6{b\pZ<◹r9ay7Qȉ{Lj)☧[)ëbZ? 8Yy^o9={8̥/K~q 7'ɂKpBM N('ⴏ8ӔIOx4d@DB ")4AsH 4HB(HD$ "8ɃBD!"LH(D$" Ʌ#!"lH8D$"ɇDD""HHD$%"ɉEDQHXD$-"ɋFD#"I{̈HhD$5"ɍGD@xD$="ɏ"Bb@AEłEEb " " " " " 2(4(6 8(: <(>(@(B(D(F(H(J(L(N(P(R(T(V(X(Z \(^(`(b(d(f(h(j(l(n(p(r(t(v@QAQAQQQ QQQHqMtv8 _ET?D_;_]h@:_r׿}rRZBR\R]2/( y1#ov{t|b;,w7?ȭi-§~05ȼqeYf:x(KH:eltV}\52Z/#d^+7+{<_^/k3-[[-i)'*t8Ji+w|/JYU=UjZoAYGfVMڰGלYkM҄k&>\*״u">z7ںaBVRd='"md5ص=WK-^iYM^~)ܑY͆Ed[} A9<0|xwm Z} j[-zjmiG{}6~}V;աқuH*tqے]Y춶Ex޲/g:3ݙ_3{6^S2{{Ӳwm[zux\+tr\YpucO3%Ϸd A>^f/~w[n^!aj;ZE>J$D F H+w'"ޡxRw*"ޭx"]Kw.CE;<2ňx'#L;є]M;ٔM;ᔸ]N;锸N;]Ow>%p(qPL@;0w`% J܁C(qPLB;0w` %B`% J܁E(q6QXE;0w`%0M`%4J܁mEG ;H ;H;w`$%JJ܁;)qR,J;w`,EG`-%\J܁`J؁ńdDd3%hJ܁Ք)qvS ,N;w`<%zJ܁~JAS<ς Y_!;b'0OV2\\aH) ?'vpΟұߜµ_"#)777n-Qw}WRxK1^RKlqCS-y6xT|mh Tx-M8R)P0JdPRDTWפG5گ -)iƕiDBە' u,TU(7|Iys#F;14>>Tj`Roדt# R`BPϋR !$ZV%짛Bk_*-έTZnhM[̓,CoHz,6M:4l'B&N"}z(\)]5t;]za&cC-[zSU1C>] }7K}-}B ^ ?^ d{Aۯ(ۯ8  BAbAl/^ d{A@P!! bBl/ W](.<^u^x/PlxKzU+5WIQxJ(F<^}>W%k' Pl{?G"" EEl/^0 bKAA# Fl/)8B9#q*<ϙl(|4^GU 4^"HU 4^bHU 4^HU 4^HHMA$i ĒƩ@0 A4i ēƫ@@ijbBJU 4^JU}(4^|E*Y%|E%ѥq*^B_Wb|EA*ezXloq+Ftmϸ.(f߸._ R++e~P0{}|JwM )(O.w8(6LDS;ss:9&qow=PEĈ Rc_A_D '>&!%Y v\p?$4(:L T8e~OXWoy*?5~۸`ƅsW?,wݸ8n3o6.isMdvai]f^51WY/Qc˺k'3j5kgeI݌S7D75n|VI+? g\w㖊[0~zAl=}U\?5;4|w&k^ gs5fhL)㾳ē7@q}ojNl9Is4~fa7{1~U5ߧt60*MsgCz?ZF/{楹pcK_#K5^~K#5Wc"Op͟Cjzmhss䘎߬k6^c}[^k2-4e5e/x75Jw:?wV3\fEM1+ȹkseO jVK)bʸ([WQ\֭^@c.8qśG([) N<:EqnooBuZ_1PIBPJ(jvj (' z (("("*)")"**!*"*+"+""""""# ###"#*#2#:#B#JS(5!5"*6"6"*7"7"*8"8"*9"9"*:":"*;";"*<"Y#r[&"7T8ΦJ 44UlTmmK}%ůwO5&숾c5?5'(u4իToj[j%RËF;J7Y 5]o6}W1B HW)R >ؿCZG qm{*O[`jjlͼuRɓz̖;4'EthcҩSӕHJKuL]&u;za}9[6zI߫fwҿ^S=Wgn.7=q\tA;Mc*Ct76H1SG6# I:5]zV_A?PK)U?ԏBIJA(=DsBJA )=ADsFJA)=DsJBKJA0 A4)-$sQJAH)=1>WVJσ(RzBKxKKi9/!/ /`Sz>L920{}G6F ҩ?7S?g)-EX*U!v7:u]Ituڼ_Tw*/sW>"@lIgujp8?UeoJ՛ɫ?(k%6aVmk_] m殮 3p2w> 7s7 3ZT;v &}:f@k_qBfln(p9',&oPoA*`g㿺X"#y@_sBldiu6No[`Uh9?'[&oXoa* m^t[y*&N7#PQߡ?t??-M&X"G7ѵn6:S0h&o hA ΩU?g+:m>p(jyEgW@'ߔmn`of&p?{Lf6a_mw$:l/C `WlW? L#`Uh9O'X87?;u ߑnk\t[?uudWrFFH&PG?;!&EG0C[7Q':au /aEG0Z7Q':͝"/Фyo歛@ޯEug ] S\>pOuyO[?R¸ԘD`NGxwq&bYfώMd?$$T%rǦ$KqB^._z3r~m46<樂;yj3?`Y]sz3^,ՙa߳n3l^ZweX>~8z+^oČU{^9cfdNm4c3I!Wglz{{򗽷r%{[O6c?Ko>sWGËۋGf/Tu}ސ|2^OŴB\\)X9_J_媕>$Wc{~\Q=SmXsjb /'봌l"֭/+78YUnyYцqrfidm*Y7A lU 9G .ܘ$՘-}#66NnԹr˥CV[[ȭ̫)#\lMn[CPg9 r:߉.$FL<_rCb;7gW.{$%3^7]' M{w$]۸oܿJGWg/>OyIOq8hoICfCN1/^D6{b\pZ<◹r9ay7Qȉ{Lj)☧[)ëbZ? 8Yy^o9={8̥/K~q 7'ɥ\Jv-Q֭ txy;ꀜj62b+۞f =\Ds9Qs\>-нWo-b>E| | uR dK*x JU|:%d%e%e%e$ e  % e   % e   % e   %d  %e%e%e%e%e%)&!H' (((t""""""""""""# ###$#,#4#<#D#LT#\#d#l#t#|###c9GDIGDYGDiGDyGDGD'GD'GDGDGDGaC"D 14 bx@AV" D 21lB@!"rTN1 bXAЂC Y3T?PB"B=~1!bBPC"%D M1D!bBP C"-B]1!bCP C"5D m1!bCP,z-@UkoPw؊CjۅFW}H;p8].Vûwn|?-i 8@9h)!)5>rnBVn|#DƧ@o 4@wIƧZLz̍Oԝ5fsSSBnn|5j87>NZ΍O'Qӹ*!j;7>'DƧsB~nx@1PB[C< qrm kC|\b' bu# j ~ b !8B !D+[1BA8 "! bA1""DG1.!bl"1 "*DW1n!bB1 "2Dg1!blCF1":Dw1!0Ѱ?Sqc!ᅚ !.Rb#L >Rb$L N@ K@̤ M@줚 O@ @,`LL@@@̵@쵩@ @,@L`l@@|ѶfډZ;qF7GC+ɴ˭,)1q [ UwdArj>ϗM͝E:h͟"cح+@8+bslr#s+#wg|ÐՐ@n(nT:SwL2? O$G?hrP7mG?f+w5ggVdll兓=wStXcz)LJՐOt#l~Tsr©į=gJ}&.V<)K@[B.!З/ˆHc*#" /)DZSY\iL?j~YJLZBjb̸|?i]/tnh(j'"'6^Eb%uGjR%[ɭ5G[;z{/֯ڑ'h_oiMY{z!$HǻzwwG u '˫^*"jU9zRl5 H]XԸDpCCB_|^" J h%ʢ[M+*>M~IY2nQ.I+~|Ur_[e˔(k=̩nF)wytu5y@xPfR]/gSogXT~r>8t!~fNKs˻NEw\lg) 6?kf̓ qĥ) Օ'By194/tŀмx :b3Oy1. /"#h/<^F񼘶 ϋY: ϋ!7z_Š̋Zg3/4b@mŀf^ cM̂à~R j@".&j-WLա< '^:,QowXMqQ{ WΉq/+go8q|hCρu!{`'ܷC|߽zd~L7oڱuw_0eMm,}iW|=uÞ0_ɴTaQ}^JQ^ٕU|cZ鰡Z靆jd|/2T?>Cmk N!Fτ2T˱@<8p}!zB 6O`S_ No} 3vP@he*P«#!:BiN)yhmBSF%9b?#kO—?M8h=+w2Yzۻ2(MI"9Qf 2!ϴ+VݣUl{8e`tn3埡 Ck0fC!pρO4? y= F-.9|u쪾_Ws?]YeW?>5+g*濬ԞDPNʓ+ukz*uFk:z?V34b9 k^|eC}soJ˕/ߟ( .ǻR3^49,ܥ6 NrdQЄ5v6H͉TF 5Zheҋ(F}jI[@Iۦ\1ps u۶^]0v'wghE$04%&~!U%k>OW_u/|kIϷn{^nTLŕ/<>[nl,bL; 쏮Q"ZDy~o!0ol\JRj'Ct:ooN=$*&_A?4$P%zb7P?@_65!ƾ4bH_%y$&$%$H-w@m;0`~?6?AeͿw&M_}\=SquMwhno,ц Mf44,mAРtܬf$5oAB31L1} xCt1\ƆS m3bh;.oCXHJ"_ddSd=$B$!Xkzs"$$5ʸ`O9/{Rw z<,w0lyDžf~yυ|nܑemjQs_/-7ux@gwi;IyiU4KoV8,D/q5Wjy"@\4p5 \ njjWp@jRЦDdq5`x W𴁫 \ 4p5U \ hD g \ "p5`GW0_jZW&} $Yrv>r_7n?^!y Q|ۙ,vcFuf[ÇK꽡1zX}(]'#,Gab(03D-pHq"cu˟Z]jYbLáͤ-ܞ A븒5%Pv}.> UH:4~׷PǛhG\=X=-!uh P뿎^~}k>Osk:ZwsaK_"-"-\I1--zäd)beişo*o6e^ٸO: i}4iCI@7is[[-WVHl߼\ٱpJYz+~weO70e_Ҿ3I_4ar`cOo~X9NrhwWŦßY9”s){"hHӱc"fr"rrQͩ4_U_9As\z3oM6~xYÍ\͍4׫] #rKsuų4~Oh.\~幷5WGN>l^Fg |uRW5cㆂϼB׽C bZ}C+RkWnTL< uT-֭n}=KT<;U[yex_DuO+= ŏ"@ AiL$@ iL$@ib#MLqi 41 D:A(ib%e * iIǧK+E \ 6XYڠ@muׁ::^jk@Ou4˷vZML^ø{||fxk٧ 'c\u?YL +TkS2~jmh ^{A:K2V/Cܭ,w/T<;]ճot6޹w@9خln}QrC6P $*l CTa)⥟^iK?k&B'DO SS`[ l+rۊ`[+O(o6K?,rg]~Y 0K?DdfjORe+WPJ ANb'r(BQ#٦bcVԊ/"LǮmbbSb3iC;20?+|NPv--seb|KiK%y={FnbqO P:2ޣ#zХ }~@`6ek7 ΘJJv/O V?hc:D1dS *~5ȧ=nT9t1ږ7H7ޛfcr%zuЩRJ/:Nqk_TIɽZUlwesދ|6˽tP~ΧPa|bw "Ԝ! K[Ц,;خg݊w() V xjZ~4El3TM Rg mFv`; i({SPJ@ooZSCTBP nĞCjAAʁꁈ H*_$jBIPDTDTDTDTDTPjjCC TR]$@" c%ԈRDP$JA(EeԉRDP(5ETSD5Et5j&p) q1\PonNOHJ? 8P/ P֋u4CK0Zk/,/5.H]Fkқ׌q+`јҞ^;ʕ} Qw]c"0.y,}r_.e}^kE"Xp*3{2'[ Gp.jY =g8rkZza$'&%䤘atѪG0CG)]UciYy {p ܟn #ܟyʾܟ!_E++gfʦf?[Sze}{W|CoӁU )^Xymm.pO:XLwQ J:9M4>WeNP#_]rkAePK|jk8jlDM!]}4YF" tNn턟:턟yEפFLvz=nh]Em_4ݱFZ)d}r-Kd|X}a8 [LblONDrb<bZlz£[{m;vdk&daԛ<&!2XFe`6ZD.pN3 MH7G1p\VEJA)YM7}V%. I@y uՉɣ3_HH>)WX(g'`RY&&Ϧ$r%Iuݐh]UI{]*c::e&>8.䵁=gy+|^:hAZp+" h!2 h 'AACAlE?e^<EAs:8ٝfwy?1g9r KD!x)ܒfW{s|92(Xvf>o"ʍ^;˪^VUj«{QȪ~.Xwլfj|Tk~8c/5i;fz[S~'{ 'إmgц;MfgN^o6K}E)l 9\+e Vc>Sk-:Trij6Gvr9SsS폙:-wVSd9O7u]=d'6zx.u[y?hQlc<[;}Vk;=u-9u?W[x^]2@"n"Cd" ,"# 1636/ lpy$ >܎P n} Z'qmIJ5s _$m|lmyM6ZU& $Jnb^jw|*?iF''v  U__{{~nnoxj=AvC^I2ErM{ iϙw-l^jj/jf4mCD*t*O!@hdq_:Y<-p\yHHM?s˽svе_ ]fGGդ{BR܋2|q}8q+ j ;'2EJPX߷B[F]Q{[a Zޞa^>-H'^Ey2wK70ҋQhY*@=j(^6H]lMnV-jYnYUvedNHIrPO6@m;ź;gEPS%?^~-]cCYslRnvC}뒘歯 +UuY_Iʒ*?E_jujJMՅRsC~(jRw:J,[J=u?ok4 _T(ȏ#-{@MtADC Ї:DB!4ĝ H'@).YH-B|!zkdUoHYTx}fmuԄ\XyPߑmϙkcco>{o(=v;={0e\\,]*rgBeIˆٷb&wV<;6&w 6+NX'#/gR?rP4BM~tyIJ_M7zEPy1c(B m,#ӝ|$9օOKJrAujz7?gVdloSZ,v X#xk!;Vh[<`Uc?T֫}8pjģe GQiJF7ەsgޱ:`͘7-$/*{Oy ȣ4-P"І6$1M EQYEQQQ./\YuEd?E\7;[>vuE~gHi&ʤ曤̹wϹ{&j#Ơ6cSSAcU` k(CpPE 0*k0Re 5#; Pe J93RʙF+URڨ6jI`_xGwNOn?{tc{gyT w+hݦqQ>sbϙ_h@mXNsNXIyW3nE>-bznxwJa_Auk̽z%a l0Ho}~O[g-~;mܗ}y\݊e;'q̛}q0݄VMw #nfoX}{e{۸>/}O[S`DٸRW v\A'tu$xnj;'+~Ag7|%X4 [& '7o(LPdЪL5hSԃ.0 LEhS]BY0LeP !d>@(LP" EBP& (!S) mV@(ECL96N___*is1amDT#ZwtHm_q9?<*}NYOUMTȍSjvPk髞O3QNfjy ]=N/ZkY/x՜6"8-9PsV^gIy&M[bUb<&ɢbᯍtwX9:+=/ewPȡr]g~f#9Zvб gMjpvdS?Gl-|UR2[&fv= o2+$}M;?)o3ho'~mAp-So짭!E{zoӁ2O-%bQ {>:| %NAweѱ$H&*R*7b⿨8nq)w߿i%x}YLevq.QoFQK3N+g؆O.a?# ֗O0kBn;Bobjv[VTguo57,GRqo{d+VS^ԟcIVKPbzs@[-Ŧ?MGkTOSjeO:v5SGUDo6Zi$i; cT=RMKL[܆߮ Wߕ@{3q-;jTYhG1;5q%5vE}:Z?QwTuöDYXf>ކ5 &d.ucGрnO ?6UiGtTobǢ / :ԟcZRmXa5Cr5l*訌6[Q*ұT iRXfӗ:T"z{Tڢc+j]Yne&NWonTE& Ć ,{rOLf{}N({~˛noyB ~V@\kOc$MZ1ݹVTm-?vtjWᑟjW-M8du_&5vpUo?@Am[},٤#^w~&wRJ;,vҹAS|6V⇬WzKxZt~9~hcdJ ?Χ-IM[R hQ3F*oCiprA H T@ rA0BF2 ـ x@AB Z90m3y|ӛQ"]Z_#߱Y⎾CD,p{ՙ҆)SKEjڿ-?61#o4mUR?mzۮzUz;K>sS~GqG]'?7^0?H?Ϥ![_kCxެz~ؒO{/7?Q:/73ܘHc_JL2po,[˛ًy֑9?h>u\]&<%?]ܤ:i; B5\)})3vH7JY Y>g'_mz~ڡ4.oo_yT؅R-zœ5EI}ֿzH \w~yK܂ҝ-_ڿ(RI_m;He#e/ϑ{N||Қ:]<\zwrU}fjR9JNz Rk5HWAQR5ĉLHQ !+S0"dE e+DR&d`bF &h`F &l`F8B&r`BGĎLQ +Q +R !+J<L)_L)_ݔ<'` *y"(+R )+j< _!*|R'U %dKy &<[B)_Z+ ܻX29UXV\gOYw/VOW_))Q6z 3c3t6I/YZЯ 5g5|Y'dXbaF(fXbaF 9(fXbaFYa(f!dġXb`F"BF$(fXbaF,(fXbaF4(fXbaF< !Œ 32R,I0#%Œ 3rR,JHJ0#*Œ 3R,HK0#.Œ3S,̈L0#3Œ 3RS,̈M0#7Œ 3S,̈N0#;Œ 3S,̈O10#?! DbB (~C+DbB(U+Dba& 3XXP ѠX8M+abW T QX L\(fC0P,Ćba&8 3ѡX L|(fD10! L%0Q,ĉba&P 3X L(fE0-pQ,ċba&`3~ L!j 3aXL(f"G0:Q,ba&zVtv4bOs| d3;BQ(8;KT؆&MמO8E5½Fk߈A{!!'E#9b;v׆G찐"9J̘zG!~V~{v붋2qhF 3)de־)c`{25{€>h4Hd4O!W#-~_zuIZ`0d6ji)2qVV:sO?_SRM80ξ L.\@GZiOnء0Mcɔf7 7ӃFpzV ;J2jSڙYb=?ShgF*uM/Yo$? G?.Ǎٻ=^?' 㿅ܲgo+v υݙ6lǬI>pusĴ‘g]G]qqOsiJ8N} y0ם yGqk^_*@| _ +5@|] 2_'dĭvqKy[tųZ㿫]T͖o14GK*ݓiE 7M0(?H2E93ڴkCW1eu8'Zsb^%TBt/Vg!@7Xh?TmGL``CP k4:v߹ĩ9a5ͧfEc_zEH}]!X9 t |H(^yf_oo,D. s0gЇ򜫜#'+.y>y~FpSz \* ]_ ŕBɋ_ɎWlãS/Yq ʿRF7Oews1 `ۙ(ԤQ3U^VarŠ?ɗqrAy㓅Չ{5v[q#ڷQ?Lfo-U61Bl}!fę݀i!!v$d+SaS Jl } 3@a{ D~LSA{ dmvA탐 !k+@ D юY[=6D"dm Ex2M판5 mvDu0h» ǙYS¿%.415KMEً[iۺO b]`)o; DbUdnT۪w 6?8B RiTʰs0yhئGtSp #&q sb+A+Lx66 (/jok=2TDsDqJ%ou NyE<Җ?rWVWVGM#pv wwezٜOw@M},0zVv+ D_Q9'alՌΡKO*ٕ(&Oާl Uq,{,]U>+ob!NoyN\*jOSo8s7\rUz*P%s Ή˵:J2j*l1h_T~Bۆ4Kp=>t7 ߖhrxZsb^i#G-2qXp"UEio`|qIW:mo1j뿣mKH17T`ho~iSOct3eՎ*1LFI"?=U` %gn fm8_5;KꜣCϦ?ߎ+ qC.?c=BKZG]+H\c>[-8oo鿯Q6=܅J"kp=\< # jo5SaEO1/|:gBO 8eTr%qo`^[NN|R}@\֦б7jmӂ-RnU}M4c]fiӁ8%iVW^hc-ۻ]Y7 rN?hz\Wߩmp ?y8ײSaѨ:Tlbw {?(޵~saϔL:sBGٛ!Ƿ;q"<)cZ}! w{ևc\FN|@ .xc/99 2B_Ƚ֖sH4!H?<9?0x z^DŽ^W{5}ڄ~>ySyb a߲M:dliL4hoAl4Ϯ4-D>4a #G?oBL'_z|E?*\~sZ7(1q/ ė偸&q#č!d7.At&qY@0 n7 &q# ʝcoi "UZg׆VsTRJ6DvsF&k/)z YmdO9_X4CU{e9E[m?qJ7%|ݴchFC3;Җf?1=࿵qkd)G1|jr8{kOR8D>+44L(`マPF%- \٢ ]PC]p@\(K.¹3r:ϰ1{+:G?K?Z?ųŊk3X4/Jy%P&8Y!7}=wَO{`NO FQmLfݕTs ˪=7yeձW]Bѯ㿁?Y쯊gf~ߜbE7)2w.s4a"{ja=m'Fwdq7-W zҋ s@Uß,zӡ?/?D~h4JsMWs/@G.I _HH>Αt}e۞0㾇m7=K^U|oWg,o]o| Mp^tn|~@4K`4C#k^t^q7%o[$?# Q"~Gc_[-%m)0QAJ^]04E2ݨ7U/}zKQ':Xo3}"tNէ&Џ?1?y#!0y4ISfdn2̥Rַ鳓~gG}nJ>[y L)́ǦK3/ 2Nx^*JF4(PlT_sK=?oQ%_+/}+t~Z^?YZ'PRL*vw=WrZ(9Q*{yqy|0'zr"&[z(.\/{{x_}|&uTs򄻥_VZo.V!iM% tEO}R G c =wPb!+ڏʭ!` 2A( H@A. Dl@HÃ."rFB H! %$N 2RΈ Y ,@Ab  4@Ar N =@"!@0!@! h!@P_q!(@ !0@ BCBt B" AB& "d"P2Bh!\@Q_ʄ 1#' !nI1#d"2B!|@!@!(!@# H!L,LB&@'!O BHS QBXW Bh Bp !L|` D!Bd Df]нoggo43̜b vѩOgϋFjr9]euD&1|g{_pF-9d\f "$, hMf3+IVv5o 6近/JnX- &S jaJB.[ -ux%brU]dnRBj:W]bTbN ar.쯊3.WM 7[QΞܚUXKu.@R?k]uE6C u{+4aqtrQe)|w宬쯆ҟTn*[oK-+Lsk?w;ˢ+*Ag:|^ӧ]Ψ/qx_{hmZ*sT:4e$eGS &d5~l-)FclKT4+ ퟒb>3]Uc1[M'c4ٴт}Գt˞ \]Ǘ>xX:4/7H|O|!~JWJ/?t~ t;?lRX?}=?\B 9B yB" B&@l!@!%@ !-@ LbB&5@ ! dʤB! rceqd|G?eTJfoZO{h=C{h=VH python-dateutil-2.4.2/dateutil/zoneinfo/__init__.py0000666000000000000000000000757412506520370020573 0ustar 00000000000000# -*- coding: utf-8 -*- import logging import os import warnings import tempfile import shutil from subprocess import check_call from tarfile import TarFile from pkgutil import get_data from io import BytesIO from contextlib import closing from dateutil.tz import tzfile __all__ = ["gettz", "rebuild"] _ZONEFILENAME = "dateutil-zoneinfo.tar.gz" # python2.6 compatability. Note that TarFile.__exit__ != TarFile.close, but # it's close enough for python2.6 _tar_open = TarFile.open if not hasattr(TarFile, '__exit__'): def _tar_open(*args, **kwargs): return closing(TarFile.open(*args, **kwargs)) class tzfile(tzfile): def __reduce__(self): return (gettz, (self._filename,)) def getzoneinfofile_stream(): try: return BytesIO(get_data(__name__, _ZONEFILENAME)) except IOError as e: # TODO switch to FileNotFoundError? warnings.warn("I/O error({0}): {1}".format(e.errno, e.strerror)) return None class ZoneInfoFile(object): def __init__(self, zonefile_stream=None): if zonefile_stream is not None: with _tar_open(fileobj=zonefile_stream, mode='r') as tf: # dict comprehension does not work on python2.6 # TODO: get back to the nicer syntax when we ditch python2.6 # self.zones = {zf.name: tzfile(tf.extractfile(zf), # filename = zf.name) # for zf in tf.getmembers() if zf.isfile()} self.zones = dict((zf.name, tzfile(tf.extractfile(zf), filename=zf.name)) for zf in tf.getmembers() if zf.isfile()) # deal with links: They'll point to their parent object. Less # waste of memory # links = {zl.name: self.zones[zl.linkname] # for zl in tf.getmembers() if zl.islnk() or zl.issym()} links = dict((zl.name, self.zones[zl.linkname]) for zl in tf.getmembers() if zl.islnk() or zl.issym()) self.zones.update(links) else: self.zones = dict() # The current API has gettz as a module function, although in fact it taps into # a stateful class. So as a workaround for now, without changing the API, we # will create a new "global" class instance the first time a user requests a # timezone. Ugly, but adheres to the api. # # TODO: deprecate this. _CLASS_ZONE_INSTANCE = list() def gettz(name): if len(_CLASS_ZONE_INSTANCE) == 0: _CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream())) return _CLASS_ZONE_INSTANCE[0].zones.get(name) def rebuild(filename, tag=None, format="gz", zonegroups=[]): """Rebuild the internal timezone info in dateutil/zoneinfo/zoneinfo*tar* filename is the timezone tarball from ftp.iana.org/tz. """ tmpdir = tempfile.mkdtemp() zonedir = os.path.join(tmpdir, "zoneinfo") moduledir = os.path.dirname(__file__) try: with _tar_open(filename) as tf: for name in zonegroups: tf.extract(name, tmpdir) filepaths = [os.path.join(tmpdir, n) for n in zonegroups] try: check_call(["zic", "-d", zonedir] + filepaths) except OSError as e: if e.errno == 2: logging.error( "Could not find zic. Perhaps you need to install " "libc-bin or some other package that provides it, " "or it's not in your PATH?") raise target = os.path.join(moduledir, _ZONEFILENAME) with _tar_open(target, "w:%s" % format) as tf: for entry in os.listdir(zonedir): entrypath = os.path.join(zonedir, entry) tf.add(entrypath, entry) finally: shutil.rmtree(tmpdir) python-dateutil-2.4.2/dateutil/__init__.py0000666000000000000000000000005612506555003016731 0ustar 00000000000000# -*- coding: utf-8 -*- __version__ = "2.4.2" python-dateutil-2.4.2/LICENSE0000666000000000000000000000332012505254345014013 0ustar 00000000000000dateutil - Extensions to the standard Python datetime module. Copyright (c) 2003-2011 - Gustavo Niemeyer Copyright (c) 2012-2014 - Tomi Pieviläinen Copyright (c) 2014 - Yaron de Leeuw All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. python-dateutil-2.4.2/MANIFEST.in0000666000000000000000000000007312505551312014540 0ustar 00000000000000include LICENSE NEWS zonefile_metadata.json updatezinfo.py python-dateutil-2.4.2/NEWS0000666000000000000000000001646312506556450013524 0ustar 00000000000000Version 2.4.2 ------------- - Updated zoneinfo to 2015b. - Fixed issue with parsing of tzstr on Python 2.7.x; tzstr will now be decoded if not a unicode type. gh #51 (lp:1331576), gh pr #55. - Fix a parser issue where AM and PM tokens were showing up in fuzzy date stamps, triggering inappropriate errors. gh #56 (lp: 1428895), gh pr #63. - Missing function "setcachsize" removed from zoneinfo __all__ list by @ryanss, fixing an issue with wildcard imports of dateutil.zoneinfo. (gh pr #66). - (PyPi only) Fix an issue with source distributions not including the test suite. Version 2.4.1 ------------- - Added explicit check for valid hours if AM/PM is specified in parser. (gh pr #22, issue #21) - Fix bug in rrule introduced in 2.4.0 where byweekday parameter was not handled properly. (gh pr #35, issue #34) - Fix error where parser allowed some invalid dates, overwriting existing hours with the last 2-digit number in the string. (gh pr #32, issue #31) - Fix and add test for Python 2.x compatibility with boolean checking of relativedelta objects. Implemented by @nimasmi (gh pr #43) and Cdric Krier (lp: 1035038) - Replaced parse() calls with explicit datetime objects in unit tests unrelated to parser. (gh pr #36) - Changed private _byxxx from sets to sorted tuples and fixed one currently unreachable bug in _construct_byset. (gh pr #54) - Additional documentation for parser (gh pr #29, #33, #41) and rrule. - Formatting fixes to documentation of rrule and README.rst. - Updated zoneinfo to 2015a. Version 2.4.0 ------------- - Fix an issue with relativedelta and freezegun (lp:1374022) - Fix tzinfo in windows for timezones without dst (lp:1010050, gh #2) - Ignore missing timezones in windows like in POSIX - Fix minimal version requirement for six (gh #6) - Many rrule changes and fixes by @pganssle (gh pull requests #13 #14 #17), including defusing some infinite loops (gh #4) Version 2.3 ----------- - Cleanup directory structure, moved test.py to dateutil/tests/test.py - Changed many aspects of dealing with the zone info file. Instead of a cache, all the zones are loaded to memory, but symbolic links are loaded only once, so not much memory is used. - The package is now zip-safe, and universal-wheelable, thanks to changes in the handling of the zoneinfo file. - Fixed tzwin silently not imported on windows python2 - New maintainer, together with new hosting: GitHub, Travis, Read-The-Docs Version 2.2 ----------- - Updated zoneinfo to 2013h - fuzzy_with_tokens parse addon from Christopher Corley - Bug with LANG=C fixed by Mike Gilbert Version 2.1 ----------- - New maintainer - Dateutil now works on Python 2.6, 2.7 and 3.2 from same codebase (with six) - #704047: Ismael Carnales' patch for a new time format - Small bug fixes, thanks for reporters! Version 2.0 ----------- - Ported to Python 3, by Brian Jones. If you need dateutil for Python 2.X, please continue using the 1.X series. - There's no such thing as a "PSF License". This source code is now made available under the Simplified BSD license. See LICENSE for details. Version 1.5 ----------- - As reported by Mathieu Bridon, rrules were matching the bysecond rules incorrectly against byminute in some circumstances when the SECONDLY frequency was in use, due to a copy & paste bug. The problem has been unittested and corrected. - Adam Ryan reported a problem in the relativedelta implementation which affected the yearday parameter in the month of January specifically. This has been unittested and fixed. - Updated timezone information. Version 1.4.1 ------------- - Updated timezone information. Version 1.4 ----------- - Fixed another parser precision problem on conversion of decimal seconds to microseconds, as reported by Erik Brown. Now these issues are gone for real since it's not using floating point arithmetic anymore. - Fixed case where tzrange.utcoffset and tzrange.dst() might fail due to a date being used where a datetime was expected (reported and fixed by Lennart Regebro). - Prevent tzstr from introducing daylight timings in strings that didn't specify them (reported by Lennart Regebro). - Calls like gettz("GMT+3") and gettz("UTC-2") will now return the expected values, instead of the TZ variable behavior. - Fixed DST signal handling in zoneinfo files. Reported by Nicholas F. Fabry and John-Mark Gurney. Version 1.3 ----------- - Fixed precision problem on conversion of decimal seconds to microseconds, as reported by Skip Montanaro. - Fixed bug in constructor of parser, and converted parser classes to new-style classes. Original report and patch by Michael Elsdrfer. - Initialize tzid and comps in tz.py, to prevent the code from ever raising a NameError (even with broken files). Johan Dahlin suggested the fix after a pyflakes run. - Version is now published in dateutil.__version__, as requested by Darren Dale. - All code is compatible with new-style division. Version 1.2 ----------- - Now tzfile will round timezones to full-minutes if necessary, since Python's datetime doesn't support sub-minute offsets. Thanks to Ilpo Nyyssnen for reporting the issue. - Removed bare string exceptions, as reported and fixed by Wilfredo Snchez Vega. - Fix bug in leap count parsing (reported and fixed by Eugene Oden). Version 1.1 ----------- - Fixed rrule byyearday handling. Abramo Bagnara pointed out that RFC2445 allows negative numbers. - Fixed --prefix handling in setup.py (by Sidnei da Silva). - Now tz.gettz() returns a tzlocal instance when not given any arguments and no other timezone information is found. - Updating timezone information to version 2005q. Version 1.0 ----------- - Fixed parsing of XXhXXm formatted time after day/month/year has been parsed. - Added patch by Jeffrey Harris optimizing rrule.__contains__. Version 0.9 ----------- - Fixed pickling of timezone types, as reported by Andreas Khler. - Implemented internal timezone information with binary timezone files [1]. datautil.tz.gettz() function will now try to use the system timezone files, and fallback to the internal versions. It's also possible to ask for the internal versions directly by using dateutil.zoneinfo.gettz(). - New tzwin timezone type, allowing access to Windows internal timezones (contributed by Jeffrey Harris). - Fixed parsing of unicode date strings. - Accept parserinfo instances as the parser constructor parameter, besides parserinfo (sub)classes. - Changed weekday to spell the not-set n value as None instead of 0. - Fixed other reported bugs. [1] http://www.twinsun.com/tz/tz-link.htm Version 0.5 ----------- - Removed FREQ_ prefix from rrule frequency constants WARNING: this breaks compatibility with previous versions. - Fixed rrule.between() for cases where "after" is achieved before even starting, as reported by Andreas Khler. - Fixed two digit zero-year parsing (such as 31-Dec-00), as reported by Jim Abramson, and included test case for this. - Sort exdate and rdate before iterating over them, so that it's not necessary to sort them before adding to the rruleset, as reported by Nicholas Piper. python-dateutil-2.4.2/PKG-INFO0000666000000000000000000000202412506556560014110 0ustar 00000000000000Metadata-Version: 1.1 Name: python-dateutil Version: 2.4.2 Summary: Extensions to the standard Python datetime module Home-page: https://dateutil.readthedocs.org Author: Yaron de Leeuw Author-email: me@jarondl.net License: Simplified BSD Description: The dateutil module provides powerful extensions to the datetime module available in the Python standard library. Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Topic :: Software Development :: Libraries Requires: six python-dateutil-2.4.2/python_dateutil.egg-info/0000777000000000000000000000000012506556560017723 5ustar 00000000000000python-dateutil-2.4.2/python_dateutil.egg-info/dependency_links.txt0000666000000000000000000000000112506556557023777 0ustar 00000000000000 python-dateutil-2.4.2/python_dateutil.egg-info/PKG-INFO0000666000000000000000000000202412506556557021024 0ustar 00000000000000Metadata-Version: 1.1 Name: python-dateutil Version: 2.4.2 Summary: Extensions to the standard Python datetime module Home-page: https://dateutil.readthedocs.org Author: Yaron de Leeuw Author-email: me@jarondl.net License: Simplified BSD Description: The dateutil module provides powerful extensions to the datetime module available in the Python standard library. Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Topic :: Software Development :: Libraries Requires: six python-dateutil-2.4.2/python_dateutil.egg-info/requires.txt0000666000000000000000000000001212506556557022322 0ustar 00000000000000six >=1.5 python-dateutil-2.4.2/python_dateutil.egg-info/SOURCES.txt0000666000000000000000000000110112506556557021606 0ustar 00000000000000LICENSE MANIFEST.in NEWS README.rst setup.cfg setup.py updatezinfo.py zonefile_metadata.json dateutil/__init__.py dateutil/easter.py dateutil/parser.py dateutil/relativedelta.py dateutil/rrule.py dateutil/tz.py dateutil/tzwin.py dateutil/test/__init__.py dateutil/test/test.py dateutil/zoneinfo/__init__.py dateutil/zoneinfo/dateutil-zoneinfo.tar.gz python_dateutil.egg-info/PKG-INFO python_dateutil.egg-info/SOURCES.txt python_dateutil.egg-info/dependency_links.txt python_dateutil.egg-info/requires.txt python_dateutil.egg-info/top_level.txt python_dateutil.egg-info/zip-safepython-dateutil-2.4.2/python_dateutil.egg-info/top_level.txt0000666000000000000000000000001112506556557022453 0ustar 00000000000000dateutil python-dateutil-2.4.2/python_dateutil.egg-info/zip-safe0000666000000000000000000000000212447574526021362 0ustar 00000000000000 python-dateutil-2.4.2/README.rst0000666000000000000000000001073112506043162014473 0ustar 00000000000000dateutil - powerful extensions to datetime ========================================== .. image:: https://img.shields.io/travis/dateutil/dateutil/master.svg?style=flat-square :target: https://travis-ci.org/dateutil/dateutil :alt: travis build status .. image:: https://img.shields.io/appveyor/ci/dateutil/dateutil/master.svg?style=flat-square :target: https://ci.appveyor.com/project/dateutil/dateutil :alt: appveyor build status .. image:: https://img.shields.io/pypi/dd/python-dateutil.svg?style=flat-square :target: https://pypi.python.org/pypi/python-dateutil/ :alt: pypi downloads per day .. image:: https://img.shields.io/pypi/v/python-dateutil.svg?style=flat-square :target: https://pypi.python.org/pypi/python-dateutil/ :alt: pypi version The `dateutil` module provides powerful extensions to the standard `datetime` module, available in Python. Download ======== dateutil is available on PyPI https://pypi.python.org/pypi/python-dateutil/ The documentation is hosted at: https://dateutil.readthedocs.org/ Code ==== https://github.com/dateutil/dateutil/ Features ======== * Computing of relative deltas (next month, next year, next monday, last week of month, etc); * Computing of relative deltas between two given date and/or datetime objects; * Computing of dates based on very flexible recurrence rules, using a superset of the `iCalendar `_ specification. Parsing of RFC strings is supported as well. * Generic parsing of dates in almost any string format; * Timezone (tzinfo) implementations for tzfile(5) format files (/etc/localtime, /usr/share/zoneinfo, etc), TZ environment string (in all known formats), iCalendar format files, given ranges (with help from relative deltas), local machine timezone, fixed offset timezone, UTC timezone, and Windows registry-based time zones. * Internal up-to-date world timezone information based on Olson's database. * Computing of Easter Sunday dates for any given year, using Western, Orthodox or Julian algorithms; * More than 400 test cases. Quick example ============= Here's a snapshot, just to give an idea about the power of the package. For more examples, look at the documentation. Suppose you want to know how much time is left, in years/months/days/etc, before the next easter happening on a year with a Friday 13th in August, and you want to get today's date out of the "date" unix system command. Here is the code: .. doctest:: readmeexample >>> from dateutil.relativedelta import * >>> from dateutil.easter import * >>> from dateutil.rrule import * >>> from dateutil.parser import * >>> from datetime import * >>> now = parse("Sat Oct 11 17:13:46 UTC 2003") >>> today = now.date() >>> year = rrule(YEARLY,dtstart=now,bymonth=8,bymonthday=13,byweekday=FR)[0].year >>> rdelta = relativedelta(easter(year), today) >>> print("Today is: %s" % today) Today is: 2003-10-11 >>> print("Year with next Aug 13th on a Friday is: %s" % year) Year with next Aug 13th on a Friday is: 2004 >>> print("How far is the Easter of that year: %s" % rdelta) How far is the Easter of that year: relativedelta(months=+6) >>> print("And the Easter of that year is: %s" % (today+rdelta)) And the Easter of that year is: 2004-04-11 Being exactly 6 months ahead was **really** a coincidence :) Author ====== The dateutil module was written by Gustavo Niemeyer in 2003 It is maintained by: * Gustavo Niemeyer 2003-2011 * Tomi Pieviläinen 2012-2014 * Yaron de Leeuw 2014- Building and releasing ====================== When you get the source, it does not contain the internal zoneinfo database. To get (and update) the database, run the updatezinfo.py script. Make sure that the zic command is in your path, and that you have network connectivity to get the latest timezone information from IANA. If you have downloaded the timezone data earlier, you can give the tarball as a parameter to updatezinfo.py. Testing ======= dateutil has a comprehensive test suite, which can be run simply by running `python setup.py test [-q]` in the project root. Note that if you don't have the internal zoneinfo database, some tests will fail. Apart from that, all tests should pass. To easily test dateutil against all supported Python versions, you can use `tox `_. All github pull requests are automatically tested using travis. python-dateutil-2.4.2/setup.cfg0000666000000000000000000000014012506556560014631 0ustar 00000000000000[bdist_wheel] universal = 1 [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 python-dateutil-2.4.2/setup.py0000666000000000000000000000330112506517720014517 0ustar 00000000000000#!/usr/bin/python from os.path import isfile import codecs import os import re from setuptools import setup if isfile("MANIFEST"): os.unlink("MANIFEST") TOPDIR = os.path.dirname(__file__) or "." VERSION = re.search('__version__ = "([^"]+)"', codecs.open(TOPDIR + "/dateutil/__init__.py", encoding='utf-8').read()).group(1) setup(name="python-dateutil", version=VERSION, description="Extensions to the standard Python datetime module", author="Yaron de Leeuw", author_email="me@jarondl.net", url="https://dateutil.readthedocs.org", license="Simplified BSD", long_description=""" The dateutil module provides powerful extensions to the datetime module available in the Python standard library. """, packages=["dateutil", "dateutil.zoneinfo"], package_data={"dateutil.zoneinfo": ["dateutil-zoneinfo.tar.gz"]}, zip_safe=True, requires=["six"], install_requires=["six >=1.5"], # XXX fix when packaging is sane again classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Topic :: Software Development :: Libraries', ], test_suite="dateutil.test.test" ) python-dateutil-2.4.2/updatezinfo.py0000666000000000000000000000175112506517720015716 0ustar 00000000000000#!/usr/bin/env python import os import hashlib import json import io from six.moves.urllib import request from dateutil.zoneinfo import rebuild METADATA_FILE = "zonefile_metadata.json" def main(): with io.open(METADATA_FILE, 'r') as f: metadata = json.load(f) if not os.path.isfile(metadata['tzdata_file']): print("Downloading tz file from iana") request.urlretrieve(os.path.join(metadata['releases_url'], metadata['tzdata_file']), metadata['tzdata_file']) with open(metadata['tzdata_file'], 'rb') as tzfile: sha_hasher = hashlib.sha512() sha_hasher.update(tzfile.read()) sha_512_file = sha_hasher.hexdigest() assert metadata['tzdata_file_sha512'] == sha_512_file, "SHA failed for" print("Updating timezone information...") rebuild(metadata['tzdata_file'], zonegroups=metadata['zonegroups']) print("Done.") if __name__ == "__main__": main() python-dateutil-2.4.2/zonefile_metadata.json0000666000000000000000000000110212506554732017354 0ustar 00000000000000{ "metadata_version" : 0.1, "releases_url" : "ftp://ftp.iana.org/tz/releases/", "tzdata_file" : "tzdata2015b.tar.gz", "tzdata_file_sha512" : "767782b87e62a8f7a4dbcae595d16a54197c9e04ca974d7016d11f90ebaf2537b804d111f204af9052c68d4670afe0af0af9e5b150867a357fc199bb541368d0", "zonegroups" : [ "africa", "antarctica", "asia", "australasia", "europe", "northamerica", "southamerica", "pacificnew", "etcetera", "systemv", "factory", "backzone", "backward"] }