Тип 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






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 - хранить его отдельно.
@Ciastopiekarz Нет. Параметр конфигурации timezone - это параметр подключения клиента. datetime with time zone, значение которого хранится в UTC, преобразуется в часовой пояс, указанный в этом параметре timezone, перед отображением или возвратом его вам. Используйте SHOW timezone, чтобы проверить, какое значение он имеет для вашего текущего соединения. Подумайте о том, что вам действительно нужно: нужно ли вам сохранять время, когда произошло событие, или вам также нужно сохранить информацию, в каком часовом поясе фактически возникло сохраненное значение datetime? Если это последнее, вам необходимо сохранить эту информацию отдельно.
если я знаю только время, когда это произошло, но в UTC это, вероятно, текущее соединение, то без информации о часовом поясе я никогда не узнаю, когда именно в правильное время пользователя произошло какое-либо событие.
Честно говоря, это SQL, а не только PostgreSQL.
решение дано в ответе этот вопрос:
вы можете обойти это, сохранив все объекты времени (даты) в своей базе данных в формате UTC и преобразовав полученные в результате простые объекты datetime в известные при извлечении.
единственный недостаток заключается в том, что вы теряете информацию о часовом поясе, но в любом случае, вероятно, будет хорошей идеей хранить ваши объекты datetime в utc.
если вам важна информация о часовом поясе, я буду хранить ее отдельно и конвертировать utc в местное время только в последнем возможном случае (например, прямо перед отображением)
Или, может быть, вам все равно, и вы можете использовать информацию о часовом поясе с компьютера, на котором работаете программа, или браузера пользователя, если это веб-приложение.
если мы в любом случае потеряем информацию о часовом поясе, но все же сможем сохранить дату / время в UTC, то почему бы не сохранить дату и время с часовым поясом в один столбец, определенный типа string вместо timestamptz?
Один из способов решить эту проблему - всегда использовать в базе данных поля с привязкой к часовому поясу. Но обратите внимание, что одно и то же время может быть выражено по-разному в зависимости от часового пояса, и хотя это не проблема для компьютеров, для нас это очень неудобно:
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)
является ли «параметр конфигурации часового пояса» информацией о часовом поясе в datetime_with_timzone в сохраненной базе данных?