如何设计可基于传入参数执行两类计算的业务日期时间服务类?
嘿,刚好我最近也在做类似的业务日期时间计算器服务类,结合你的需求,分享一下我梳理的设计思路和实现要点:
核心设计框架
这个服务类的核心是把自定义规则(工作排班、节假日)和计算逻辑解耦,让所有计算方法都基于初始化时传入的规则运行,这样扩展性和灵活性拉满。
初始化参数(重中之重)
初始化时必须传入两个核心参数,这是所有计算的基础:
- 自定义工作时长:用字典
work_hours来存储,键是星期的编号(建议用datetime.weekday()的返回值,0=周一,6=周日),值是该星期对应的工作时段列表。比如周二到周四的排班可以写成:
这样还能支持单日多时段的场景,比如带午休的排班:work_hours = { 1: [(time(9, 0), time(18, 0))], # 周二 2: [(time(9, 0), time(18, 0))], # 周三 3: [(time(9, 0), time(17, 0))] # 周四(提前1小时下班) }[(time(9,0), time(12,0)), (time(13,0), time(18,0))] - 节假日集合:用
Set[date]来存储所有非工作日的日期,比如法定节假日、公司自定义假期,这样判断的时候能快速O(1)查询。
核心功能方法实现
我把几个核心需求拆解成了独立的方法,每个方法都依赖基础的工作日判断逻辑:
1. 判断给定日期是否处于工作时段
这个是最基础的方法,分两步校验:
- 先确认该日期是工作日(在
work_hours中有对应排班,且不在节假日集合里) - 再检查给定的时间是否落在该日的任意一个工作时段内
代码示例大概是这样:
def is_in_work_hours(self, target_datetime: datetime) -> bool: target_date = target_datetime.date() # 先判断是不是工作日 if target_date.weekday() not in self.work_hours or target_date in self.holidays: return False target_time = target_datetime.time() # 遍历当天的所有工作时段 for start, end in self.work_hours[target_date.weekday()]: if start <= target_time <= end: return True return False
2. 计算给定日期后X个工作日的日期
这里要注意,如果起始时间在工作日的工作时段内,是否要考虑剩余时长?我默认实现的是返回第X个完整工作日的起始时间,逻辑如下:
- 从起始日期的下一天开始遍历
- 每遇到一个工作日(非节假日+有排班)就计数减1
- 计数归0时,返回该工作日的第一个工作时段起始时间
代码示例:
def add_work_days(self, start_datetime: datetime, days: int) -> datetime: if days <= 0: return start_datetime current_date = start_datetime.date() remaining_days = days while remaining_days > 0: current_date = current_date.replace(day=current_date.day + 1) # 检查是否是工作日 if current_date.weekday() in self.work_hours and current_date not in self.holidays: remaining_days -= 1 # 返回目标工作日的第一个工作时段起始时间 first_work_start = self.work_hours[current_date.weekday()][0][0] return datetime.combine(current_date, first_work_start)
如果需要支持“按工作时长累加”(比如起始时间是周三下午3点,加4小时工作时长可能到周四上午),可以扩展这个方法,引入工作时长的计算逻辑。
3. 获取下一个工作日的起始时间
这个方法要分两种情况处理:
- 如果当前时间在工作日的未开始的工作时段之前,返回当天该时段的起始时间
- 如果当天的工作时段都过了,或者当前日期是非工作日,就往后找第一个工作日,返回其第一个工作时段的起始时间
代码示例:
def get_next_workday_start(self, current_datetime: datetime) -> datetime: current_date = current_datetime.date() current_time = current_datetime.time() # 先检查当天是否还有未开始的工作时段 if current_date.weekday() in self.work_hours and current_date not in self.holidays: for start, end in self.work_hours[current_date.weekday()]: if start > current_time: return datetime.combine(current_date, start) # 当天没机会了,找下一个工作日 next_date = current_date while True: next_date = next_date.replace(day=next_date.day + 1) if next_date.weekday() in self.work_hours and next_date not in self.holidays: first_start = self.work_hours[next_date.weekday()][0][0] return datetime.combine(next_date, first_start)
关键细节处理
- 非标准排班兼容:因为用字典存储星期到时段的映射,不管是周二到周四上班,还是周末单休,都能直接通过调整
work_hours的键来实现,完全不需要修改计算逻辑 - 节假日动态更新:如果节假日是动态生成的(比如每年的法定节假日),可以把初始化参数改成接收一个生成节假日集合的函数,或者提供一个
update_holidays方法来动态更新 - 时间边界处理:要注意
time类型的比较,比如刚好是上班/下班时间点,要判定为属于工作时段(可根据业务需求灵活调整)
内容的提问来源于stack exchange,提问作者Drew
相关产品推荐
相关产品推荐

