web-dev-qa-db-ja.com

SQLAlchemy DateTimeタイムゾーン

SQLAlchemyのDateTime型では、timezone=True引数を使用して、ナイーブではない日時オブジェクトをデータベースに保存し、そのように返すことができます。 SQLAlchemyが渡すtzinfoのタイムゾーンを変更して、たとえばUTCにすることができる方法はありますか?私はdefault=datetime.datetime.utcnowだけを使用できることに気づきました。ただし、これはtimezone=Trueを使用した場合でも、単純なローカル時間ベースの日時を渡す誰かを喜んで受け入れる単純な時間です。これにより、正規化するベースタイムゾーンがなく、ローカル時間またはUTC時間が非単純になるためです。それで。 ( pytz を使用して)datetimeオブジェクトを非ナイーブにしようとしましたが、これをDBに保存するとナイーブとして返されます。

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#今回はタイムゾーンをデータベースに保存することさえしませんでした

42
alif

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

すべてのタイムゾーン対応の日付と時刻はUTCに内部的に保存されます。これらは、クライアントに表示される前に、timezone構成パラメーターで指定されたゾーンの現地時間に変換されます。

Postgresqlで保存する唯一の方法は、個別に保存することです。

18
iny

解決策は この質問の 回答に記載されています。

すべての(日付)時刻オブジェクトをデータベースにUTCで保存し、結果として得られた単純な日付時刻オブジェクトを取得時に認識できるものに変換することで、これを回避できます。

唯一の欠点は、タイムゾーン情報が失われることですが、とにかく、日付時刻オブジェクトをutcに保存することをお勧めします。

タイムゾーン情報が気になる場合は個別に保存し、考えられる最後のインスタンス(表示される直前など)でのみutcを現地時間に変換します。

または、結局のところ、気にする必要はなく、プログラムを実行しているマシン、またはそれがWebアプリケーションの場合はユーザーのブラウザーからローカルタイムゾーン情報を使用できます。

9
flying sheep

この問題を解決する1つの方法は、データベースのタイムゾーン対応フィールドを常に使用することです。ただし、同じ時間はタイムゾーンに応じて異なる形で表現される可能性があることに注意してください。これはコンピュータにとっては問題ではありませんが、非常に不便です。

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タイムスタンプを使用し、データベースではタイムゾーンナイーブな日付と時刻を使用し、ユーザーに表示される前にユーザーのローカルタイムゾーンにのみ変換することをお勧めします。

この戦略により、コードが最もクリーンになり、タイムゾーンの変換や混乱を回避し、「ローカルタイムゾーン」の違いに関係なく、データベースとアプリを一貫して機能させることができます。たとえば、開発マシンと本番サーバーを異なるタイムゾーンのクラウドで実行しているとします。

これを実現するには、エンジンを初期化する前にUTCでタイムゾーンを表示したいことをPostgresqlに伝えます。

SqlAlchemyでは、次のようにします。

engine = create_engine(..., connect_args={"options": "-c timezone=utc"})

また、tornado-sqlalchemyを使用している場合は、以下を使用できます。

factory = make_session_factory(..., connect_args={"options": "-c timezone=utc"})

すべての場所ですべてのUTCタイムゾーンを使用するため、モデルでは単にタイムゾーンナイーブな日付と時刻を使用します。

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)
0
Caner