import re from datetime import datetime, date, timedelta class DateFilter(object): """ Filtering tools for the dates input provided by the user """ def __init__(self, date_format): self.date_format = date_format def days(self, current_date, days, action='-'): """ Add/substract the given number of days from the given current_date """ delta = timedelta(days=days) if action == '-': new_date = current_date - delta else: new_date = current_date + delta new_date = new_date.replace(hour=0, minute=0, second=0) return new_date def days_ago(self, current_date, days): """ Return the date N days ago from the current date """ return self.days(current_date, days, '-') def days_future(self, current_date, days): """ Return the date N days in the future from the current date """ return self.days(current_date, days, '+') def months(self, current_date, months, action='-'): """ Add/substract the given number of months from the given current_date """ if action == '-': # start travelling in time, back to N months ago for n in range(months): current_date = current_date.replace(day=1) - timedelta(days=1) else: # start travelling in time, forward to N months for n in range(months): current_date = current_date.replace(day=1) + timedelta(days=1) # Now use the new year/month values + the current day to set the proper # date new_date = datetime( current_date.year, current_date.month, date.today().day) return new_date def months_ago(self, current_date, months): """ Return the date N days ago from the current date """ return self.months(current_date, months, '-') def months_future(self, current_date, months): """ Return the date N days in the future from the current date """ return self.months(current_date, months, '+') def parse_number_filter(self, number_filter, future=False): """ If a filter contains a number of days/months/years, try to find out which filter it is exactly to apply it, then calculate the number of days and find """ number = None filter_from = None if re.search(r'(\d+[dD]{1})', number_filter): number = int(number_filter.lower().replace('d', '')) if future: filtered_date = self.days_future(datetime.today(), number) else: filtered_date = self.days_ago(datetime.today(), number) elif re.search(r'(\d+[wW]{1})', number_filter): number = int(number_filter.lower().replace('w', '')) * 7 if future: filtered_date = self.days_future(datetime.today(), number) else: filtered_date = self.days_ago(datetime.today(), number) elif re.search(r'(\d+[mM]{1})', number_filter): number = int(number_filter.lower().replace('m', '')) if future: filtered_date = self.months_future(datetime.today(), number) else: filtered_date = self.months_ago(datetime.today(), number) elif re.search(r'(\d+[yY]{1})', number_filter): number = int(number_filter.lower().replace('y', '')) today = date.today() if future: year = today.year + number else: year = today.year - number filtered_date = datetime(year, today.month, today.day) return number, filtered_date def validate(self, stamp_filter): """ Validate a given filter. Filters can have the following notation: - %Y-%m-%d: Times recorded at a given date - %Y-%m-%d--%Y-%m-%d: Times recorded between two dates - *%Y-%m-%d: Times recorded up to a given date - %Y-%m-%d*: Times recorded from a given date - %Y-%m-%d+sN[d|w|m|y]: Times recorded since the given date up to N more days/weeks/months/years - N...N[d|w|m|y]: Times recorded N...N days/weeks/months/years ago Important: all date comparisons are made on datetime objects, using 00:00 as the time (first second of the given day). This means that for range filters, the first day is included, but the second day is not """ filter_from = None filter_to = None if stamp_filter is None: return filter_from, filter_to if '--' in stamp_filter: filter_from, filter_to = stamp_filter.split('--') filter_from = datetime.strptime(filter_from, self.date_format) filter_to = datetime.strptime(filter_to, self.date_format) elif stamp_filter.startswith('*'): filter_to = datetime.strptime(stamp_filter, '*'+self.date_format) filter_to = filter_to.replace(hour=0, minute=0, second=0) elif stamp_filter.endswith('*'): filter_from = datetime.strptime(stamp_filter, self.date_format+'*') filter_from = filter_from.replace(hour=0, minute=0, second=0) elif '+' in stamp_filter: # "+" filtering works with a date following by "+", a number and # a letter (d|w|m|y) which sets the number of days, months, years # we would like to go into the future filter_to, number = stamp_filter.split('+') filter_to = datetime.strptime(filter_to, self.date_format) number, filter_from = self.parse_number_filter(number) filter_from = self.days_ago(filter_to, int(number)) else: # Check if the user is asking for N days/weeks/months/years number, filter_from = self.parse_number_filter(stamp_filter) if number is None: # no filtering found, maybe they are giving us a fixed date try: filter_from = datetime.strptime(stamp_filter, self.date_format) except: # nothing to be used as a filter, go on, printing a warning print('[warning] invalid date filter: ' + stamp_filter) else: filter_from = filter_from.replace(hour=0, minute=0, second=0) filter_to = filter_from + timedelta(days=1) return filter_from, filter_to