"""Utility functions""" from datetime import datetime, timedelta from typing import Tuple, Optional, List, Dict, Any import re from .constants import DATE_FORMAT, MONTH_FORMAT def parse_date_range(start_date: Optional[str] = None, end_date: Optional[str] = None, days: Optional[int] = None, last_week: bool = False, exclude_recent_days: int = 0) -> Tuple[datetime, datetime]: """解析日期范围参数 Args: start_date: 开始日期字符串 end_date: 结束日期字符串 days: 最近天数 last_week: 是否获取上周数据 exclude_recent_days: 排除最近的天数 Returns: (开始日期, 结束日期) 元组 """ today = datetime.now().date() # 处理排除最近天数 if exclude_recent_days > 0: effective_date = today - timedelta(days=exclude_recent_days) else: effective_date = today if last_week: # 从安全日期往前推7天(含安全日期当天) end = effective_date # 安全日期作为结束日期 start = end - timedelta(days=6) # 往前推6天,总共7天 return datetime.combine(start, datetime.min.time()), \ datetime.combine(end, datetime.max.time()) if days: # 最近N天 start = effective_date - timedelta(days=days) end = effective_date - timedelta(days=1) return datetime.combine(start, datetime.min.time()), \ datetime.combine(end, datetime.max.time()) if start_date and end_date: # 指定日期范围 - 不在此处调整日期,让 _get_safe_end_date 统一处理 start = datetime.strptime(start_date, DATE_FORMAT) end = datetime.strptime(end_date, DATE_FORMAT) return start, end # 默认返回前天 default_date = effective_date - timedelta(days=2) return datetime.combine(default_date, datetime.min.time()), \ datetime.combine(default_date, datetime.max.time()) def format_currency(amount: float, currency: str = 'USD') -> str: """格式化货币金额(使用整数格式) Args: amount: 金额 currency: 货币代码 Returns: 格式化后的货币字符串(整数) """ # 四舍五入到整数 amount_int = round(amount) if currency == 'USD': return f"${amount_int:,}" else: return f"{amount_int:,} {currency}" def format_percentage(value: float) -> str: """格式化百分比 Args: value: 百分比值 (如 1.5 表示150%) Returns: 格式化的百分比字符串 """ return f"{value * 100:.1f}%" def sanitize_filename(filename: str) -> str: """清理文件名,移除不安全字符 Args: filename: 原始文件名 Returns: 安全的文件名 """ # 移除或替换不安全字符 safe_name = re.sub(r'[<>:"/\\|?*]', '_', filename) safe_name = safe_name.strip('. ') return safe_name if safe_name else 'unnamed' def chunk_list(lst: List[Any], chunk_size: int) -> List[List[Any]]: """将列表分块 Args: lst: 要分块的列表 chunk_size: 每块大小 Returns: 分块后的列表 """ return [lst[i:i + chunk_size] for i in range(0, len(lst), chunk_size)] def merge_dicts(dict1: Dict, dict2: Dict) -> Dict: """深度合并两个字典 Args: dict1: 第一个字典 dict2: 第二个字典 (会覆盖dict1中的值) Returns: 合并后的字典 """ result = dict1.copy() for key, value in dict2.items(): if key in result and isinstance(result[key], dict) and isinstance(value, dict): result[key] = merge_dicts(result[key], value) else: result[key] = value return result def calculate_date_range_overlap(range1: Tuple[datetime, datetime], range2: Tuple[datetime, datetime]) -> Optional[Tuple[datetime, datetime]]: """计算两个日期范围的重叠部分 Args: range1: 第一个日期范围 (start, end) range2: 第二个日期范围 (start, end) Returns: 重叠的日期范围,如果没有重叠则返回None """ start = max(range1[0], range2[0]) end = min(range1[1], range2[1]) if start <= end: return (start, end) return None def format_date_for_display(date: datetime, format_type: str = 'date') -> str: """格式化日期用于显示 Args: date: 日期对象 format_type: 格式类型 ('date', 'month', 'datetime') Returns: 格式化的日期字符串 """ if format_type == 'month': return date.strftime(MONTH_FORMAT) elif format_type == 'datetime': return date.strftime('%Y-%m-%d %H:%M:%S') else: return date.strftime(DATE_FORMAT) def safe_float(value: Any, default: float = 0.0) -> float: """安全转换为浮点数 Args: value: 要转换的值 default: 转换失败时的默认值 Returns: 浮点数 """ try: return float(value) except (TypeError, ValueError): return default def safe_int(value: Any, default: int = 0) -> int: """安全转换为整数 Args: value: 要转换的值 default: 转换失败时的默认值 Returns: 整数 """ try: return int(value) except (TypeError, ValueError): return default