如何用Django ORM计算两日期间周末天数并调整截止日期
调整Django ORM截止日期计算逻辑,跳过周末仅按工作日计算
原代码直接将days_to_post转为时长叠加到approved_at上,会把周六、周日纳入截止日期计算,不符合仅按工作日统计的需求。以下是修改后的ORM查询逻辑,通过统计目标时间段内的周末天数,自动调整总天数来跳过周末:
from django.db.models import Case, When, F, ExpressionWrapper, IntegerField, DateTimeField, DurationField from django.db.models.functions import ExtractWeekDay, Extract import timezone # 假设DEFAULT_DAYS_TO_POST是你定义的默认工作日数 Article.objects.annotate( # 计算不跳过周末的临时截止日期,用于确定统计周末的时间范围 temp_due_date=ExpressionWrapper( F('approved_at') + Case( When(days_to_post__isnull=False, then=F('days_to_post') * timezone.timedelta(days=1)), default=timezone.timedelta(days=DEFAULT_DAYS_TO_POST), output_field=DurationField() ), output_field=DateTimeField() ), # 统计temp_due_date与approved_at之间的周末天数 weekend_days=Case( When( days_to_post__isnull=False, then=ExpressionWrapper( # 总天数减去工作日数,得到周末天数 Extract(F('temp_due_date') - F('approved_at'), 'days') - ( # 完整周的工作日数:总周数*5 (Extract(F('temp_due_date') - F('approved_at'), 'days') // 7) * 5 + Case( # 情况1:起始周几 <= 结束周几,未跨周末 When( ExtractWeekDay(F('approved_at')) <= ExtractWeekDay(F('temp_due_date')), then=Case( # 结束日是周末,工作日算到周五 When(ExtractWeekDay(F('temp_due_date')) >= 6, then=5 - ExtractWeekDay(F('approved_at')) + 1), # 起始和结束都是工作日,直接算差值+1 When(ExtractWeekDay(F('approved_at')) <= 5, then=ExtractWeekDay(F('temp_due_date')) - ExtractWeekDay(F('approved_at')) + 1), default=0, output_field=IntegerField() ) ), # 情况2:起始周几 > 结束周几,跨了至少一个周末 default=Case( # 起始日是工作日,算到周五的天数 When(ExtractWeekDay(F('approved_at')) <=5, then=5 - ExtractWeekDay(F('approved_at')) +1), # 结束日是工作日,算从周一到结束日的天数 When(ExtractWeekDay(F('temp_due_date')) <=5, then=ExtractWeekDay(F('temp_due_date'))), default=0, output_field=IntegerField() ) + (Extract(F('temp_due_date') - F('approved_at'), 'days') //7 -1)*5 ) ), output_field=IntegerField() ) ), default=0, output_field=IntegerField() ), # 计算实际需要叠加的总天数:原工作日数 + 需跳过的周末天数 actual_days=Case( When(days_to_post__isnull=False, then=F('days_to_post') + F('weekend_days')), default=DEFAULT_DAYS_TO_POST + F('weekend_days'), output_field=IntegerField() ), # 最终仅按工作日计算的截止日期 due_date=ExpressionWrapper( F('approved_at') + F('actual_days') * timezone.timedelta(days=1), output_field=DateTimeField() ), # 保留原有的截止日期时长和剩余天数计算 due_date_duration=ExpressionWrapper( F('due_date') - timezone.now(), output_field=DurationField() ), due_days_=Extract('due_date_duration', 'days'), )
关键逻辑说明
- temp_due_date:先算出不考虑周末的临时截止日期,用来划定统计周末天数的时间范围。
- weekend_days:通过「总天数 - 工作日数」得到时间段内的周末数量,分两种场景处理跨周情况:
- 起始日和结束日在同一周内:直接计算中间的工作日数量。
- 起始日和结束日跨周:拆分计算第一周剩余工作日、最后一周工作日,加上中间完整周的工作日数。
- actual_days:把原工作日数和需要跳过的周末天数相加,得到实际要叠加的总天数,确保跳过所有周末。
- due_date:用审核通过日期加上实际总天数,得到符合需求的工作日截止日期。
示例验证
如果approved_at是周五(周5),days_to_post=3:
- temp_due_date = 周五 + 3天 = 周一
- 这段时间内的周末天数是2(周六、周日)
- actual_days = 3 + 2 = 5
- due_date = 周五 +5天 = 下周三,完全符合需求。
内容的提问来源于stack exchange,提问作者Moin Khan
相关产品推荐
相关产品推荐

