如何在Django QuerySet中将DateField与TimeField组合转换为本地时间
解决方法
首先确认settings.py中已开启时区支持:
USE_TZ = True TIME_ZONE = "你的本地时区,例如Asia/Shanghai"
方案1:Django 3.2及以上版本使用内置AtTimezone函数
Django 3.2开始内置了django.db.models.functions.AtTimezone函数,就是PostgreSQL AT TIME ZONE语法的原生封装,直接修改你的查询集逻辑即可:
from django.db.models import F, ExpressionWrapper from django.db.models.functions import AtTimezone from django.conf import settings from . import models class SessionQuerySet(models.QuerySet): def with_datetimes(self): # 先拼接得到无时区的日期时间,再通过AtTimezone标记为本地时区时间 naive_start = F('date') + F('start_time') naive_end = F('date') + F('end_time') return self.annotate( start_datetime=ExpressionWrapper( AtTimezone(naive_start, settings.TIME_ZONE), output_field=models.DateTimeField() ), end_datetime=ExpressionWrapper( AtTimezone(naive_end, settings.TIME_ZONE), output_field=models.DateTimeField() ), )
方案2:低版本Django自定义Func实现
如果你用的Django版本低于3.2,可以自己封装对应AT TIME ZONE的数据库函数:
from django.db.models import F, ExpressionWrapper, Func from django.conf import settings from . import models class AtTimeZone(Func): function = 'AT TIME ZONE' arity = 2 # 接收两个参数:原始时间、目标时区 class SessionQuerySet(models.QuerySet): def with_datetimes(self): naive_start = F('date') + F('start_time') naive_end = F('date') + F('end_time') return self.annotate( start_datetime=ExpressionWrapper( AtTimeZone(naive_start, settings.TIME_ZONE), output_field=models.DateTimeField() ), end_datetime=ExpressionWrapper( AtTimeZone(naive_end, settings.TIME_ZONE), output_field=models.DateTimeField() ), )
效果验证
修改后查询得到的就是带本地时区信息的aware datetime:
>>> Session.objects.with_datetimes()[0].start_datetime <<< datetime.datetime(2021, 9, 20, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='Asia/Shanghai'))
注意事项
- 时区请使用IANA标准格式(例如
Asia/Shanghai、America/New_York),不要使用GMT+8这类偏移量写法,避免夏令时等场景出现计算错误。 - 如果你使用MySQL作为数据库,需要先在数据库中导入时区信息表,否则
AT TIME ZONE语法会执行报错。
内容的提问来源于stack exchange,提问作者Ben Davis
相关产品推荐
相关产品推荐

