【问题标题】:django filter to calculate hours within rangedjango过滤器计算范围内的小时数
【发布时间】:2015-07-14 20:17:58
【问题描述】:

我有一组数据有效地定义了日期时间戳“输入”和日期时间戳“输出”,用于表示某人工作的时间。每个人在数月内都会有多种进出组合。

class InOut(models.Model):
    user = models.ForeignKey(User)
    in_dt = models.DateTime
    out_dt = models.DateTime
    hours = models.FloatField

(那么我现在实际上有一个信号可以计算 out_dt 和 in_dt 之间的工作时间。)

我想编写代码/过滤器/查询来计算他们在 start_date 和 end_date 之间工作的总小时数(例如,超过一个月)。关键是如果他们在一个月的最后一天开始工作,然后在下个月的第一天结束,那么小时数应该只包括当月最后一天午夜之前的小时数。

现在,我可以创建一个查询集,用于过滤包含在 start_date 和 end_date 之间的所有条目(例如月份)。

worked_in_month = InOut.objects.filter( in_dt__lte=end_date, out_dt__gte=start_date)

然后我可以做一个注释或值和注释和 Sum(hours) 但这不考虑 start_date/end_date 之外的小时数。或者我可以尝试使用 in_dt 和 out_dt 做一些事情,而忽略预先计算的时间。

我显然可以在 python 中进行计算(这可能是唯一的答案),但我想知道我是否会在 Djano 中通过过滤等遗漏一些东西。

【问题讨论】:

  • 作为一个建议,我可能会在月底的环绕中插入一个额外的开始/结束条目,以简化进一步的处理。
  • 我不太明白你在想什么,但想听听更多......

标签: python django


【解决方案1】:

我能想到至少两种方法来解决您的问题。

一个(相当复杂的)查询:

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 条目在逻辑上(而非物理上)是两个条目:

  1. 从指定时间开始,到月底结束
  2. 月末开始,指定时间结束

过滤掉受影响的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_monthnext_month_start__lt 以及 __gte 之间徘徊,因为这 扩展后,实现在每个环绕结束时损失一微秒。

是的,这是一个很好的练习;-)

【讨论】:

  • 啊,谢谢!我没有看到 Func/Max/Min 部分。看起来它会起作用并且效率更高。我试试看……
  • 我正在使用 postgres,似乎需要进行一些强制转换,因为它不喜欢使用 datetime 实例的 max。如果您知道解决方法,那就太好了,否则我会尝试弄清楚并发布。
  • 嗯,对于 PostgreSQL,您似乎必须使用 GREATEST()LEAST() 作为日期类型。这失去了数据库的可移植性,因为这些不再是纯 SQL 函数。
  • 啊,完美。我也在其中添加了一个值。
  • 暂时离开这个,不得不回到这个。我很接近,但我似乎无法完成最后一部分。您的最后一行总和有效,但我真正想要的是为每个用户执行此操作(为清楚起见缩短了一些内容):.annotate(sim=F('in_dt'),eim=F('out_dt'),worked=(F('out_dt')-F('in_dt'))).values('user').annotate(t=Sum(F('worked'))) 但最后的注释给了我一个错误“KeyError:'工作'”。我很接近,但似乎工作没有通过价值观(??)。
【解决方案2】:

根据您上面的回答,这是我所拥有的(未经验证但似乎有效):

ins = InOut.objects.filter(
    (
       Q(in_dt__gte=start_date) and Q(in_dt__lt=end_date)) |
        (Q(out_dt__gte=start_date) and Q(out_dt__lt=end_date)
    )
).values('user').annotate(
   start_in_month=Func(F('in_dt'), start_date, function='greatest'),
   end_in_month=Func(F('out_dt'), end_date, function='least')
).annotate(worked=Sum(F('end_in_month') - F('start_in_month')))

非常感谢@dhke,非常有帮助。

【讨论】:

    猜你喜欢
    • 2013-11-26
    • 2015-12-26
    • 1970-01-01
    • 2020-08-05
    • 1970-01-01
    • 2018-05-15
    • 1970-01-01
    • 2017-10-26
    • 1970-01-01
    相关资源
    最近更新 更多