Как сделать неизвестный часовой пояс datetime в python

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

У меня есть объект 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
>>>

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

>>> 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
>>> 

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

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

Контекст

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

Ответ 1

В общем случае, чтобы сделать наивное datetime timezone-aware, используйте метод локализации:

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
from datetime import timezone

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

print(dt.replace(tzinfo=timezone.utc).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

Я использовал от 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

Я использую этот оператор в Django для преобразования неизвестного времени в известное:

from django.utils import timezone

dt_aware = timezone.make_aware(dt_unaware, timezone.get_current_timezone())

Ответ 5

Я согласен с предыдущими ответами, и это нормально, если вы в порядке, чтобы начать в UTC. Но я думаю, что это также распространенный сценарий для людей работать с знанием tz, которое имеет datetime, у которого есть локальный часовой пояс UTC.

Если бы вам просто нужно было по имени, можно было бы предположить, что replace() будет применимым и создаст правильный объект, поддерживающий дату и время. Это не так.

replace (tzinfo =...) кажется случайным в своем поведении. Поэтому бесполезно. Не используйте это!

localize - правильная функция для использования. Пример:

localdatetime_aware = tz.localize(datetime_nonaware)

Или более полный пример:

import pytz
from datetime import datetime
pytz.timezone('Australia/Melbourne').localize(datetime.now())

дает мне информацию о времени и времени текущего времени текущего времени:

datetime.datetime(2017, 11, 3, 7, 44, 51, 908574, tzinfo=<DstTzInfo 'Australia/Melbourne' AEDT+11:00:00 DST>)

Ответ 6

Это кодирует ответы @Sérgio и @unutbu . Он будет "просто работать" с объектом pytz.timezone или строка IANA Time Zone.

def make_tz_aware(dt, tz='UTC', is_dst=None):
    """Add timezone information to a datetime object, only if it is naive."""
    tz = dt.tzinfo or tz
    try:
        tz = pytz.timezone(tz)
    except AttributeError:
        pass
    return tz.localize(dt, is_dst=is_dst) 

Это похоже на то, что должно делать datetime.localize() (или .inform() или .awarify()), принимать как строки, так и объекты часового пояса для аргумента tz и по умолчанию для UTC, если часовой пояс не указан.

Ответ 7

Чтобы добавить локальный часовой пояс; (с использованием dateutil)

from dateutil import tz
import datetime

dt_unaware = datetime.datetime(2017, 6, 24, 12, 24, 36)

dt_aware = dt_unaware.replace(tzinfo=tz.tzlocal())

Ответ 8

два метода, которые я знаю чисто

from datetime import datetime
import pytz

naive = datetime.now()
aware = naive.replace(tzinfo=pytz.UTC)

или

aware = pytz.UTC.localize(naive)

конечно, вы можете использовать любой часовой пояс вместо UTC,

Ответ 9

В формате ответа unutbu; Я создал модуль утилиты, который обрабатывает подобные вещи с более интуитивным синтаксисом. Может устанавливаться с помощью пипа.

import datetime
import saturn

unaware = datetime.datetime(2011, 8, 15, 8, 15, 12, 0)
now_aware = saturn.fix_naive(unaware)

now_aware_madrid = saturn.fix_naive(unaware, 'Europe/Madrid')