如何确定指定timestamp与格式字符串对应的datetime取值范围
实现方案
核心思路
- 直接用
datetime.strptime()解析得到时间范围的最小值(下界),这部分符合原生方法的默认逻辑,无需修改。 - 用带负向断言的正则表达式提取格式字符串中所有非转义的格式码,自动跳过
%%转义的字面量百分号场景,不需要手动全量解析格式串。 - 根据提取到的格式码判断当前时间的最高精度,基于最高精度计算上界:最小值加1个对应精度单位,再减去1个最小时间单位(微秒/秒可根据需求自定义)。
代码实现(依赖dateutil处理年月偏移)
该方案用dateutil.relativedelta处理年月级别的偏移,避免了手动处理闰年、月末等边界场景的问题,稳定性更高。
from datetime import datetime from dateutil.relativedelta import relativedelta import re # 格式码对应精度优先级,数值越大精度越高 FORMAT_PRECISION = { # 年相关格式码 'Y': 1, 'y': 1, # 月相关格式码 'm': 2, 'b': 2, 'B': 2, # 日相关格式码 'd': 3, 'j': 3, 'w': 3, 'U': 3, 'W': 3, # 时相关格式码 'H': 4, 'I': 4, 'p': 4, # 分相关格式码 'M': 5, # 秒相关格式码 'S': 6, # 微秒相关格式码 'f': 7 } # 精度对应的偏移参数 PRECISION_DELTA = { 1: {'years': 1}, 2: {'months': 1}, 3: {'days': 1}, 4: {'hours': 1}, 5: {'minutes': 1}, 6: {'seconds': 1}, 7: {'microseconds': 1} } def get_datetime_range(timestamp: str, fmt: str) -> tuple[datetime, datetime]: # 计算下界 min_dt = datetime.strptime(timestamp, fmt) # 提取所有非转义的格式码,自动跳过%%转义场景 format_codes = re.findall(r'(?<!%)%([a-zA-Z])', fmt) # 确定最高精度 max_precision = max(FORMAT_PRECISION[code] for code in format_codes if code in FORMAT_PRECISION) # 计算上界:加1个精度单位后减1微秒,不需要微秒可改成减1秒 delta = relativedelta(**PRECISION_DELTA[max_precision]) max_dt = min_dt + delta - relativedelta(microseconds=1) return min_dt, max_dt
测试示例
# 示例1:年精度 print(get_datetime_range('2020', '%Y')) # 输出:(datetime.datetime(2020, 1, 1, 0, 0), datetime.datetime(2020, 12, 31, 23, 59, 59, 999999)) # 示例2:日精度 print(get_datetime_range('2020-01-01', '%Y-%m-%d')) # 输出:(datetime.datetime(2020, 1, 1, 0, 0), datetime.datetime(2020, 1, 1, 23, 59, 59, 999999)) # 示例3:带%%转义的格式 print(get_datetime_range('2020%05', '%Y%%%m')) # 格式串实际匹配"年+%+月",精度为月,输出:(datetime.datetime(2020, 5, 1, 0, 0), datetime.datetime(2020, 5, 31, 23, 59, 59, 999999))
无第三方依赖的简化实现
如果不想引入dateutil依赖,可以手动处理年月偏移,其他精度直接用原生timedelta计算即可:
from datetime import datetime, timedelta import re FORMAT_PRECISION = { 'Y': 1, 'y': 1, 'm': 2, 'b': 2, 'B': 2, 'd': 3, 'j': 3, 'w': 3, 'U': 3, 'W': 3, 'H': 4, 'I':4, 'p':4, 'M':5, 'S':6, 'f':7 } def get_datetime_range(timestamp: str, fmt: str) -> tuple[datetime, datetime]: min_dt = datetime.strptime(timestamp, fmt) format_codes = re.findall(r'(?<!%)%([a-zA-Z])', fmt) max_precision = max(FORMAT_PRECISION[code] for code in format_codes if code in FORMAT_PRECISION) if max_precision == 1: next_dt = min_dt.replace(year=min_dt.year + 1) elif max_precision == 2: if min_dt.month == 12: next_dt = min_dt.replace(year=min_dt.year + 1, month=1) else: next_dt = min_dt.replace(month=min_dt.month + 1) elif max_precision == 3: next_dt = min_dt + timedelta(days=1) elif max_precision == 4: next_dt = min_dt + timedelta(hours=1) elif max_precision == 5: next_dt = min_dt + timedelta(minutes=1) elif max_precision == 6: next_dt = min_dt + timedelta(seconds=1) else: next_dt = min_dt + timedelta(microseconds=1) max_dt = next_dt - timedelta(microseconds=1) return min_dt, max_dt
内容的提问来源于stack exchange,提问作者Adam Stewart
相关产品推荐
相关产品推荐

