Django如何基于DateTimeField字段计算每周、每月的总打卡时长
前置说明
默认你的inout字段业务规则是:1代表上班/入场打卡,0代表下班/离场打卡。如果你的实际规则不同,调整下方案里的对应过滤条件即可。
核心实现思路
打卡时长统计核心逻辑是先把同一用户的上下班打卡记录配对,计算出有效时长后再按周/月维度聚合,具体实现步骤如下:
步骤1:计算单日有效打卡时长
先按用户、自然天分组,取出每天最早的上班打卡和最晚的下班打卡,过滤掉只有上班/只有下班的无效单边记录,计算出单日有效时长:
import models from django.db.models import F, ExpressionWrapper, DurationField, Sum, Min, Max from django.db.models.functions import TruncDate, TruncWeek, TruncMonth # 按用户+日期分组计算单日有效时长 daily_records = Clockin_Transaction.objects.values( "userid", "clockinusers", date=TruncDate("datetime") ).annotate( # 取当日最早的上班打卡时间 first_clockin=Min("datetime", filter=models.Q(inout=1)), # 取当日最晚的下班打卡时间 last_clockout=Max("datetime", filter=models.Q(inout=0)) ).filter( # 过滤无效单边打卡记录 first_clockin__isnull=False, last_clockout__isnull=False ).annotate( # 计算当日打卡时长 daily_duration=ExpressionWrapper( F("last_clockout") - F("first_clockin"), output_field=DurationField() ) )
如果不需要按用户拆分统计,只看全平台总时长,把values里的userid、clockinusers字段删掉即可
步骤2:按周统计总打卡小时
基于上述单日时长结果,按周维度聚合求和:
weekly_stats = daily_records.values( "userid", "clockinusers", week=TruncWeek("date") ).annotate( total_duration=Sum("daily_duration") ).order_by("-week") # 把时长转成小时单位 for item in weekly_stats: item["total_hours"] = item["total_duration"].total_seconds() / 3600
TruncWeek默认以周一为一周的起始,如果你的业务规则是周日为周起始,需要调整Django时区配置或者数据库周起始规则
步骤3:按月统计总打卡小时
和按周统计逻辑完全一致,把分组维度换成TruncMonth即可:
monthly_stats = daily_records.values( "userid", "clockinusers", month=TruncMonth("date") ).annotate( total_duration=Sum("daily_duration") ).order_by("-month") # 转成小时单位 for item in monthly_stats: item["total_hours"] = item["total_duration"].total_seconds() / 3600
优化建议
- 如果数据量超过10万条,建议单独建日/周/月统计结果表,用定时任务每天凌晨更新统计结果,避免每次查询都全量计算拖慢性能
- 如果存在跨天加班场景(比如当日23点上班、次日凌晨5点下班),需要单独写逻辑处理跨天的打卡配对,不要直接按自然天截断记录
内容的提问来源于stack exchange,提问作者user11766958
相关产品推荐
相关产品推荐

