SQLAlchemy DateTime 时区
Posted
技术标签:
【中文标题】SQLAlchemy DateTime 时区【英文标题】:SQLAlchemy DateTime timezone 【发布时间】:2010-09-29 17:17:43 【问题描述】:SQLAlchemy 的 DateTime
类型允许使用 timezone=True
参数将非天真的日期时间对象保存到数据库,并将其原样返回。有什么方法可以修改 SQLAlchemy 传入的tzinfo
的时区,例如它可能是UTC?我意识到我可以使用default=datetime.datetime.utcnow
;然而,这是一个天真的时间,即使我使用了timezone=True
,它也会很乐意接受传递一个天真的基于本地时间的日期时间的人,因为它使本地或 UTC 时间变得不天真,而没有基本时区来规范它.我已经尝试(使用pytz)使日期时间对象不幼稚,但是当我将其保存到数据库时,它又变得幼稚。
请注意 datetime.datetime.utcnow 如何不能很好地与 timezone=True
配合使用:
import sqlalchemy as sa
from sqlalchemy.sql import select
import datetime
metadata = sa.MetaData('postgres://user:pass@machine/db')
data_table = sa.Table('data', metadata,
sa.Column('id', sa.types.Integer, primary_key=True),
sa.Column('date', sa.types.DateTime(timezone=True), default=datetime.datetime.utcnow)
)
metadata.create_all()
engine = metadata.bind
conn = engine.connect()
result = conn.execute(data_table.insert().values(id=1))
s = select([data_table])
result = conn.execute(s)
row = result.fetchone()
(1, datetime.datetime(2009, 1, 6, 0, 9, 36, 891887))
row[1].utcoffset()
datetime.timedelta(-1, 64800) # 这是我的本地时间偏移量!!
datetime.datetime.now(tz=pytz.timezone("US/Central"))
datetime.timedelta(-1, 64800)
datetime.datetime.now(tz=pytz.timezone("UTC"))
datetime.timedelta(0) #UTC
即使我将其更改为明确使用 UTC:
...
data_table = sa.Table('data', metadata,
sa.Column('id', sa.types.Integer, primary_key=True),
sa.Column('date', sa.types.DateTime(timezone=True), default=datetime.datetime.now(tz=pytz.timezone('UTC')))
)
row[1].utcoffset()
...
datetime.timedelta(-1, 64800) # 它没有使用我明确添加的时区
或者如果我放弃timezone=True
:
...
data_table = sa.Table('data', metadata,
sa.Column('id', sa.types.Integer, primary_key=True),
sa.Column('date', sa.types.DateTime(), default=datetime.datetime.now(tz=pytz.timezone('UTC')))
)
row[1].utcoffset() is None
...
True # 这次它甚至没有将时区保存到数据库中
【问题讨论】:
【参考方案1】:建议使用以下结构将UTC日期和时间数据存储在数据库中,并防止没有此类位置信息的数据存储。
import datetime
from sqlalchemy import DateTime
from sqlalchemy.types import TypeDecorator
class TZDateTime(TypeDecorator):
"""
A DateTime type which can only store tz-aware DateTimes.
"""
impl = DateTime(timezone=True)
def process_bind_param(self, value, dialect):
if isinstance(value, datetime.datetime) and value.tzinfo is None:
raise ValueError('!r must be TZ-aware'.format(value))
return value
def __repr__(self):
return 'TZDateTime()'
存储在数据库中的值应定义如下:
import datetime
import pytz
def tzware_datetime():
"""
Return a timezone aware datetime.
:return: Datetime
"""
return datetime.datetime.now(pytz.utc)
【讨论】:
从 Python 3.2 开始,UTC 可以作为 dt.datetime.timezone.utc 使用,无需导入 pytz。【参考方案2】:解决此问题的一种方法是始终在数据库中使用可识别时区的字段。但请注意,根据时区的不同,相同的时间可以有不同的表达方式,即使这对计算机来说不是问题,但对我们来说却很不方便:
2003-04-12 23:05:06 +01:00
2003-04-13 00:05:06 +02:00 # This is the same time as above!
此外,Postgresql 在内部以 UTC 存储所有时区感知日期和时间。在显示给客户端之前,它们会转换为 timezone 配置参数指定的区域中的本地时间。
相反,我建议在整个应用程序中使用UTC
时间戳,并在数据库中使用时区初始日期和时间,并且仅在用户看到它们之前将它们转换为用户本地时区。
此策略可让您拥有最简洁的代码,避免任何时区转换和混淆,并使您的数据库和应用能够始终如一地工作,不受“本地时区”差异的影响。例如,您的开发机器和生产服务器可能在不同时区的云上运行。
要实现这一点,请在初始化引擎之前告诉 Postgresql 您希望查看 UTC 时区。
在 SqlAlchemy 中你可以这样做:
engine = create_engine(..., connect_args="options": "-c timezone=utc")
如果您使用的是 tornado-sqlalchemy,您可以使用:
factory = make_session_factory(..., connect_args="options": "-c timezone=utc")
由于我们在任何地方都使用所有 UTC 时区,因此我们只需在模型中使用 timezone-naive 日期和时间:
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime)
如果您使用的是 alembic,也是如此:
sa.Column('created_at', sa.DateTime()),
sa.Column('updated_at', sa.DateTime()),
并且在代码中使用 UTC 时间:
from datetime import datetime
...
model_object.updated_at = datetime.now(timezone.utc)
【讨论】:
【参考方案3】:this question’s答案中给出了解决方案:
您可以通过将所有(日期)时间对象以 UTC 格式存储在数据库中并在检索时将生成的原始日期时间对象转换为可感知的对象来规避这种情况。
唯一的缺点是您会丢失时区信息,但无论如何,将日期时间对象存储在 utc 中可能是个好主意。
如果您关心时区信息,我会将其单独存储,并且仅在最后一个可能的实例中(例如,在显示之前)将 utc 转换为本地时间
或者也许您根本不需要关心,并且可以使用您运行程序的机器上的本地时区信息,或者如果它是一个 web 应用程序,则可以使用用户的浏览器。
【讨论】:
如果我们以任何一种方式丢失时区信息但仍然能够以 UTC 格式保存日期/时间,那么为什么不将带有时区的日期时间保存到定义为string
而不是 timestamptz
类型的列中?【参考方案4】:
http://www.postgresql.org/docs/8.3/interactive/datatype-datetime.html#DATATYPE-TIMEZONES
所有可识别时区的日期和时间都以 UTC 格式在内部存储。在显示给客户端之前,它们会转换为 timezone 配置参数指定的区域中的本地时间。
用 postgresql 存储它的唯一方法是单独存储它。
【讨论】:
“时区配置参数”是存储数据库中datetime_with_timzone中的时区信息吗? @Ciastopiekarz 否。timezone
配置参数是客户端连接参数。 datetime with time zone
,其值存储在 UTC 中,在显示或返回给您之前,将转换为在此 timezone
参数中指定的时区。使用SHOW timezone
检查它对您当前连接的价值。想想您真正需要什么:您是否需要存储事件发生的时间,或者您是否还需要存储存储的日期时间值实际源自哪个时区的信息?如果是后者,则需要单独存储这些信息。
如果我只知道它发生的时间,但在 UTC 中,那可能是当前连接,那么如果没有时区信息,我将永远不知道用户某些事件发生的确切时间。跨度>
公平地说,这是一个 SQL,而不仅仅是 PostgreSQL。以上是关于SQLAlchemy DateTime 时区的主要内容,如果未能解决你的问题,请参考以下文章
如何在忽略时区问题的情况下从 SQL Server 获取 DateTime 数据?
将 Postgres 中没有时区的 DateTime 字段从中欧时间转换为 UTC