Как преобразовать наивное datetime в datetime с учетом DST в Python?

В настоящее время я работаю над бэкэндом для системы календаря, которая возвращает наивные даты Python. Принцип работы внешнего интерфейса заключается в том, что пользователь создает различные события календаря, а интерфейс возвращает наивную версию события, которое они создали (например, если пользователь выбирает 5 октября 2020 г. с 15:00 до 16:00, интерфейс возвращает datetime.datetime(2020, 10, 5, 15, 0, 0) как начало и datetime.datetime(2011, 10, 5, 16, 0, 0) как конец.

Что мне нужно сделать, так это взять наивное datetime и преобразовать его в UTC для хранения в базе данных. Каждый пользователь системы уже указал свои предпочтения часового пояса, поэтому наивное datetime считается тем же часовым поясом, что и их предпочтение часового пояса. Очевидно, что дату и время нужно хранить относительно UTC, чтобы, если пользователи изменят свой часовой пояс, существующие события по-прежнему будут отображаться в правильное время, в которое они запланировали их.

Интерфейс находится вне моего контроля, поэтому я не могу изменить получаемые данные. Дизайн базы данных также находится вне моего контроля, поэтому я не могу изменить, какие данные хранятся и как.

Вот примерный подход, который я использовал до сих пор:

import pytz
def convert_to_UTC(naive_datetime, user_tz_preference):
    user_datetime = naive_datetime.replace(tzinfo=user_tz_preference)
    utc_datetime = user_datetime.astimezone(pytz.utc)

Проблема, с которой я столкнулся, связана с переходом на летнее время:

>>> from datetime import datetime
>>> import pytz
>>> user_tz_preference = pytz.timezone('US/Pacific')
>>> naive_datetime = datetime(2011, 10, 26, 12, 0, 0)
>>> user_datetime = naive_datetime.replace(tzinfo=user_tz_preference)
>>> user_datetime
datetime.datetime(2011, 10, 26, 12, 0, tzinfo=<DstTzInfo 'US/Pacific' PST-1 day, 16:00:00 STD>)
>>> received_utc = user_datetime.astimezone(pytz.utc)
>>> received_utc
datetime.datetime(2011, 10, 26, 20, 0, tzinfo=<UTC>)
>>> expected_utc = datetime(2011, 10, 26, 19, 0, tzinfo=pytz.utc)
>>> expected_utc == received_utc
False

Обратите внимание, что при использовании «replace» часовой пояс устанавливается на PST вместо PDT независимо от даты, что дает смещение UTC на 8 часов вместо ожидаемого смещения на 7 часов DST, поэтому время в конечном итоге сохраняется неправильно.

Какие у меня есть варианты для преобразования наивного datetime в правильное PDT (или другое DST относительно часового пояса) tzinfo?

(Также обратите внимание, что не все пользователи живут в часовом поясе, который соблюдает DST, или могут жить в часовом поясе, который переключается в разное время, поэтому для решения, такого как корректировка timedelta перед сохранением, мне нужно знать, если часовой пояс поддерживает DST, и в какие даты он переключается).


person Jay    schedule 02.11.2011    source источник
comment
enricozini.org/2009/debian/using-python-datetime   -  person Maxim Egorushkin    schedule 03.11.2011
comment
возможный дубликат Как сделать неизвестный часовой пояс datetime осведомленным в питон   -  person Pureferret    schedule 18.05.2015
comment
Я ответил на такой вопрос, пожалуйста, проверьте, что astimezone () нельзя применить к наивному datetime, это может помочь   -  person Joseph Daudi    schedule 19.06.2018


Ответы (2)


Функция Pytz localize может сделать это: http://pytz.sourceforge.net/#localized-times-and-date-arithmetic

from datetime import datetime
import pytz    

tz = pytz.timezone('US/Pacific')
naive_dt = datetime(2020, 10, 5, 15, 0, 0) 
utc_dt = tz.localize(naive_dt, is_dst=None).astimezone(pytz.utc)
# -> 2020-10-05 22:00:00+00:00
person Mark Ransom    schedule 02.11.2011
comment
Спасибо. Похоже, это то, что я искал. - person Jay; 03.11.2011

С zoneinfo из стандартной библиотеки Python 3.9:

from datetime import datetime
from zoneinfo import ZoneInfo   

naive_datetime = datetime(2011, 10, 26, 12, 0, 0)
user_tz_preference = ZoneInfo('US/Pacific')

# it is safe to replace the tzinfo:
user_datetime = naive_datetime.replace(tzinfo=user_tz_preference)
# ...or set it directly:
user_datetime = datetime(2011, 10, 26, 12, tzinfo=ZoneInfo('US/Pacific'))

# astimezone works as before:
utc_datetime = user_datetime.astimezone(ZoneInfo('UTC'))

print(repr(user_datetime))
# datetime.datetime(2011, 10, 26, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='US/Pacific'))
print(user_datetime.isoformat())
# 2011-10-26T12:00:00-07:00
print(utc_datetime.isoformat())
# 2011-10-26T19:00:00+00:00

документы

person MrFuppes    schedule 22.10.2020