首页 > 解决方案 > 当时区信息存储在数据库中时,Django 按一天中的小时过滤

问题描述

具有以下模型结构:

class Place(models.Model):
    ...
    timezone = TimeZoneField()  # Defined in a package called django-timezone-utils
    ...

class Session(models.Model):
    ...
    place = model.ForeignKey(Place, ...)
    created = models.DateTimeField(auto_now_add=True)  # Saved in utc
    ...

我想获取在一天中的第 10 个小时内创建的所有会话。对此的基本方法是:

Session.objects.filter(created__hour=10)

但是,正如预期的那样,结果将按 UTC 时间过滤。如何获取在一天中的第 10 个小时内创建的 Sessions 对象,在它们连接到的地方的时区中?请注意,可能会在具有不同时区的不同地点进行会话,但我想获取在当地时区的一天中的第 10 个小时内创建的会话。

使用本地时区中的创建时间注释每个结果,然后过滤该注释值可能会起作用,但我不知道如何构建该注释

标签: djangotimezonedjango-orm

解决方案


我通过定义将日期时间字段转换为时区的自定义数据库函数来完成我想要的,该时区也在相关列中定义。现在仅适用于 postgres(使用“在时区”表达式),但可以更新以使用其他数据库引擎以及使用它们的时区转换功能。

从 django.db.models 导入函数,日期时间字段

class ConvertToTimezone(Func):
    """
    Custom SQL expression to convert time to timezone stored in database column
    """

    output_field = DateTimeField()

    def __init__(self, datetime_field, timezone_field, **extra):
        expressions = datetime_field, timezone_field
        super(ConvertToTimezone, self).__init__(*expressions, **extra)

    def as_sql(self, compiler, connection, fn=None, template=None, arg_joiner=None, **extra_context):
        params = []
        sql_parts = []
        for arg in self.source_expressions:
            arg_sql, arg_params = compiler.compile(arg)
            sql_parts.append(arg_sql)
            params.extend(arg_params)

        return "%s AT TIME ZONE %s" % tuple(sql_parts), params

这就是我使用它的方式:

Session.objects.annotate(created_local=ConvertToTimezone('created', 'place__timezone')).filter(created_local__hour=10)

我对 Django 数据库函数和数据库表达式的工作方式没有深入的了解,因此欢迎(并且愿意听到)对解决方案的任何反馈/改进


推荐阅读