当时区信息存储在数据库中时,Django 按一天中的几个小时进行过滤

Django filter by hour of day when timezone info stored in database

模型结构如下:

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 小时内创建的会话对象,在它们所连接的地点的时区?请注意,可能会在具有不同时区的不同地方进行会话,但我想获取在当地时区的一天中的第 10 小时内创建的会话。

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

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

从 django.db.models 导入 Func、DateTimeField

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 数据库函数和数据库表达式的工作原理不是很了解,所以欢迎(也愿意听取)对解决方案的任何反馈/改进