Вопрос-Ответ

How to make a datetime object aware (not naive)

Как сделать объект datetime осведомленным (не наивным)

Что мне нужно сделать

У меня есть объект datetime, не зависящий от часового пояса, к которому мне нужно добавить часовой пояс, чтобы иметь возможность сравнивать его с другими объектами datetime, зависящими от часового пояса. Я не хочу, чтобы все мое приложение не знало о часовом поясе для этого единственного устаревшего случая.

Что я пробовал

Сначала, чтобы продемонстрировать проблему:

Python 2.6.1 (r261:67515, Jun 24 2010, 21:47:49) 
[GCC 4.2.1 (Apple Inc. build 5646)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import datetime
>>> import pytz
>>> unaware = datetime.datetime(2011,8,15,8,15,12,0)
>>> unaware
datetime.datetime(2011, 8, 15, 8, 15, 12)
>>> aware = datetime.datetime(2011,8,15,8,15,12,0,pytz.UTC)
>>> aware
datetime.datetime(2011, 8, 15, 8, 15, 12, tzinfo=<UTC>)
>>> aware == unaware
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can't compare offset-naive and offset-aware datetimes

Сначала я попробовал astimezone:

>>> unaware.astimezone(pytz.UTC)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: astimezone() cannot be applied to a naive datetime
>>>

Неудивительно, что это не удалось, поскольку на самом деле он пытается выполнить преобразование. Замена показалась лучшим выбором (согласно Как мне получить значение datetime.today() в Python, которое "осознает часовой пояс"?):

>>> unaware.replace(tzinfo=pytz.UTC)
datetime.datetime(2011, 8, 15, 8, 15, 12, tzinfo=<UTC>)
>>> unaware == aware
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can't compare offset-naive and offset-aware datetimes
>>>

Но, как вы можете видеть, replace, похоже, устанавливает tzinfo, но не делает объект осведомленным. Я готов вернуться к обработке входной строки, чтобы у нее был часовой пояс перед ее анализом (я использую dateutil для синтаксического анализа, если это имеет значение), но это кажется невероятно запутанным.

Кроме того, я пробовал это как в Python 2.6, так и в Python 2.7, с теми же результатами.

Контекст

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

Переведено автоматически
Ответ 1

В общем, чтобы сделать наивный объект datetime ориентированным на часовой пояс, используйте метод localize:

import datetime
import pytz

unaware = datetime.datetime(2011, 8, 15, 8, 15, 12, 0)
aware = datetime.datetime(2011, 8, 15, 8, 15, 12, 0, pytz.UTC)

now_aware = pytz.utc.localize(unaware)
assert aware == now_aware

Для часового пояса UTC на самом деле нет необходимости использовать localize поскольку нет необходимости обрабатывать расчет перехода на летнее время:

now_aware = unaware.replace(tzinfo=pytz.UTC)

работает. (.replace возвращает новое datetime; оно не изменяется unaware.)

Ответ 2

Во всех этих примерах используется внешний модуль, но вы можете достичь того же результата, используя только модуль datetime, как также представлено в этом ответе SO:

from datetime import datetime, timezone

dt = datetime.now()
dt = dt.replace(tzinfo=timezone.utc)

print(dt.isoformat())
# '2017-01-12T22:11:31+00:00'

Меньше зависимостей и нет проблем с pytz.

ПРИМЕЧАНИЕ: Если вы хотите использовать это с python3 и python2, вы также можете использовать это для импорта часового пояса (жестко заданного для UTC):

try:
from datetime import timezone
utc = timezone.utc
except ImportError:
#Hi there python2 user
class UTC(tzinfo):
def utcoffset(self, dt):
return timedelta(0)
def tzname(self, dt):
return "UTC"
def dst(self, dt):
return timedelta(0)
utc = UTC()
Ответ 3

Я написал этот скрипт на Python 2 в 2011 году, но никогда не проверял, работает ли он на Python 3.

Я перешел с dt_aware на dt_unaware:

dt_unaware = dt_aware.replace(tzinfo=None)

и dt_unware в dt_aware:

from pytz import timezone
localtz = timezone('Europe/Lisbon')
dt_aware = localtz.localize(dt_unware)
Ответ 4

Python 3.9 добавляет zoneinfo модуль, так что теперь нужна только стандартная библиотека!

from zoneinfo import ZoneInfo
from datetime import datetime
unaware = datetime(2020, 10, 31, 12)

Прикрепите часовой пояс:

>>> unaware.replace(tzinfo=ZoneInfo('Asia/Tokyo'))
datetime.datetime(2020, 10, 31, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='Asia/Tokyo'))
>>> str(_)
'2020-10-31 12:00:00+09:00'

Привязать локальный часовой пояс системы:

>>> unaware.replace(tzinfo=ZoneInfo('localtime'))
datetime.datetime(2020, 10, 31, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='localtime'))
>>> str(_)
'2020-10-31 12:00:00+01:00'

Впоследствии он должным образом преобразуется в другие часовые пояса:

>>> unaware.replace(tzinfo=ZoneInfo('localtime')).astimezone(ZoneInfo('Asia/Tokyo'))
datetime.datetime(2020, 10, 31, 20, 0, tzinfo=backports.zoneinfo.ZoneInfo(key='Asia/Tokyo'))
>>> str(_)
'2020-10-31 20:00:00+09:00'

Список доступных часовых поясов в Википедии


не имеет базы данных часовых поясов системы Windows, поэтому здесь необходим дополнительный пакет:

pip install tzdata  

Существует бэкпорт, позволяющий использовать zoneinfo в Python с 3.6 по 3.8:

pip install backports.zoneinfo

Затем:

from backports.zoneinfo import ZoneInfo
2023-12-31 18:58 python