【问题标题】:How to make TimeField timezone-aware?如何使 TimeField 时区感知?
【发布时间】:2013-11-15 19:13:46
【问题描述】:

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

【问题讨论】:

    标签: django


    【解决方案1】:
    1. 在您的模型中time_field = models.TimeField(null=True)

    2. 创建将时间转换为日期时间的函数,然后将其转换为 UTC 日期时间,然后保存。 Django 会自动从日期时间开始计时。

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

    • 注意:您应该在middleware中激活当前用户的时区。
    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)
    
    
    1. 创建自定义字段序列化程序,使用这些函数进行转换。
    
    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
    
    

    【讨论】:

      【解决方案2】:

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

      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 将尝试节省天真的时间(不确定这是否会引发错误)。

      编辑

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

      编辑 2

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

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

      【讨论】:

        【解决方案3】:

        答案是你不知道。要使时间能够感知时区,它必须有一个与之关联的日期。想想夏令时......我的解决方案是在模型上使用DateTimeField 并像这样覆盖表单:

        # Model
        class MyModel(models.Model):
            time_of_day = models.DateTimeField()
        
        # Form Fields
        from django.forms.util import from_current_timezone
        from django.forms.util import 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
        class MyForm(forms.ModelForm):
            time_of_day = TzAwareTimeField()
        

        【讨论】:

        • 您的开场白(以及 Django 的行为)与 Python 处理 time 对象的方式不一致。 Python time 对象可能有一个 tzinfo 字段,允许时区感知 time 对象没有关联日期,这在某些情况下很有用,例如当日期存储在其他地方时.我刚刚在 Django 中的 Day 模型上遇到了 open_timeclose_time TimeFields 这个问题。理想情况下,这些时间是时区感知的,但不可能是因为 Django 不支持。
        • 这是旧的,但仍然有用,所以我想指出,我认为您需要将clean 方法的返回值包装在from_current_timezone 中。
        • @josh 我知道已经晚了,但其他人可能会读到这个。您断言时区需要日期是错误的。时间点 (datetime) 和一天中的时间 (time) 之间存在差异。 11:00,如“每周五 11:00”,由时间表示。该时间可能会与许多不同的日期相结合以形成一个时间点,因此如果您需要转换到另一个时区,您可以考虑 DST。尝试在周五 11:00 安排一次国际会议,您很快就会发现您需要知道时区,而不是日期。
        • 如何为 DateField 做同样的事情?
        猜你喜欢
        • 2020-11-20
        • 2013-07-08
        • 2012-03-30
        • 1970-01-01
        • 2019-01-18
        • 1970-01-01
        • 2020-11-07
        • 2017-06-28
        • 2013-10-10
        相关资源
        最近更新 更多