"""Date time utility functions.""" import datetime from datetime import timedelta from oto import response from analytics.consts import error from analytics.consts import models as model_consts def get(date_str, date_format='%Y-%m-%d'): """Create a date from a string. Datetime offers strptime which takes a string and a format. In most cases, we use the format Year-Month-Day as a value, so this method creates a handy shortcut for this method. Example:: from utils import date current_date = date.get('2014-10-10') Args: date_str (str): the date as a string. date_format (str): the date format. """ try: return datetime.datetime.strptime(date_str, date_format) except Exception: return None def create_empty_date_ranges(start_date, end_date): """Create empty date ranges. For all the dates between a starting date and an ending date, fill each day with 0. Args: start_date (str): date to start from. end_date (str): date to end. Returns: dict: contains all the dates between the two points. """ dates = {} start = get(start_date) delta = get(end_date) - start for day in range(delta.days + 1): dates[int((start + timedelta(days=day)).timestamp())] = 0 return dates def month_difference(start_date, end_date): """Return the number of months between two dates. Args: start_date (datetime): the datetime object that contains the date informations. end_date (datetime): similar to start_date. Returns: int: Number of months between two dates. """ return ( (end_date.year - start_date.year) * 12 - start_date.month + end_date.month) + 1 # +1 to include the last month def month_chunk(start_date, end_date, size=1): """Split a date range into tuples of x month(s). The goal of this method is to create chunk of date ranges that cover a period of time (defined by start date, end date). The size param defines the maximum number of months each date range should cover. Example:: for start_date, end_date in month_range('10/10/2014', '12/25/2014'): print(start_date, end_date) # prints: # (10/10/2014, 10/31/2014) # (11/1/2014, 12/1/2014) # (12/1/2014, 12/25/2014) # Using a size of 3 for start_date, end_date in month_range( '8/29/2014', '12/25/2014', size=3): print(start_date, end_date) # prints: # (8/29/2014, 10/31/2014) - 3 months (august, sept, oct) # (11/1/2014, 12/25/2014) - 2 remaining months (nov, dec) Args: start_date (datetime): the start date. end_date (datetime): the end date size (int): how many months should be in each chunk. Yield: tuple: first part contains the start date, second part contains the end date. """ date_range = dict(start_date=start_date) generator = generator_month_between_dates(start_date, end_date) # The first date returned by the generator is start_date, which is not # needed. generator.__next__() for i, current in enumerate(generator): date_range.update(end_date=current - timedelta(days=1)) if (i % size) + 1 == size: yield date_range.get('start_date'), date_range.get('end_date') date_range = dict(start_date=current) if date_range: yield date_range['start_date'], end_date def generator_month_between_dates(start_date, end_date): """Return a generator that returns each month between two dates. This generator comes handy for any operations that requires the list of two months between two dates. Args: start_date (datetime): the start date. end_date (datetime): the end date (included.) Yields: datetime: The current date + 1 month. """ current_year = start_date.year current_month = start_date.month current_date = start_date while current_date <= end_date: yield current_date current_month = current_month + 1 if current_month > 12: current_month = 1 current_year = current_year + 1 current_date = datetime.datetime(current_year, current_month, 1) def determine_date_period(start_date, end_date): """Determine the period between dates. Valid results are week, month, or year. This method is used to generate a part of the hash key, that is used to query DynamoDB streams placement table. Args: start_date (str): start date of a period end_date (str): end date of a period Returns: oto.Response: Response object, containing W for week period, M for month period, or Y for year period. """ # Get number of days in a week, month, and year week_days = 7 month_days = [28, 29, 30, 31] year_days = [366, 367, 372, 373] # 367 for leap years, 366 for other years, 372 and 373 for weekly summary start_date = get(start_date) end_date = get(end_date) days = (end_date - start_date).days + 1 if days == week_days: result = response.Response(model_consts.WEEK_PERIOD) elif days in month_days: result = response.Response(model_consts.MONTH_PERIOD) elif days in year_days: result = response.Response(model_consts.YEAR_PERIOD) else: result = response.create_error_response( error.VALIDATION_ERROR_CODE, error.INVALID_DATE_RANGE_MESSAGE) return result