强制 Google Cloud 的数据存储日期时间默认为 UTC
Forcing Google Cloud's Datastore datetimes to default to UTC
根据数据存储 docs,日期时间始终以 UTC 格式存储和返回。
但是,在云仪表板中,我的所有日期时间都显示在 CEST
中,即 UTC+2
。
保存实体时,我不包括任何时区并使用 datetime.utcnow()
。
更具体地说,这是我的 TimeZonedDateTimeProperty
模型,extending/partially 覆盖 google 自己的 db.DateTimeProperty
:
class TimeZonedDateTimeProperty(db.DateTimeProperty):
def __init__(self, *args, **kwargs):
super(TimeZonedDateTimeProperty, self).__init__(*args, **kwargs)
def __get__(self, model_instance, model_class):
"""Returns the value for this property on the given model instance."""
if model_instance is None:
return self
attr_to_return = None
try:
# access the db.DateTimeProperty's getter
attr_to_return = getattr(model_instance, super(TimeZonedDateTimeProperty, self)._attr_name())
except AttributeError:
attr_to_return = None
if attr_to_return:
# super methods of db.DateTimeProperty will be calling this
# getter too and we don't wanna mess up
# the UTC convention when working with datetimes
includes_correct_caller = None
try:
# after noticing that this getter is also being called
# outside of user requests, inspect the caller and decide
# whether to offset the UTC datetime or not
includes_correct_caller = "main_class_name" in str(inspect.stack()[1][0].f_locals["self"].__class__)
except Exception:
includes_correct_caller = False
if includes_correct_caller:
return attr_to_return + relativedelta(hours=+2)
else:
return attr_to_return
else:
return None
下面是子类 db.Model
中的示例用法:
class Message(db.Model):
...
sent = TimeZonedDateTimeProperty()
...
m = Message(...
sent=datetime.utcnow(),
...)
m.put()
现在,如果我在本地主机上调用 m.sent
运行,事情会按计划进行,即 sent
属性 以 UTC 保存,getter returns 偏移日期时间。
在 live/production 环境中,属性 未 以 UTC 格式保存,因此,被覆盖的 getter 不必要地增加了 2 小时。
所以:
如何强制实时数据存储在保存和提供我的日期时间时始终使用 UTC?
我的做法对吗?假设是,是否有更优雅的方法来处理 GAE 中的时区日期时间?
谢谢!
固定
基于 getter 确实在标准 get
请求之外调用的事实,尤其是在执行 put()
时,只需观察调用堆栈并查找put
关键字。
换句话说,在将 UTC 时间戳正确保存到数据库之前阻止修改。
...
includes_correct_caller = None
try:
_, _, _, stack_trace, _, _ = zip(*list(inspect.stack()))
includes_correct_caller = "put" not in stack_trace
except Exception:
includes_correct_caller = False
if includes_correct_caller:
return attr_to_return + relativedelta(hours=+2)
else:
return attr_to_return
Google 数据存储 以 UTC(0) 格式存储所有 dates/times。但是,云仪表板与您机器的时区有关,因此日期是根据您的操作系统设置的时区呈现的。如果您使用的是基于 Window 的系统,请尝试更改时区,然后刷新仪表板。您会发现所列实体的任何日期属性都会更新以反映您系统的新时区。
为了回答第一个问题,App Engine 已经将时间存储为 UTC。
针对第二个问题,问题中的方法和OP的回答会在未来造成问题:
- 当中欧夏令时 (CEST) 结束和中欧时间 (CET) 开始时,它会产生不正确的值
- 检查堆栈以避免复合偏移量可能不起作用,例如,如果
Message.sent
是在另一个对象的 put
方法中检索到的
- 如果应用程序增长到使用更多时区,这将导致复杂化
可以通过将日期时间存储在标准 DateTimeProperty 中并在需要时将它们转换为时区感知日期时间来避免这些问题。
db docs 为 OP 的用例提出了一个解决方案:
If you only convert values to and from a particular time zone, you can
implement a custom datetime.tzinfo to convert values from the
datastore
实施自定义 tzinfo
可能很棘手,但是 dateutil 包中已经实施了 tzinfos。
该模型可以有一个方法 returns 时区感知日期时间:
from dateutil import tz
from google.appengine.ext import db
class Message(db.Model):
sent = db.DateTimeProperty()
def local_sent(self, default_tz='Europe/Paris'):
"""
Returns a timezone-aware datetime for the local
timezone.
The default timezone can be overridden to support
other locales.
"""
utc = tz.gettz('UTC')
local = tz.gettz(tz) # or some other CET timezone
return self.sent.replace(tzinfo=utc).astimezone(local)
用法:
m = Message(sent=datetime.datetime.utcnow())
m.sent
datetime.datetime(2017, 6, 16, 18, 0, 12, 364017)
m.local_sent()
datetime.datetime(2017, 6, 16, 20, 0, 12, 364017, tzinfo=tzfile('/usr/share/zoneinfo/Europe/Paris'))
m.local_sent.strftime('%Y-%m-%d %H:%M:%S.%f %z %Z')
'2017-06-16 20:00:12.364017 +0200 CEST'
m = Message(sent=datetime.datetime.utcnow())
2017-06-16 18:15:33.555208
2017-06-16 18:15:33.555208
m.local_sent('Asia/Beijing')
datetime.datetime(2017, 6, 17, 2, 15, 33, 555208, tzinfo=tzfile('/usr/share/zoneinfo/Asia/Beijing'))
m.local_sent('Asia/Beijing').strftime('%Y-%m-%d %H:%M:%S.%f %z %Z')
'2017-06-17 02:15:33.555208 +0800 CST'
这种方法提供了正确的夏令时行为,避免了可能脆弱的变通办法并处理了大多数时区。
根据数据存储 docs,日期时间始终以 UTC 格式存储和返回。
但是,在云仪表板中,我的所有日期时间都显示在 CEST
中,即 UTC+2
。
保存实体时,我不包括任何时区并使用 datetime.utcnow()
。
更具体地说,这是我的 TimeZonedDateTimeProperty
模型,extending/partially 覆盖 google 自己的 db.DateTimeProperty
:
class TimeZonedDateTimeProperty(db.DateTimeProperty):
def __init__(self, *args, **kwargs):
super(TimeZonedDateTimeProperty, self).__init__(*args, **kwargs)
def __get__(self, model_instance, model_class):
"""Returns the value for this property on the given model instance."""
if model_instance is None:
return self
attr_to_return = None
try:
# access the db.DateTimeProperty's getter
attr_to_return = getattr(model_instance, super(TimeZonedDateTimeProperty, self)._attr_name())
except AttributeError:
attr_to_return = None
if attr_to_return:
# super methods of db.DateTimeProperty will be calling this
# getter too and we don't wanna mess up
# the UTC convention when working with datetimes
includes_correct_caller = None
try:
# after noticing that this getter is also being called
# outside of user requests, inspect the caller and decide
# whether to offset the UTC datetime or not
includes_correct_caller = "main_class_name" in str(inspect.stack()[1][0].f_locals["self"].__class__)
except Exception:
includes_correct_caller = False
if includes_correct_caller:
return attr_to_return + relativedelta(hours=+2)
else:
return attr_to_return
else:
return None
下面是子类 db.Model
中的示例用法:
class Message(db.Model):
...
sent = TimeZonedDateTimeProperty()
...
m = Message(...
sent=datetime.utcnow(),
...)
m.put()
现在,如果我在本地主机上调用 m.sent
运行,事情会按计划进行,即 sent
属性 以 UTC 保存,getter returns 偏移日期时间。
在 live/production 环境中,属性 未 以 UTC 格式保存,因此,被覆盖的 getter 不必要地增加了 2 小时。
所以:
如何强制实时数据存储在保存和提供我的日期时间时始终使用 UTC?
我的做法对吗?假设是,是否有更优雅的方法来处理 GAE 中的时区日期时间?
谢谢!
固定
基于 getter 确实在标准 get
请求之外调用的事实,尤其是在执行 put()
时,只需观察调用堆栈并查找put
关键字。
换句话说,在将 UTC 时间戳正确保存到数据库之前阻止修改。
...
includes_correct_caller = None
try:
_, _, _, stack_trace, _, _ = zip(*list(inspect.stack()))
includes_correct_caller = "put" not in stack_trace
except Exception:
includes_correct_caller = False
if includes_correct_caller:
return attr_to_return + relativedelta(hours=+2)
else:
return attr_to_return
Google 数据存储 以 UTC(0) 格式存储所有 dates/times。但是,云仪表板与您机器的时区有关,因此日期是根据您的操作系统设置的时区呈现的。如果您使用的是基于 Window 的系统,请尝试更改时区,然后刷新仪表板。您会发现所列实体的任何日期属性都会更新以反映您系统的新时区。
为了回答第一个问题,App Engine 已经将时间存储为 UTC。
针对第二个问题,问题中的方法和OP的回答会在未来造成问题:
- 当中欧夏令时 (CEST) 结束和中欧时间 (CET) 开始时,它会产生不正确的值
- 检查堆栈以避免复合偏移量可能不起作用,例如,如果
Message.sent
是在另一个对象的put
方法中检索到的 - 如果应用程序增长到使用更多时区,这将导致复杂化
可以通过将日期时间存储在标准 DateTimeProperty 中并在需要时将它们转换为时区感知日期时间来避免这些问题。
db docs 为 OP 的用例提出了一个解决方案:
If you only convert values to and from a particular time zone, you can implement a custom datetime.tzinfo to convert values from the datastore
实施自定义 tzinfo
可能很棘手,但是 dateutil 包中已经实施了 tzinfos。
该模型可以有一个方法 returns 时区感知日期时间:
from dateutil import tz
from google.appengine.ext import db
class Message(db.Model):
sent = db.DateTimeProperty()
def local_sent(self, default_tz='Europe/Paris'):
"""
Returns a timezone-aware datetime for the local
timezone.
The default timezone can be overridden to support
other locales.
"""
utc = tz.gettz('UTC')
local = tz.gettz(tz) # or some other CET timezone
return self.sent.replace(tzinfo=utc).astimezone(local)
用法:
m = Message(sent=datetime.datetime.utcnow())
m.sent
datetime.datetime(2017, 6, 16, 18, 0, 12, 364017)
m.local_sent()
datetime.datetime(2017, 6, 16, 20, 0, 12, 364017, tzinfo=tzfile('/usr/share/zoneinfo/Europe/Paris'))
m.local_sent.strftime('%Y-%m-%d %H:%M:%S.%f %z %Z')
'2017-06-16 20:00:12.364017 +0200 CEST'
m = Message(sent=datetime.datetime.utcnow())
2017-06-16 18:15:33.555208
2017-06-16 18:15:33.555208
m.local_sent('Asia/Beijing')
datetime.datetime(2017, 6, 17, 2, 15, 33, 555208, tzinfo=tzfile('/usr/share/zoneinfo/Asia/Beijing'))
m.local_sent('Asia/Beijing').strftime('%Y-%m-%d %H:%M:%S.%f %z %Z')
'2017-06-17 02:15:33.555208 +0800 CST'
这种方法提供了正确的夏令时行为,避免了可能脆弱的变通办法并处理了大多数时区。