SQLAlchemy DateTime时区

ali*_*lif 42 python postgresql timezone datetime sqlalchemy

SQLAlchemy的DateTime类型允许timezone=True参数将非天真的日期时间对象保存到数据库,并返回它.有没有办法修改tzinfoSQLAlchemy传入的时区,所以它可能是,例如,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()
Run Code Online (Sandbox Code Playgroud)

(1,datetime.datetime(2009,1,6,0,9,36,891887))

row[1].utcoffset()
Run Code Online (Sandbox Code Playgroud)

datetime.timedelta(-1,64800)#这是我的本地时间偏移!!

datetime.datetime.now(tz=pytz.timezone("US/Central"))
Run Code Online (Sandbox Code Playgroud)

datetime.timedelta(-1,64800)

datetime.datetime.now(tz=pytz.timezone("UTC"))
Run Code Online (Sandbox Code Playgroud)

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()
Run Code Online (Sandbox Code Playgroud)

...

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
Run Code Online (Sandbox Code Playgroud)

...

真的#这次甚至都没有为数据库保存时区

iny*_*iny 18

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

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

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

  • @Ciastopiekarz 否。`timezone` 配置参数是客户端连接参数。`datetime with time zone`,其值存储在 UTC 中,在显示或返回给你之前,它会被转换为这个 `timezone` 参数中指定的时区。使用“SHOW timezone”检查当前连接的值。想想您真正需要的是什么:您是否需要存储事件发生的时间,或者您是否还需要存储所存储的日期时间值实际源自哪个时区的信息?如果是后者,则需要单独存储这些信息。 (3认同)
  • 公平地说,这是 SQL 的事情,而不仅仅是 PostgreSQL。 (2认同)

fly*_*eep 9

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

您可以通过以UTC格式存储数据库中的所有(日期)时间对象,并在检索时将生成的天真日期时间对象转换为有意识的对象来避免这种情况.

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

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

或者你可能根本不需要关心,并且可以使用运行程序的机器上的本地时区信息,或者用户的浏览器(如果它是webapp).


Can*_*ner 9

One way to solve this issue is to always use timezone-aware fields in the database. But note that the same time can be expressed differently depending on the timezone, and even though this is not a problem for computers it is very inconvenient for us:

2003-04-12 23:05:06 +01:00
2003-04-13 00:05:06 +02:00 # This is the same time as above!
Run Code Online (Sandbox Code Playgroud)

Also Postgresql stores all timezone-aware dates and times internally in UTC. They are converted to local time in the zone specified by the timezone configuration parameter before being displayed to the client.

Instead, I recommend to use UTC timestamps both throughout the app and timezone-naive dates and times in the database, and only convert them to users local timezone before user sees them.

This strategy lets you have the cleanest code, avoiding any timezone conversions and confusions, and makes your database and app work consistently independent of the "local timezone" differences. For example, you might have your development machine and production server running on cloud in different timezones.

To achieve this, tell Postgresql that you want to see timezones in UTC before initializing the engine.

In SqlAlchemy you do it like this:

engine = create_engine(..., connect_args={"options": "-c timezone=utc"})
Run Code Online (Sandbox Code Playgroud)

And if you are using tornado-sqlalchemy you can use:

factory = make_session_factory(..., connect_args={"options": "-c timezone=utc"})
Run Code Online (Sandbox Code Playgroud)

Since we use all UTC timezones everywhere, we simply use timezone-naive dates and times in the model:

created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime)
Run Code Online (Sandbox Code Playgroud)

And the same in case if you are using alembic:

sa.Column('created_at', sa.DateTime()),
sa.Column('updated_at', sa.DateTime()),
Run Code Online (Sandbox Code Playgroud)

And in the code use UTC time:

from datetime import datetime
...
model_object.updated_at = datetime.now(timezone.utc)
Run Code Online (Sandbox Code Playgroud)


小智 5

建议使用以下结构在数据库中存储 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()'
Run Code Online (Sandbox Code Playgroud)

存储在数据库中的值应定义如下:

import datetime

import pytz


def tzware_datetime():
    """
    Return a timezone aware datetime.

    :return: Datetime
    """
    return datetime.datetime.now(pytz.utc)
Run Code Online (Sandbox Code Playgroud)

  • 从 Python 3.2 开始,UTC 可以作为 dt.datetime.timezone.utc 使用,无需导入 pytz。 (4认同)