使用 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 计算不同时区每天的事件类型的主要内容,如果未能解决你的问题,请参考以下文章
在每个时区每天凌晨 3:00 运行 celery 任务?姜戈