我能想到至少两种方法来解决您的问题。
一个(相当复杂的)查询:
month_start = datetime(year, month, 1, 0, 0, 0, 0, tz);
next_month = (month % 12) + 1
next_month_start = datetime(year, next_month, 1, 0, 0, 0, 0, tz)
models.InOut.objects.filter(
(
Q(in_dt__gte=month_start) and Q(in_dt__lt=next_month_start))
| (Q(out_dt__gte=month_start) and Q(out_dt__lt=next_month_start)
)
).annotate(
start_in_month=Func(F('in_dt'), month_start, function='MAX'),
end_in_month=Func(F('out_dt'), month_end, function='MIN')
).aggregate(worked=Sum(F('end_in_month') - F('start_in_month'))
如果使用PostgreSQL你需要使用
.annotate(
start_in_month=Func(F('in_dt'), month_start, function='GREATEST'),
end_in_month=Func(F('out_dt'), month_end, function='LEAST')
)
因为在 PostgreSQL 中 MAX() 和 MIN() 没有为日期类型定义。
还要注意聚合在 SQLite 上不起作用,因为它没有适当的数据类型(日期存储为文本)。
预处理条目
在您的数据库中,跨越月份边界的 InOut 条目在逻辑上(而非物理上)是两个条目:
- 从指定时间开始,到月底结束
- 月末开始,指定时间结束
过滤掉受影响的InOut 对象需要一些思考,特别是因为F() 对象(当前)无法解析部分日期时间(例如in_dt__month)。
一些东西
# XXX - magic number of months
for month in range(1, 13):
for wraparound in models.InOut.objects.filter(
Q(in_dt__month=month) and ~Q(out_dt__month=month)
)
year = wraparound.in_dt.year
next_month = (month % 12) + 1
month_end = datetime(year, next_month, calendar.monthrange(year, month)[1], 23, 59, 59, 999999, tz)
next_month_start = datetime(year, next_month, 1, 0, 0, 0, 0, tz)
models.InOut.objects.bulk_create([
models.InOut(user=wraparound.user, in_dt=wraparound.in_dt, out_dt=month_end),
models.InOut(user=wraparound.user, in_dt=next_month_start, out_dt=wraparound.out_dt)
])
wraparound.delete()
然而,可以做到这一点。
理想情况下,您不要在之后执行此操作,而是在保存视图中的时间条目时执行此操作。然而,这可能会让用户感到困惑,因为他们现在在输入环绕工作跨度时会得到两个条目而不是一个。
购买者警告:您可能需要在 next_month、next_month_start 和 __lt 以及 __gte 之间徘徊,因为这
扩展后,实现在每个环绕结束时损失一微秒。
是的,这是一个很好的练习;-)