def string_to_number(text): ''' Used to evaluate string that are numbers #### Examples 1. `four hundred` == `4 * 100` == `400` 2. `sixty three thousand` == `(60+3) * 1000` == `63000` 3. `one hundred thirty five` == `1 * 100 + (30+5)` == 135` 4. `three hundred fifty two thousand seven hundred sixty one` = `(3 * 100 + (50 + 2)) * 1000 + 7 * 100 + (60 + 1)` == `352,761` ''' if type(text) is str: try: # the text may already be a number. float(text.replace(',', '')) return float(text) except ValueError: text = text.lower() s = LONG.sub( lambda m: ' * %s' % NUMBERS.get(m.groupdict().get('n')), text) s = NUM.sub( lambda m: "(%s) " % '+'.join( map(lambda n: str(NUMBERS.get(n)), m.group().strip().split(' '))), s) if re.search('/[a-z]/', s): raise TimestringInvalid("Invalid characters in number string.") try: return eval(s) except SyntaxError: raise TimestringInvalid("Invalid number string.") else: return text
def string_to_number(text): ''' Used to evaluate string that are numbers #### Examples 1. `four hundred` == `4 * 100` == `400` 2. `sixty three thousand` == `(60+3) * 1000` == `63000` 3. `one hundred thirty five` == `1 * 100 + (30+5)` == 135` 4. `three hundred fifty two thousand seven hundred sixty one` = `(3 * 100 + (50 + 2)) * 1000 + 7 * 100 + (60 + 1)` == `352,761` ''' if type(text) is str: try: # the text may already be a number. float(text.replace(',', '')) return float(text) except ValueError: text = text.lower() r = dict(one=1, two=2, three=3, four=4, five=5, six=6, seven=7, eight=8, nine=9, ten=10, eleven=11, twelve=12, thirteen=13, fourteen=14, fifteen=15, sixteen=16, seventeen=17, eighteen=18, nineteen=19, twenty=20, thirty=30, fourty=40, fifty=50, sixty=60, seventy=70, eighty=80, ninety=90, hundred=100) s = re.sub('(?P<s>\s)(?P<n>hundred|thousand)', lambda m: ' * %s' % r.get(m.groupdict().get('n')), text) s = re.sub('((one|two|twenty|twelve|three|thirty|thirteen|four(teen|ty)?|five|fif(teen|ty)|six(teen|ty)?|seven(teen|ty)?|eight(een|y)?|nine(teen|ty)?|ten|eleven)\s?)+', lambda m: "(%s) " % '+'.join(map(lambda n: str(r.get(n)), m.group().strip().split(' '))), s) if re.search('/[a-z]/', s): raise TimestringInvalid("Invalid characters in number string.") try: return eval(s) except SyntaxError: raise TimestringInvalid("Invalid number string.") else: return text
def plus(self, duration: Union[str, int, float, timedelta]): """ :return a new Date adjusted by the duration :param duration: int or float number of seconds or string of number and time unit. The number can begin with '-' to indicate subtraction """ if self.date == 'infinity': return if isinstance(duration, timedelta): return Date(self.date + duration) if isinstance(duration, (str, unicode)): duration = duration.lower().strip() res = TIMESTRING_RE.search(duration) if res: res = res.groupdict() sign = -1 if duration.startswith('-') else 1 num = res.get('num') unit = res.get('delta') or res.get('delta_2') return self.plus_(num, unit, sign) elif isinstance(duration, (float, int)): new = copy(self) new.date = new.date + timedelta(seconds=duration) return new raise TimestringInvalid('Invalid type for plus(): %s' % (type(duration)))
def adjust(self, to): ''' Adjusts the time from kwargs to timedelta **Will change this object** return new copy of self ''' if self.date == 'infinity': return new = copy(self) if type(to) in (str, unicode): to = to.lower() res = TIMESTRING_RE.search(to) if res: rgroup = res.groupdict() if (rgroup.get('delta') or rgroup.get('delta_2')): i = int(text2num(rgroup.get( 'num', 'one'))) * (-1 if to.startswith('-') else 1) delta = (rgroup.get('delta') or rgroup.get('delta_2')).lower() if delta.startswith('y'): try: new.date = new.date.replace(year=(new.date.year + i)) except ValueError: # day is out of range for month new.date = new.date + timedelta(days=(365 * i)) elif delta.startswith('month'): if (new.date.month + i) > 12: new.date = new.date.replace(month=(i - (i / 12)), year=(new.date.year + 1 + (i / 12))) elif (new.date.month + i) < 1: new.date = new.date.replace(month=12, year=(new.date.year - 1)) else: new.date = new.date.replace(month=(new.date.month + i)) elif delta.startswith('q'): # NP pass elif delta.startswith('w'): new.date = new.date + timedelta(days=(7 * i)) elif delta.startswith('s'): new.date = new.date + timedelta(seconds=i) else: new.date = new.date + timedelta( **{ ('days' if delta.startswith('d') else 'hours' if delta.startswith('h') else 'minutes' if delta.startswith('m') else 'seconds'): i }) return new else: new.date = new.date + timedelta(seconds=int(to)) return new raise TimestringInvalid('Invalid addition request')
def plus_(self, num: Union[str, int, float], unit: str, sign: int = 1): assert sign in [-1, 1] mag = get_num(num) n = sign * mag whole = int(n) fraction = n - whole unit = unit.lower().strip() new_date = copy(self.date) if unit.startswith('y'): try: new_date = new_date.replace(year=new_date.year + whole) new_date += timedelta(days=365 * fraction) except ValueError: # Leap date in a non-leap year new_date += timedelta(days=365 * n) elif unit.startswith('month'): try: month = new_date.month + whole new_date = new_date.replace(year=new_date.year + month // 12, month=abs(month) % 12) new_date += timedelta(days=30 * fraction) except ValueError: # No such day in that month new_date += timedelta(days=30 * n) elif unit.startswith('q'): # TODO This section is not working q1 = datetime(new_date.year, 1, 1) q2 = datetime(new_date.year, 4, 1) q3 = datetime(new_date.year, 7, 1) q4 = datetime(new_date.year, 10, 1) if q1 <= new_date < q2: if n == -1: new_date = datetime(new_date.year - 1, 10, 1) else: new_date = q2 elif q2 <= new_date < q3: pass elif q3 <= new_date < q4: pass else: pass new_date += timedelta(days=91 * n) else: _unit = TIMEDELTA_UNITS.get(unit[0]) if _unit: new_date += timedelta(**{_unit: n}) else: raise TimestringInvalid('Unknown time unit: ' + unit) return Date(new_date)
def get_num(num): """ :param num: int, float or string repersenting a number, such as '1.5' or 'one' """ if isinstance(num, (int, float)): return num if 'couple' in (num or ''): return 2 try: return float(num) except ValueError: try: return w2n.word_to_num(num or 'one') except ValueError: raise TimestringInvalid('Unknown number: %s' % num)
def __init__(self, date, offset=None, start_of_week=None, tz=None, verbose=False): if isinstance(date, Date): self.date = copy(date.date) return # The original request self._original = date if tz: tz = pytz.timezone(str(tz)) if date == 'infinity': self.date = 'infinity' elif date == 'now': self.date = datetime.now() elif type(date) in (str, unicode) and re.match( r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+-\d{2}", date): self.date = datetime.strptime( date[:-3], "%Y-%m-%d %H:%M:%S.%f") - timedelta(hours=int(date[-3:])) else: # Determinal starting date. if type(date) in (str, unicode): """The date is a string and needs to be converted into a <dict> for processesing """ _date = date.lower() res = TIMESTRING_RE.search(_date.strip()) if res: date = res.groupdict() if verbose: print( "Matches:\n", ''.join([ "\t%s: %s\n" % (k, v) for k, v in date.items() if v ])) else: raise TimestringInvalid('Invalid date string >> %s' % date) date = dict((k, v if type(v) is str else v) for k, v in date.items() if v) #print(_date, dict(map(lambda a: (a, date.get(a)), filter(lambda a: date.get(a), date)))) if isinstance(date, dict): # Initial date. new_date = datetime(*time.localtime()[:3]) if tz and tz.zone != "UTC": # # The purpose here is to adjust what day it is based on the timezeone # ts = datetime.now() # Daylight savings === second Sunday in March and reverts to standard time on the first Sunday in November # Monday is 0 and Sunday is 6. # 14 days - dst_start.weekday() dst_start = datetime( ts.year, 3, 1, 2, 0, 0) + timedelta(13 - datetime(ts.year, 3, 1).weekday()) dst_end = datetime( ts.year, 11, 1, 2, 0, 0) + timedelta(6 - datetime(ts.year, 11, 1).weekday()) ts = ts + tz.utcoffset(new_date, is_dst=(dst_start < ts < dst_end)) new_date = datetime(ts.year, ts.month, ts.day) if date.get('unixtime'): new_date = datetime.fromtimestamp(int( date.get('unixtime'))) # !number of (days|...) (ago)? elif date.get('num') and (date.get('delta') or date.get('delta_2')): if date.get('num', '').find('couple') > -1: i = 2 * int(1 if date.get('ago', True) or date.get('ref') == 'last' else -1) else: i = int(text2num(date.get( 'num', 'one'))) * int(1 if date.get('ago') or ( date.get('ref', '') or '') == 'last' else -1) delta = (date.get('delta') or date.get('delta_2')).lower() if delta.startswith('y'): try: new_date = new_date.replace(year=(new_date.year - i)) # day is out of range for month except ValueError: new_date = new_date - timedelta(days=(365 * i)) elif delta.startswith('month'): try: new_date = new_date.replace(month=(new_date.month - i)) # day is out of range for month except ValueError: new_date = new_date - timedelta(days=(30 * i)) elif delta.startswith('q'): ''' This section is not working... Most likely need a generator that will take me to the right quater. ''' q1, q2, q3, q4 = datetime( new_date.year, 1, 1), datetime(new_date.year, 4, 1), datetime( new_date.year, 7, 1), datetime(new_date.year, 10, 1) if q1 <= new_date < q2: # We are in Q1 if i == -1: new_date = datetime(new_date.year - 1, 10, 1) else: new_date = q2 elif q2 <= new_date < q3: # We are in Q2 pass elif q3 <= new_date < q4: # We are in Q3 pass else: # We are in Q4 pass new_date = new_date - timedelta(days=(91 * i)) elif delta.startswith('w'): new_date = new_date - timedelta(days=(i * 7)) else: new_date = new_date - timedelta( **{ ('days' if delta.startswith('d') else 'hours' if delta.startswith('h') else 'minutes' if delta.startswith('m') else 'seconds'): i }) # !dow if [ date.get(key) for key in ('day', 'day_2', 'day_3') if date.get(key) ]: dow = max([ date.get(key) for key in ('day', 'day_2', 'day_3') if date.get(key) ]) iso = dict(monday=1, tuesday=2, wednesday=3, thursday=4, friday=5, saturday=6, sunday=7, mon=1, tue=2, tues=2, wed=3, wedn=3, thu=4, thur=4, fri=5, sat=6, sun=7).get(dow) if iso: # determin which direction if date.get('ref') not in ('this', 'next'): days = iso - new_date.isoweekday() - ( 7 if iso >= new_date.isoweekday() else 0) else: days = iso - new_date.isoweekday() + ( 7 if iso < new_date.isoweekday() else 0) new_date = new_date + timedelta(days=days) elif dow == 'yesterday': new_date = new_date - timedelta(days=1) elif dow == 'tomorrow': new_date = new_date + timedelta(days=1) # !year year = [ int(CLEAN_NUMBER.sub('', date[key])) for key in ('year', 'year_2', 'year_3', 'year_4', 'year_5', 'year_6') if date.get(key) ] if year: year = max(year) if len(str(year)) != 4: year += 2000 if year <= 40 else 1900 try: new_date = new_date.replace(year=year) except Exception: new_date = new_date.replace(year=year, day=28) # !month month = [ date.get(key) for key in ('month', 'month_1', 'month_2', 'month_3', 'month_4') if date.get(key) ] if month: new_date = new_date.replace(day=1) new_date = new_date.replace( month=int(max(month)) if re.match('^\d+$', max(month)) else dict(january=1, february=2, march=3, april=4, june=6, july=7, august=8, september=9, october=10, november=11, december=12, jan=1, feb=2, mar=3, apr=4, may=5, jun=6, jul=7, aug=8, sep=9, sept=9, oct=10, nov=11, dec=12).get(max(month), new_date.month)) # !day day = [ date.get(key) for key in ('date', 'date_2', 'date_3') if date.get(key) ] if day: new_date = new_date.replace(day=int(max(day))) # !daytime if date.get('daytime'): if date['daytime'].find('this time') >= 1: new_date = new_date.replace( hour=datetime(*time.localtime()[:5]).hour, minute=datetime(*time.localtime()[:5]).minute) else: new_date = new_date.replace(hour=dict( morning=9, noon=12, afternoon=15, evening=18, night=21, nighttime=21, midnight=24).get(date.get('daytime'), 12)) # No offset because the hour was set. offset = False # !hour hour = [ date.get(key) for key in ('hour', 'hour_2', 'hour_3') if date.get(key) ] if hour: new_date = new_date.replace(hour=int(max(hour))) am = [ date.get(key) for key in ('am', 'am_1') if date.get(key) ] if am and max(am) in ('p', 'pm'): h = int(max(hour)) if h < 12: new_date = new_date.replace(hour=h + 12) # No offset because the hour was set. offset = False #minute minute = [ date.get(key) for key in ('minute', 'minute_2') if date.get(key) ] if minute: new_date = new_date.replace(minute=int(max(minute))) #second seconds = date.get('seconds', 0) if seconds: new_date = new_date.replace(second=int(seconds)) self.date = new_date elif type(date) in (int, long, float) and re.match( '^\d{10}$', str(date)): self.date = datetime.fromtimestamp(int(date)) elif isinstance(date, datetime): self.date = date elif date is None: self.date = datetime.now() else: # Set to the current date Y, M, D, H0, M0, S0 self.date = datetime(*time.localtime()[:3]) if tz: self.date = self.date.replace(tzinfo=tz) # end if type(date) is types.DictType: and self.date.hour == 0: if offset and isinstance(offset, dict): self.date = self.date.replace(**offset)
def __init__(self, start, end=None, offset=None, start_of_week=0, tz=None, verbose=False): """`start` can be type <class timestring.Date> or <type str> """ self._dates = [] pgoffset = None if start is None: raise TimestringInvalid("Range object requires a start valie") if not isinstance(start, (Date, datetime)): start = str(start) if end and not isinstance(end, (Date, datetime)): end = str(end) if start and end: """start and end provided """ self._dates = (Date(start, tz=tz), Date(end, tz=tz)) elif start == 'infinity': # end was not provided self._dates = (Date('infinity'), Date('infinity')) elif re.search(r'(\s(and|to)\s)', start): """Both sides where provided in the start """ start = re.sub('^(between|from)\s', '', start.lower()) # Both arguments found in start variable r = tuple(re.split(r'(\s(and|to)\s)', start.strip())) self._dates = (Date(r[0], tz=tz), Date(r[-1], tz=tz)) elif re.match( r"(\[|\()((\"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(\.\d+)?(\+|\-)\d{2}\")|infinity),((\"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(\.\d+)?(\+|\-)\d{2}\")|infinity)(\]|\))", start): """postgresql tsrange and tstzranges support """ start, end = tuple( re.sub('[^\w\s\-\:\.\+\,]', '', start).split(',')) self._dates = (Date(start), Date(end)) else: now = datetime.now() # no tz info but offset provided, we are UTC so convert if re.search(r"(\+|\-)\d{2}$", start): # postgresql tsrange and tstzranges pgoffset = re.search(r"(\+|\-)\d{2}$", start).group() + " hours" # tz info provided if tz: now = now.replace(tzinfo=pytz.timezone(str(tz))) # Parse res = TIMESTRING_RE.search(start) if res: group = res.groupdict() if verbose: print( dict( map(lambda a: (a, group.get(a)), filter(lambda a: group.get(a), group)))) if (group.get('delta') or group.get('delta_2')) is not None: delta = (group.get('delta') or group.get('delta_2')).lower() # always start w/ today start = Date("today", offset=offset, tz=tz) # make delta di = "%s %s" % (str(int(group['num'] or 1)), delta) # this [ x ] if group['ref'] == 'this': if delta.startswith('y'): start = Date(datetime(now.year, 1, 1), offset=offset, tz=tz) # month elif delta.startswith('month'): start = Date(datetime(now.year, now.month, 1), offset=offset, tz=tz) # week elif delta.startswith('w'): start = Date("today", offset=offset, tz=tz) - (str( Date("today", tz=tz).date.weekday()) + ' days') # day elif delta.startswith('d'): start = Date("today", offset=offset, tz=tz) # hour elif delta.startswith('h'): start = Date("today", offset=dict(hour=now.hour + 1), tz=tz) # minute, second elif delta.startswith('m') or delta.startswith('s'): start = Date("now", tz=tz) else: raise TimestringInvalid( "Not a valid time reference") end = start + di #next x [ ] elif group['ref'] == 'next': if int(group['num'] or 1) > 1: di = "%s %s" % (str(int(group['num'] or 1) - 1), delta) end = start + di # ago [ ] x elif group.get('ago') or group['ref'] == 'last' and int( group['num'] or 1) == 1: #if group['ref'] == 'last' and int(group['num'] or 1) == 1: # start = start - ('1 ' + delta) end = start - di # last & no ref [ x] else: # need to include today with this reference if not (delta.startswith('h') or delta.startswith('m') or delta.startswith('s')): start = Range('today', offset=offset, tz=tz).end end = start - di elif group.get('month_1'): # a single month of this yeear start = Date(start, offset=offset, tz=tz) start = start.replace(day=1) end = start + '1 month' elif group.get('year_5'): # a whole year start = Date(start, offset=offset, tz=tz) start = start.replace(day=1, month=1) end = start + '1 year' else: # after all else, we set the end to + 1 day start = Date(start, offset=offset, tz=tz) end = start + '1 day' else: raise TimestringInvalid("Invalid timestring request") if end is None: # no end provided, so assume 24 hours end = start + '24 hours' if start > end: # flip them if this is so start, end = copy(end), copy(start) if pgoffset: start = start - pgoffset if end != 'infinity': end = end - pgoffset self._dates = (start, end) if self._dates[0] > self._dates[1]: self._dates = (self._dates[0], self._dates[1] + '1 day')
def __init__(self, date=None, offset: dict = None, tz: str = None, now: datetime = None, verbose=False, context=None): self._original = date if tz: tz = pytz.timezone(str(tz)) else: tz = None if not now: now = datetime.now(tz) if isinstance(date, Date): self.date = copy(date.date) elif isinstance(date, datetime): self.date = date elif date == 'now' or date is None: self.date = now elif date == 'infinity': self.date = 'infinity' elif isinstance(date, (str, unicode)) and re.match( r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+-\d{2}", date): self.date = datetime.strptime( date[:-3], "%Y-%m-%d %H:%M:%S.%f") - timedelta(hours=int(date[-3:])) elif isinstance(date, (str, unicode, dict)): if type(date) in (str, unicode): # Convert the string to a dict _date = date.lower() res = TIMESTRING_RE.search(_date.strip()) if res: date = res.groupdict() if verbose: print( "Matches:\n", ''.join([ "\t%s: %s\n" % (k, v) for k, v in date.items() if v ])) else: raise TimestringInvalid('Invalid date string: %s' % date) date = dict((k, v if type(v) is str else v) for k, v in date.items() if v) new_date = copy(now) # TODO Refactor if isinstance(date, dict): # This will always be True unit = date.get('delta') or date.get('delta') num = date.get('num') if date.get('unixtime'): new_date = datetime.fromtimestamp(int( date.get('unixtime'))) # Number of (days|...) [ago] elif num and unit: unit = unit.lower() if date.get('ago') or context == Context.PREV or date.get( 'prev'): sign = -1 elif date.get('in') or date.get( 'from_now') or context == Context.NEXT or date.get( 'next'): sign = 1 else: raise TimestringInvalid( 'Missing relationship such as "ago" or "from now"') new_date = Date(new_date).plus_(num, unit, sign).date weekday = date.get('weekday') relative_day = date.get('relative_day') if weekday: new_date = new_date.replace(hour=0, minute=0, second=0, microsecond=0) iso = WEEKDAY_ORDINALS.get(weekday) if iso is not None: days = iso - new_date.isoweekday() if date.get('prev') or context == Context.PREV: if iso >= new_date.isoweekday(): days -= 7 elif not (days == 0 and context in [Context.PAST, Context.FUTURE]): if iso <= new_date.isoweekday(): days += 7 new_date += timedelta(days=days) elif relative_day: days = RELATIVE_DAYS.get(re.sub(r'\s+', ' ', relative_day)) if days: new_date += timedelta(days=days) new_date = new_date.replace(hour=0, minute=0, second=0, microsecond=0) # !year year = [ int(CLEAN_NUMBER.sub('', date[key])) for key in ('year', 'year_2', 'year_3', 'year_4', 'year_5', 'year_6') if date.get(key) ] if year: if date.get('recurrence'): TimestringInvalid('"next" %s' % year) year = max(year) if len(str(year)) != 4: year += 2000 if year <= 40 else 1900 new_date = new_date.replace(year=year) # !month month = [ date.get(key) for key in ('month', 'month_1', 'month_2', 'month_3', 'month_4', 'month_5') if date.get(key) ] if month: month_ = max(month) if month_.isdigit(): month_ord = int(month_) if not 1 <= month_ord <= 12: raise TimestringInvalid( 'Month not in range 1..12:' + month_) else: month_ord = MONTH_ORDINALS.get(month_, new_date.month) new_date = new_date.replace(month=int(month_ord)) if year == []: if date.get('next') or context == Context.NEXT: if month_ord <= now.month: new_date = new_date.replace( year=new_date.year + 1) elif date.get('prev') or context == Context.PREV: if month_ord >= now.month: new_date = new_date.replace( year=new_date.year - 1) elif month_ord < now.month and not year: new_date = new_date.replace(year=new_date.year + 1) # !day day = [ date.get(key) for key in ('date', 'date_2', 'date_3', 'date_4') if date.get(key) ] if day: if date.get('recurrence'): TimestringInvalid('"next" %s' % day) new_date = new_date.replace(day=int(max(day))) # !daytime daytime = date.get('daytime') if daytime: if 'this time' not in daytime: _hour = DAYTIMES.get(date.get('daytime'), 12) new_date = new_date.replace(hour=_hour, minute=0, second=0, microsecond=0) # No offset because the hour was set. offset = False # !hour hour = [ date.get(key) for key in ('hour', 'hour_2', 'hour_3') if date.get(key) ] if hour: new_date = new_date.replace(hour=int(max(hour)), minute=0, second=0) am = [ date.get(key) for key in ('am', 'am_1') if date.get(key) ] if am and max(am) in ('p', 'pm'): h = int(max(hour)) if h < 12: new_date = new_date.replace(hour=h + 12) # No offset because the hour was set. offset = False minute = [ date.get(key) for key in ('minute', 'minute_2') if date.get(key) ] if minute: new_date = new_date.replace(minute=int(max(minute))) seconds = date.get('seconds', 0) if seconds: new_date = new_date.replace(second=int(seconds)) new_date = new_date.replace(microsecond=0) if not day and not relative_day and new_date < now: new_date += timedelta(days=1) if year != [] and not month and weekday is None and not day: new_date = new_date.replace(month=1) if (year != [] or month) and weekday is None and not (day or hour): new_date = new_date.replace(day=1) if not hour and daytime is None and not unit: new_date = new_date.replace(hour=0, minute=0, second=0) self.date = new_date else: raise TimestringInvalid('Invalid type for constructing Date') if offset and isinstance(offset, dict): self.date = self.date.replace(**offset)
def __init__(self, start: Union[int, str, long, float, datetime, Date], end: Union[datetime, Date] = None, offset: dict = None, week_start: int = 1, tz: str = None, verbose=False, context: Context = None): """`start` can be type <class timestring.Date> or <type str> """ self._dates = [] pgoffset = None if tz: tz = pytz.timezone(str(tz)) if start is None: raise TimestringInvalid("Range object requires a start value") if not isinstance(start, (Date, datetime)): start = str(start) if end and not isinstance(end, (Date, datetime)): end = str(end) if start and end: self._dates = (Date(start, tz=tz), Date(end, tz=tz)) elif start == 'infinity': self._dates = (Date('infinity'), Date('infinity')) elif re.search(r'(\s(and|to)\s)', start): # Both sides are provided in string "start" start = re.sub('^(between|from)\s', '', start.lower()) r = tuple(re.split(r'(\s(and|to)\s)', start.strip())) start = Date(r[0], tz=tz) self._dates = start, Date(r[-1], now=start.date) elif POSTGRES_RANGE_RE.match(start): # Postgresql tsrange and tstzranges support start, end = re.sub('[^\w\s\-\:\.\+\,]', '', start).split(',') self._dates = Date(start), Date(end) else: now = datetime.now(tz) if re.search(r"(\+|\-)\d{2}$", start): # postgresql tsrange and tstzranges pgoffset = re.search(r"(\+|\-)\d{2}$", start).group() + " hours" # Parse res = TIMESTRING_RE.search(start) if res: group = res.groupdict() def g(*keys): return next((group.get(k) for k in keys if group.get(k) is not None), None) if verbose: print(dict(map(lambda a: (a, group.get(a)), filter(lambda a: group.get(a), group)))) if not group['this']: if group['since']: context = Context.PREV if group['until'] or group['by']: context = Context.NEXT delta = group.get('delta') or group.get('delta_2') if delta: delta = delta.lower().strip() num = group['num'] start = Date("now", offset=offset, tz=tz) end = None # ago [ ]( )x # from now x( )[ ] # in x( )[ ] if group['ago'] or group['from_now'] or group['in']: n = get_num(num or 1) whole = int(n) fraction = n - whole if verbose: print('ago or from_now or in') start = Date(res.string) if not re.match('(hour|minute|second)s?', delta): if not fraction: start = start.replace(hour=0, minute=0, second=0, microsecond=0) end = start.plus_(1, 'day') elif delta.startswith('hour'): if not fraction: start = start.replace(minute=0, second=0, microsecond=0) end = start + '1 hour' elif delta.startswith('minute'): if not fraction: start = start.replace(second=0, microsecond=0) end = start + '1 minute' else: end = start + '1 second' # "next 2 weeks", "the next hour" x[ ][ ] elif group['next'] and (group['num'] or group['article']): if verbose: print('next and (num or article)') end = start.plus_(num, delta) # "next week" ( x )[ ] elif group['next'] or (not group['this'] and context == Context.NEXT): if verbose: print('next or (not this and Context.NEXT)') this = Range('this ' + delta, offset=offset, tz=tz, week_start=week_start) if delta.startswith('weekend'): if 'now' in this: start, end = this.plus_(num, delta) else: start, end = this else: start = this.end end = start.plus_(num, delta) # "last 2 weeks", "the last hour" [ ][ ]x elif group['prev'] and (group['num'] or group['article']): if verbose: print('prev and (num or article)') end = start.plus_(num, delta, -1) # "last week" [ ]( x ) elif group['prev']: if verbose: print('prev') this = Range('this ' + delta, offset=offset, tz=tz, week_start=week_start) start = this.start.plus_(num, delta, -1) end = this.end.plus_(num, delta, -1) # "1 year", "10 days" till now elif num: if verbose: print('num') end = start.plus_(num, delta, -1) # this [ x ] elif group['this'] or not group['recurrence']: if verbose: print('this or not recurrence') start = Date(res.string, tz=tz) if delta.startswith('y'): start = start.replace(month=1, day=1, hour=0, minute=0, second=0, microsecond=0) elif delta.startswith('mo'): start = start.replace(day=1, hour=0, minute=0, second=0, microsecond=0) # weekend elif delta.startswith('weekend'): days = (WEEKEND_START_DAY - start.isoweekday + 7) % 7 start = Date(now + timedelta(days=days)) start = start.replace(hour=WEEKEND_START_HOUR, minute=0, second=0, microsecond=0) days = (WEEKEND_END_DAY + 7 - WEEKEND_START_DAY) % 7 end = Date(start.date + timedelta(days=days)) end = end.replace(hour=WEEKEND_END_HOUR) start = start.replace(**offset or {}) end = end.replace(**offset or {}) # week elif delta.startswith('w'): start.date -= timedelta(days=start.isoweekday - week_start % 7) start = start.replace(hour=0, minute=0, second=0, microsecond=0) elif delta.startswith('d'): start = start.replace(hour=0, minute=0, second=0, microsecond=0) elif delta.startswith('h'): start = start.replace(minute=0, second=0, microsecond=0) elif delta.startswith('m'): start = start.replace(second=0, microsecond=0) elif delta.startswith('s'): start = start.replace(microsecond=0) else: raise TimestringInvalid("Not a valid time reference") if offset: start = start.replace(**offset) if not end: end = start.plus_(num, delta) elif group['relative_day'] or group['weekday']: if verbose: print('relative_day or weekday') start = Date(res.string, offset=offset, tz=tz, context=context) end = start + '1 day' elif group.get('month_1'): if verbose: print('month_1') start = Date(res.string, offset=offset, tz=tz, context=context) start = start.replace(hour=0, minute=0, second=0) end = start + '1 month' elif group['date_5'] or group['date_6']: if verbose: print('date_5 or date_6') start = Date(res.string, offset=offset, tz=tz) year = g('year', 'year_2', 'year_3', 'year_4', 'year_5', 'year_6') month = g('month', 'month_2', 'month_3', 'month_4', 'month_5') day = g('date', 'date_2', 'date_3', 'date_4') if day: end = start + '1 day' elif month: end = start + '1 month' elif year is not None: end = start + '1 year' else: end = start if not isinstance(start, Date): start = Date(now) if group['time_2']: if verbose: print('time_2') temp = Date(res.string, offset=offset, now=start, tz=tz).date start = start.replace(hour=temp.hour, minute=temp.minute, second=temp.second) hour = g('hour', 'hour_2', 'hour_3') minute = g('minute', 'minute_2') second = g('seconds') if second: end = start + '1 second' elif minute: end = start + '1 minute' elif hour: end = start + '1 hour' else: end = start if group['since']: end = now elif group['until'] or group['by']: end = start start = now if start <= now <= end: if context == Context.PAST: end = now elif context == Context.FUTURE: start = now else: raise TimestringInvalid('Invalid range: %s' % start) if end is None: # no end provided, so assume 24 hours if isinstance(start, str): start = Date(start) end = start + '24 hours' if start > end: start, end = copy(end), copy(start) if pgoffset: start = start - pgoffset if end != 'infinity': end = end - pgoffset self._dates = (start, end) if self._dates[0] > self._dates[1]: self._dates = (self._dates[0], self._dates[1] + '1 day')