57

SQLAlchemy 的DateTime类型允许一个timezone=True参数将一个非天真的日期时间对象保存到数据库中,并按原样返回它。有什么方法可以修改tzinfoSQLAlchemy 传入的时区,例如,它可能是 UTC?我意识到我可以使用default=datetime.datetime.utcnow; 然而,这是一个天真的时间,即使我使用timezone=True它,它也会很乐意接受传递一个天真的基于本地时间的日期时间的人,因为它使本地或 UTC 时间变得不天真,而没有一个基准时区来规范它。我已经尝试(使用pytz)使 datetime 对象不幼稚,但是当我将它保存到数据库时,它又变得幼稚。

请注意 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 # 这次它甚至没有将时区保存到数据库

4

4 回答 4

28

http://www.postgresql.org/docs/8.3/interactive/datatype-datetime.html#DATATYPE-TIMEZONES

所有可识别时区的日期和时间都以 UTC 格式在内部存储。在显示给客户端之前,它们会转换为 timezone 配置参数指定的区域中的本地时间。

用 postgresql 存储它的唯一方法是单独存储它。

于 2009-01-20T16:24:22.457 回答
10

这个问题的答案给出了一个解决方案:

您可以通过以 UTC 格式将所有(日期)时间对象存储在数据库中,并将生成的原始日期时间对象转换为检索时的感知对象来规避这种情况。

唯一的缺点是您会丢失时区信息,但无论如何,将您的日期时间对象存储在 UTC 中可能是个好主意。

如果您关心时区信息,我会将其单独存储,并且仅在最后一个可能的实例中将 utc 转换为本地时间(例如,在显示之前)

或者也许你根本不需要关心,并且可以使用运行程序的机器上的本地时区信息,或者如果它是一个 webapp,则可以使用用户的浏览器。

于 2012-10-02T17:16:57.690 回答
10

解决此问题的一种方法是始终在数据库中使用时区感知字段。但是请注意,同一时间可以根据时区的不同来表示,即使这对计算机来说不是问题,但对我们来说非常不方便:

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)
于 2020-01-27T14:16:48.203 回答
4

建议使用以下结构将 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)
于 2020-06-23T15:32:58.633 回答