如何使用和不使用SQLAlchemy处理MySQL上的时区感知日期时间对象的“超出范围”警告

时间:2013-08-14 23:23:54

标签: python mysql datetime sqlalchemy

在将时区感知日期时间对象插入MySQL上的DateTime列时,我收到来自Mysql-Python的警告:

test_mysql.py:13: Warning: Out of range value for column 'created_at' at row 1
  cur.execute("INSERT INTO test (created_at) VALUES (%s)", now)

测试代码如下所示:

import MySQLdb
from datetime import datetime
from pytz import utc

conn = MySQLdb.connect(...)  # connect
cur = conn.cursor()
now = datetime.utcnow()
cur.execute("CREATE TABLE test (created_at DATETIME)")
print("Test 1")
cur.execute("INSERT INTO test (created_at) VALUES (%s)", now)
now = utc.localize(now)
print("Test 2")
cur.execute("INSERT INTO test (created_at) VALUES (%s)", now)
print("Test 3")
now = now.replace(tzinfo=None)
assert now.tzinfo is None
cur.execute("INSERT INTO test (created_at) VALUES (%s)", now)
print("Tests done")
cur.execute("DROP TABLE test")

完整输出为:

Test 1
Test 2
test_mysql.py:13: Warning: Out of range value for column 'created_at' at row 1
  cur.execute("INSERT INTO test (created_at) VALUES (%s)", now)
Test 3
Tests done

我在原始应用程序中使用SQLAlchemy,但由于这是SQLAlchemy下面的一个问题,我对使用SA和不使用SA的解决方案感兴趣。

1 个答案:

答案 0 :(得分:2)

如果没有SQLAlchemy,答案基本上已经存在于问题中:让它变得幼稚但是总是存储相同的(读取:UTC)时区:

if now.tzinfo is not None:
    now = now.astimezone(utc).replace(tzinfo=None)
return now

这将确保始终存储UTC(但如果传递天真,请确保始终天真!并且当将项目拉出数据库时,请确保使其具有UTC感知能力再次:

if created_at.tzinfo is None:
    created_at = utc.localize(created_at)
return created_at

由于MySQL不具备tz感知对象,因此可以删除检查(它应始终为None)。

对于SQLAlchemy,可以使用相同的方法(但允许SA自动执行此扩展)。为此,我们使用TypeDecorator

class TZDateTime(TypeDecorator):
    """
    Coerces a tz-aware datetime object into a naive utc datetime object to be
    stored in the database. If already naive, will keep it.

    On return of the data will restore it as an aware object by assuming it
    is UTC.

    Use this instead of the standard :class:`sqlalchemy.types.DateTime`.
    """

    impl = DateTime

    def process_bind_param(self, value, dialect):
        if value.tzinfo is not None:
            value = value.astimezone(utc).replace(tzinfo=None)
        return value

    def process_result_value(self, value, dialect):
        if value.tzinfo is None:
            value = utc.localize(value)
        return value

通过这种方式确保所处理的数据始终在UTC上具有tz感知能力,只要始终只为UTC生成天真值:datetime.datetime.utcnow()

定义列时,请使用该列而不是常规类型:

class Test(Base):
    __tablename__ = 'test'
    created_at = Column(TZDateTime)