import re from datetime import datetime, timedelta from typing import List import pytz from slz_api_service.errors import Codes, RangeError from slz_api_service.v1.constants import APPLE_REPORT_TYPE_MAP from slz_api_service.v1.enums import DateConfig class DateUtils: """Static date utility functions""" @staticmethod def date_range(start: str, end: str, fmt: str = DateConfig.FormatString.value) -> List[str]: """Pass two date strings to this method to build a range of dates Args: start: start date as string end: end date as string fmt: format for parsing datetime input strings and formatting datetime output strings Returns: a list of all date strings between ``start`` and ``end``, inclusively """ start_date = datetime.strptime(start, fmt) end_date = datetime.strptime(end, fmt) if not start_date <= end_date: raise RangeError({ 'code': Codes.invalid_input.value, 'description': 'Range start_date must be less than or equal to end_date', }) # probably faster to do the iter via total (delta) days instead of 1..n date comparisons delta: timedelta = end_date - start_date dates = [] for x in range(delta.days + 1): date_str = (start_date + timedelta(days=x)).strftime(fmt) dates.append(date_str) return dates @staticmethod def today() -> datetime: """Better than calling ``datetime.today()``, this includes tzinfo """ return datetime.now(tz=pytz.utc) @staticmethod def as_str(date: datetime, fmt: str = DateConfig.FormatString.value) -> str: """Formats a date as a string using config format as a default Args: date: datetime object fmt: format string for ouput Returns: string of date in specified format """ return date.strftime(fmt) @staticmethod def from_str(date: str, fmt: str = DateConfig.FormatString.value) -> datetime: """Create a datetime object from a string using config format as a default and adding tz Args: date: a date string fmt: format of the date string Returns: datetime: a datetime object """ return datetime.strptime(date, fmt).replace(tzinfo=pytz.utc) @staticmethod def before(date1: datetime, date2: datetime) -> bool: """Compares if ``date1`` occurs before ``date2`` with support for mixed offset-aware and offset-naïve dates Args: date1: datetime object date2: datetime object Returns: bool: True if ``date1`` occurs before ``date2`` """ return date1.replace(tzinfo=pytz.utc) < date2.replace(tzinfo=pytz.utc) class QueryUtils: """Static data and query utility functions""" @staticmethod def get_uow_id(dsp: str, report_date: str, report_licensor: str, report_type: str, version: str): if dsp == 'apple': report_type = APPLE_REPORT_TYPE_MAP.get(report_type, report_type) return f"{dsp}-{report_date.replace('-', '')}-{report_licensor}-{report_type}-{version}" class StringUtils: """Static string utility functions""" @staticmethod def camel_to_snake_case(name: str) -> str: """Converts a camelCaseString to a snake_case_string Args: name: string to convert Returns: a snake_case_string """ pattern = r'(.+?)([A-Z0-9])' snake_case = re.sub(pattern, r'\1_\2', name) deduped = snake_case.replace('__', '_') return deduped.lower()