有时候,您需要从用户那里收集时间,而不收集相关的日期。例如,如果用户正在配置每天以相同时间运行的重复事件。Django的TimeField
并不支持时区。不过,在这种特定情况下(以及可能记录单独时间的任何时间),时区是一个重要因素。那么,如何存储具有时区意识的时间?
有时候,您需要从用户那里收集时间,而不收集相关的日期。例如,如果用户正在配置每天以相同时间运行的重复事件。Django的TimeField
并不支持时区。不过,在这种特定情况下(以及可能记录单独时间的任何时间),时区是一个重要因素。那么,如何存储具有时区意识的时间?
答案是你不需要。要使时间有时区意识,它必须与日期相关联。想想夏令时...我对此的解决方案是在模型上使用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()
这是未经测试和不完整的:
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以外时区的时间,则会引发异常。
我添加了一个更新的get_db_prep_value
来将提供的带时区的time
转换为字符串,但仅在提供的时区输出utc偏移量时才有效(可能没有日期会有歧义)。
似乎time with time zone
有点误导人...据我所知,它实际上存储带有UTC偏移量而不是时区的时间。因此,很难获取返回值,添加日历日期并返回正确的夏令时时间。
在你的模型中,time_field = models.TimeField(null=True)
创建函数将时间转换为日期时间,然后将其转换为UTC日期时间,然后保存。Django将自动从日期时间中仅获取时间。
创建一个函数,将存储在数据库中的时间转换为当前时区
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)
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
clean
方法的返回值中使用from_current_timezone
进行包装。 - John Rorktime
对象可以与tzinfo
相关联,但该值仅用于信息目的。如果不知道日期,就无法进行从一时区到另一时区的转换。例如,当我输入这个问题时,现在是下午3:45 PDT,在秋季时,这相当于22:00 UTC,但在夏季同一时间,则相当于23:00 UTC。 - James Emerton