Часовой пояс SQLAlchemy DateTime

Тип DateTime в SQLAlchemy позволяет аргументу timezone=True сохранять в базе данных ненативный объект datetime и возвращать его как таковой. Есть ли способ изменить часовой пояс tzinfo, который передает SQLAlchemy, чтобы это могло быть, например, 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) # that's my localtime offset!!

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) # it did not use the timezone I explicitly added

Или если я уроню 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 # it didn't even save a timezone to the db this time

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
50
0
60 315
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий

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

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

Единственный способ сохранить его с помощью postgresql - хранить его отдельно.

является ли «параметр конфигурации часового пояса» информацией о часовом поясе в datetime_with_timzone в сохраненной базе данных?

Ciasto piekarz 28.01.2018 09:08

@Ciastopiekarz Нет. Параметр конфигурации timezone - это параметр подключения клиента. datetime with time zone, значение которого хранится в UTC, преобразуется в часовой пояс, указанный в этом параметре timezone, перед отображением или возвратом его вам. Используйте SHOW timezone, чтобы проверить, какое значение он имеет для вашего текущего соединения. Подумайте о том, что вам действительно нужно: нужно ли вам сохранять время, когда произошло событие, или вам также нужно сохранить информацию, в каком часовом поясе фактически возникло сохраненное значение datetime? Если это последнее, вам необходимо сохранить эту информацию отдельно.

compostus 23.02.2018 15:30

если я знаю только время, когда это произошло, но в UTC это, вероятно, текущее соединение, то без информации о часовом поясе я никогда не узнаю, когда именно в правильное время пользователя произошло какое-либо событие.

Ciasto piekarz 23.02.2018 16:08

Честно говоря, это SQL, а не только PostgreSQL.

beldaz 10.08.2018 11:19

решение дано в ответе этот вопрос:

вы можете обойти это, сохранив все объекты времени (даты) в своей базе данных в формате UTC и преобразовав полученные в результате простые объекты datetime в известные при извлечении.

единственный недостаток заключается в том, что вы теряете информацию о часовом поясе, но в любом случае, вероятно, будет хорошей идеей хранить ваши объекты datetime в utc.

если вам важна информация о часовом поясе, я буду хранить ее отдельно и конвертировать utc в местное время только в последнем возможном случае (например, прямо перед отображением)

Или, может быть, вам все равно, и вы можете использовать информацию о часовом поясе с компьютера, на котором работаете программа, или браузера пользователя, если это веб-приложение.

если мы в любом случае потеряем информацию о часовом поясе, но все же сможем сохранить дату / время в UTC, то почему бы не сохранить дату и время с часовым поясом в один столбец, определенный типа string вместо timestamptz?

Ciasto piekarz 22.08.2017 18:45

Один из способов решить эту проблему - всегда использовать в базе данных поля с привязкой к часовому поясу. Но обратите внимание, что одно и то же время может быть выражено по-разному в зависимости от часового пояса, и хотя это не проблема для компьютеров, для нас это очень неудобно:

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. Они преобразуются в местное время в зоне, указанной параметром конфигурации часового пояса, перед отображением для клиента.

Вместо этого я рекомендую использовать временные метки 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, мы просто используем в модели дату и время, не зависящие от часовых поясов:

created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime)

И то же самое в случае, если вы используете перегонный куб:

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)

Следующая структура рекомендуется для хранения данных о дате и времени в формате 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)

Другие вопросы по теме