如何使TimeField具备时区感知能力?

14

有时候,您需要从用户那里收集时间,而不收集相关的日期。例如,如果用户正在配置每天以相同时间运行的重复事件。Django的TimeField并不支持时区。不过,在这种特定情况下(以及可能记录单独时间的任何时间),时区是一个重要因素。那么,如何存储具有时区意识的时间?

3个回答

11

答案是你不需要。要使时间有时区意识,它必须与日期相关联。想想夏令时...我对此的解决方案是在模型上使用DateTimeField,并像这样重写表单:

# models.py
class MyModel(models.Model):
    time_of_day = models.DateTimeField()


# form_fields.py
from django.forms.util import from_current_timezone, to_current_timezone
from django.utils import timezone

class TzAwareTimeField(forms.fields.TimeField):
    def prepare_value(self, value):
        if isinstance(value, datetime.datetime):
            value = to_current_timezone(value).time()
        return super(TzAwareTimeField, self).prepare_value(value)

    def clean(self, value):
        value =  super(TzAwareTimeField, self).to_python(value)
        dt = to_current_timezone(timezone.now())
        return dt.replace(
            hour=value.hour, minute=value.minute,
            second=value.second, microsecond=value.microsecond)


# forms.py
class MyForm(forms.ModelForm):
    time_of_day = TzAwareTimeField()

9
你的开头陈述(以及Django的行为)与Python处理“时间”对象的方式不一致。Python“时间”对象可以具有“tzinfo”字段,允许具有时区信息的“时间”对象而没有相关联的日期,在某些情况下非常有用,例如当日期存储在其他地方时。我最近在Django中的“Day”模型上遇到了这个问题,涉及到“open_time”和“close_time”两个“TimeField”。这些时间最好是有时区意识的,但是无法实现,因为Django不支持。 - jbg
这虽然有点老,但仍然很有用,所以我想指出我认为您需要在clean方法的返回值中使用from_current_timezone进行包装。 - John Rork
1
@josh 我知道现在已经很晚了,但是其他人可能会看到这个帖子。你所断言的时区需要一个日期是错误的。时间点(datetime)和一天中的时间(time)是有区别的。例如,“每周五上午11:00”表示的是一个时间。这个时间可以与许多不同的日期结合起来形成一个时间点,在这种情况下,如果你需要转换到另一个时区,你可以考虑夏令时(DST)。试着安排一个每周五上午11:00的国际会议,你很快就会发现你需要知道时区,但不需要知道日期。 - ThoughtfulHacking
如何对DateField执行相同操作? - Ali Husham
@josh是正确的,虽然Python的time对象可以与tzinfo相关联,但该值仅用于信息目的。如果不知道日期,就无法进行从一时区到另一时区的转换。例如,当我输入这个问题时,现在是下午3:45 PDT,在秋季时,这相当于22:00 UTC,但在夏季同一时间,则相当于23:00 UTC。 - James Emerton

0

这是未经测试和不完整的:

class TimeFieldWithZone(TimeField):
    def db_type(self, connection):
        if (connection.settings_dict['ENGINE'] == 
            'django.db.backends.postgresql_psycopg2'):
            return 'time with time zone'
        raise Exception('Unsupported database type')

    def get_db_prep_value(self, value, *args, **kwargs):
        try:
            return super(TimeFieldWithZone, self).get_db_prep_value(
                value, *args, **kwargs)
        except ValueError:
            return six.text_type(value)

这将使用Postgres的time with time zone数据类型。如果您传递格式为'HH:MM:SS.mmmmmm+HH:MM'的字符串,它将会出错,并且使用auto_now将尝试保存一个naive时间(不确定是否会抛出错误)。

编辑

在通用后端代码中,如果您尝试插入具有UTC以外时区的时间,则会引发异常。

编辑2

我添加了一个更新的get_db_prep_value来将提供的带时区的time转换为字符串,但仅在提供的时区输出utc偏移量时才有效(可能没有日期会有歧义)。

似乎time with time zone有点误导人...据我所知,它实际上存储带有UTC偏移量而不是时区的时间。因此,很难获取返回值,添加日历日期并返回正确的夏令时时间。


0
  1. 在你的模型中,time_field = models.TimeField(null=True)

  2. 创建函数将时间转换为日期时间,然后将其转换为UTC日期时间,然后保存。Django将自动从日期时间中仅获取时间。

  3. 创建一个函数,将存储在数据库中的时间转换为当前时区

  • 注意:您应该在中间件中激活当前用户的时区。
import datetime

import pytz
from django.utils import timezone


def ConvertToUTC(parsed):
    date_time = timezone.get_current_timezone().localize(parsed)
    utc_date_time = date_time.astimezone(pytz.utc)
    return utc_date_time


def ConvertToTimeZone(value, format):
    value = value.strftime(format)
    value = datetime.datetime.strptime(value, format)
    value = value.astimezone(timezone.get_current_timezone())

    return value.strftime(format)


4. 创建自定义字段序列化器,使用函数来进行转换。

class TimeSer(Field):
    default_error_messages = {
        'invalid': 'Time has wrong format, expecting %H:%M:%S%z.',
    }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def to_internal_value(self, value):
        try:
            parsed = datetime.datetime.strptime(value, '%H:%M:%S')
        except (ValueError, TypeError) as e:
            pass
        else:
            return ConvertToUTC(parsed)
        self.fail('invalid')

    def to_representation(self, value):

        if not value:
            return None

        if isinstance(value, str):
            return value
        if isinstance(value, datetime.time):
            return ConvertToTimeZone(value,'%H:%M:%S')

        return None


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接