使用 Django ORM 计算不同时区每天的事件类型

Posted

技术标签:

【中文标题】使用 Django ORM 计算不同时区每天的事件类型【英文标题】:Count events types per day for different timezones using Django ORM 【发布时间】:2020-07-17 11:47:06 【问题描述】:

我们有一个表格,其中包含多个事件以及添加事件的时间。用于存储事件的默认时区是 UTC。例如:

class Events:
    type = models.CharField(max_length=45, null=False)
    date_added = models.DateTimeField(auto_now_add=True)

现在,我们想要获取两个日期之间不同事件类型的每日计数 - start_date 和 end_date。例如:对于 start_date = "2020-03-1" 和 end_date = "2020-03-31",输出应该是 -

[
    "date" : "2020-03-1",
    "event1" : 200,
    "event2" : 606,
    "event3" : 595
,

    "date" : "2020-03-2",
    "event1" : 357,
    "event2" : 71,
    "event3" : 634
,

    "date" : "2020-03-3",
    "event1" : 106,
    "event2" : 943,
    "event3" : 315
,

    "date" : "2020-03-4",
    "event1" : 187,
    "event2" : 912,
    "event3" : 743
,
.
.
.
.

    "date" : "2020-03-31",
    "event1" : 879,
    "event2" : 292,
    "event3" : 438
]

由于用户位于不同的时区(美国、欧洲、亚洲等),我们希望在计算事件之前根据用户转换时区。以 UTC 计数在用户的时区中每天都会有错误的计数。例如:创建于 3 月 3 日凌晨 1:30 IST 的事件将在 3 月 2 日晚上 8 点(UTC 时间)显示并相应计数。

如果我们使用 for 循环,它会变得非常昂贵。因此,我们希望使用 Django ORM 在 DB 级别进行操作。如果不可能完全依赖 Django ORM,我们希望使其尽可能高效。

我们能想到的最好的查询是:

 Events.objects.filter(
    pk = user_pk, date__range = (
        (end_date - time_delta).strftime("%Y-%m-%d"), 
        end_date.strftime("%Y-%m-%d")
        )
    ).extra(
        "date_added" : "date(date_added)"
    ).values(
        "date_added", 
        "type"
    ).annotate(
        models.Count("type")
    ) 

我们得到的结果如下:

<QuerySet ['date_added': datetime.date(2020, 3, 6), 'type': 'event1', 'type__count': 30, 
'date_added': datetime.date(2020, 3, 6), 'type': 'event2', 'type__count': 189, 
'date_added': datetime.date(2020, 3, 6), 'type': 'event3', 'type__count': 1, 
'date_added': datetime.date(2020, 3, 6), 'type': 'event4', 'type__count': 3, 
'date_added': datetime.date(2020, 3, 9), 'type': 'event2', 'type__count': 57, 
'date_added': datetime.date(2020, 3, 9), 'type': 'event1', 'type__count': 23, 
'date_added': datetime.date(2020, 3, 9), 'type': 'event4', 'type__count': 1, 
'date_added': datetime.date(2020, 3, 10), 'type': 'event1', 'type__count': 5, 
'date_added': datetime.date(2020, 3, 10), 'type': 'event2', 'type__count': 21, 
'date_added': datetime.date(2020, 3, 11), 'type': 'event2', 'type__count': 9, 
'date_added': datetime.date(2020, 3, 11), 'type': 'event1', 'type__count': 15, 
'date_added': datetime.date(2020, 3, 12), 'type': 'event2', 'type__count': 49, 
'date_added': datetime.date(2020, 3, 13), 'type': 'event2', 'type__count': 8, 
'date_added': datetime.date(2020, 3, 13), 'type': 'event1', 'type__count': 3, 
'date_added': datetime.date(2020, 3, 17), 'type': 'event1', 'type__count': 16, 
'date_added': datetime.date(2020, 3, 17), 'type': 'event2', 'type__count': 26, 
'date_added': datetime.date(2020, 3, 17), 'type': 'event4', 'type__count': 1, 
'date_added': datetime.date(2020, 3, 17), 'type': 'event3', 'type__count': 1, 
'date_added': datetime.date(2020, 3, 18), 'type': 'event2', 'type__count': 64, 
'date_added': datetime.date(2020, 3, 18), 'type': 'event1', 'type__count': 11, 
'...(remaining elements truncated)...']>

这仍然需要一个 for 循环来将具有相同日期的所有事件添加到一个字典中,但时区问题仍然存在。

如何解决?

【问题讨论】:

【参考方案1】:

我们终于解决了这个问题。我们仍在使用 for 循环来获取所需格式的数据,但我们能够将繁重的工作转移到数据库。先说几点:

    我正在使用 mysql 数据库。 (检查以下几点,了解为什么这是相关的。)

    如果您想直接使用时差转换时区(IST 为“+05:30”,UTC 为“+00:00”),则无需运行任何命令。

    但是,您需要运行命令以在 MySQL 中按名称支持时区 D B。例如:如果您要使用命名时区(“ASIA/KOLKATA”或“UTC”), 您将需要运行一个命令: mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -D mysql -u root -p 此命令适用于在 Ubuntu 中运行的 MySQL DB。有类似的 用于其他数据库(如 Postgresql)和平台(如 Windows)的命令。

这是 Django ORM 查询:

events_list = Events.objects.all().extra(
        
            "date_added" : "date(CONVERT_TZ(date_added, 'UTC', 'America/Chicago'))"
        
    ).values(
        "date_added",
        "type"
    ).annotate(
        models.Count(
            "type"
        )
    )

这将给出以下格式的数据:

<QuerySet ['date_added': datetime.date(2020, 3, 6), 'type': 'event1', 'type__count': 31, 
'date_added': datetime.date(2020, 3, 6), 'type': 'event2', 'type__count': 189, 
'date_added': datetime.date(2020, 3, 6), 'type': 'event3', 'type__count': 1, 
'date_added': datetime.date(2020, 3, 6), 'type': 'event4', 'type__count': 3, 
'date_added': datetime.date(2020, 3, 9), 'type': 'event2', 'type__count': 58, 
'date_added': datetime.date(2020, 3, 9), 'type': 'event1', 'type__count': 21, 
'date_added': datetime.date(2020, 3, 9), 'type': 'event4', 'type__count': 1, 
'date_added': datetime.date(2020, 3, 10), 'type': 'event1', 'type__count': 1, 
'date_added': datetime.date(2020, 3, 10), 'type': 'event2', 'type__count': 23, 
'date_added': datetime.date(2020, 3, 11), 'type': 'event2', 'type__count': 10, 
'date_added': datetime.date(2020, 3, 11), 'type': 'event1', 'type__count': 16, 
'date_added': datetime.date(2020, 3, 12), 'type': 'event2', 'type__count': 50, 
'date_added': datetime.date(2020, 3, 13), 'type': 'event2', 'type__count': 10, 
'date_added': datetime.date(2020, 3, 13), 'type': 'event1', 'type__count': 1, 
'date_added': datetime.date(2020, 3, 17), 'type': 'event1', 'type__count': 19, 
'date_added': datetime.date(2020, 3, 17), 'type': 'event2', 'type__count': 27, 
'date_added': datetime.date(2020, 3, 17), 'type': 'event4', 'type__count': 3, 
'date_added': datetime.date(2020, 3, 17), 'type': 'event3', 'type__count': 1, 
'date_added': datetime.date(2020, 3, 18), 'type': 'event2', 'type__count': 61, 
'date_added': datetime.date(2020, 3, 18), 'type': 'event1', 'type__count': 13, 
'...(remaining elements truncated)...']>

这里特定事件的数量是在时区转换后计算的。现在,一旦我们在时区转换后获得了事件的计数,剩下的就是以所需的格式获取这些数据,这可以通过 for 循环轻松完成。

PS:

    如果查询有条件,可以使用filter() 而是全部。例如:

    from django.utils import timezone
    Events.objects.filter(
        type__in = ["event1", "event2"], 
        date__gt = (
            timezone.now() - timezone.timedelta(days =  30)
        )
    ).extra(
        
            "date_added" : "date(CONVERT_TZ(date_added, 'UTC', 'ASIA/KOLKATA'))"
        
    ).values(
        "date_added",
        "type"
    ).annotate(
        models.Count(
            "type"
        )
    ) 
    

    这将为事件类型 1 和 2 提供过去 30 天的数据。

    您可以使用“+00:00”表示 UTC,“+05:30”表示亚洲/加尔各答。例如 : "date_added" : "date(CONVERT_TZ(date_added, '+00:00', '+05:30'))"

【讨论】:

以上是关于使用 Django ORM 计算不同时区每天的事件类型的主要内容,如果未能解决你的问题,请参考以下文章

Django 中的时区

基于事件或用户当前时区的事件时间?

在每个时区每天凌晨 3:00 运行 celery 任务?姜戈

Java 每天在同一时间向不同时区发送电子邮件

[Django]Django的orm中get和filter的不同

使用 Django ORM 进行快速移动平均计算