Як зрозуміти невідомий часовий пояс дати в пітоні


506

Що мені потрібно зробити

У мене є невідомий часовий пояс об'єкт дати, до якого мені потрібно додати часовий пояс, щоб мати можливість порівнювати його з іншими об'єктами дат, відомих часовим поясом. Я не хочу перетворювати всю свою програму на часовий пояс, не знаючи для цього одного застарілого випадку.

Що я пробував

По-перше, щоб продемонструвати проблему:

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

По-перше, я спробував астимезон:

>>> 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 для розбору, якщо це має значення), але це здається неймовірно хитним.

Також я спробував це як у python 2.6, так і в python 2.7, з однаковими результатами.

Контекст

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


1
unaware.replace()повернеться, Noneякби він міняв unawareоб'єкт на місці. REPL показує, що тут .replace()повертається новий datetimeоб’єкт.
jfs

3
Що мені було потрібно, коли я прийшов сюди:import datetime; datetime.datetime.now(datetime.timezone.utc)
Мартін Тома

1
@MartinThoma Я б використав названий tzаргумент, щоб він був більш зрозумілим:datetime.datetime.now(tz=datetime.timezone.utc)
Acumenus

Відповіді:


594

Загалом, щоб зробити наївним часовий пояс дати, використовуйте метод локалізації :

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повертає новий час дати; він не змінюється unaware.)


10
Ну, я відчуваю себе дурним. Замінити повертає новий час. Там сказано, що прямо там, і в документах, я це зовсім пропустив. Дякую, саме це я шукав.
Марк Тоцзі

2
Msgstr "Замінити повертає новий час." Так. Підказка, яку дає REPL, полягає в тому, що вона показує вам повернене значення. :)
Карл Кнечтел - вдома від

спасибі, я повинен був використовувати від dt_aware до некондиційного dt_unware = datetime.datetime (* (dt_aware.timetuple () [: 6])),
Сержіо

4
якщо часовий пояс не є UTC, не використовуйте конструктор безпосередньо:, aware = datetime(..., tz)використовуйте .localize()замість цього.
jfs

1
Варто згадати, що місцевий час може бути неоднозначним. tz.localize(..., is_dst=None)стверджує, що це не так.
jfs

166

У всіх цих прикладах використовується зовнішній модуль, але ви можете досягти такого ж результату, використовуючи лише модуль дати, як це також представлено у цій відповіді ТА :

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

10
Дуже гарна відповідь щодо запобігання pytzпроблем, я радий, що трохи прокрутився вниз! pytzДійсно не хотів братися за мої віддалені сервери :)
Tregoreg

7
Зауважте, що from datetime import timezoneпрацює в py3, але не в py2.7.
7yl4r

11
Слід зазначити, що dt.replace(tzinfo=timezone.utc)повертає новий час, він не змінюється dtна місці. (Я відредагую, щоб це показати).
Blairg23

2
Як ви можете замість використання timezone.utc надавати інший часовий пояс у якості рядка (наприклад, "Америка / Чикаго")?
гарбуз

2
@bumpkin краще пізно, ніж ніколи, я здогадуюсь:tz = pytz.timezone('America/Chicago')
Флоріан

82

Я використовував від 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)

але відповідь раніше - також хороше рішення.


2
ви могли б використовувати localtz.localize(dt_unware, is_dst=None)виняток, якщо dt_unwareпредставляє неіснуючий або неоднозначний місцевий час (зверніть увагу: у попередній редакції вашої відповіді не було жодної проблеми, де localtzбув UTC, оскільки у UTC немає переходів DST
jfs

@JF Sebastian, перший коментар застосовується
SERGIO

1
Дякую, що показали обидва напрямки конверсії.
Крістіан Лонг

42

Я використовую цей вислів у Django, щоб перетворити невідомий час на усвідомлене:

from django.utils import timezone

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

2
Мені подобається це рішення (+1), але воно залежить від Джанго, а це не те, що шукали (-1). =)
mkoistinen

3
Ви насправді не є другим аргументом. Аргумент за замовчуванням (None) буде означати, що локальний часовий пояс використовується неявно. Те саме з DST (це третій аргумент_
Олі,

14

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

Якби ви просто йшли по імені, певно, можна зробити висновок про заміну () і застосувати правильний об'єкт, що знає дату. Це не так.

заміна (tzinfo = ...) здається випадковою у своїй поведінці . Тому марно. Не використовуйте це!

локалізація - це правильна функція, яку потрібно використовувати. Приклад:

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

3
Для цього потрібно більше оновлень, спроби зробити replace(tzinfo=...)це в часовий пояс, відмінний від UTC, призведе до порушення вашого часу. Я отримав -07:53замість, -08:00наприклад. Див stackoverflow.com/a/13994611/1224827
Blairg23

Чи можете ви навести відтворюваний приклад replace(tzinfo=...)несподіваної поведінки?
xjcl

11

Використовуйте, dateutil.tz.tzlocal()щоб отримати часовий пояс у використанні datetime.datetime.now()та datetime.datetime.astimezone():

from datetime import datetime
from dateutil import tz

unlocalisedDatetime = datetime.now()

localisedDatetime1 = datetime.now(tz = tz.tzlocal())
localisedDatetime2 = datetime(2017, 6, 24, 12, 24, 36, tz.tzlocal())
localisedDatetime3 = unlocalisedDatetime.astimezone(tz = tz.tzlocal())
localisedDatetime4 = unlocalisedDatetime.replace(tzinfo = tz.tzlocal())

Зауважте, що datetime.astimezoneспочатку перетворите ваш datetimeоб’єкт у UTC, а потім у часовий пояс, що є тим самим, що й дзвінки datetime.replaceз початковою інформацією про часовий пояс None.


1
Якщо ви хочете зробити це UTC:.replace(tzinfo=dateutil.tz.UTC)
Мартін Тома

2
На один імпорт менше і просто:.replace(tzinfo=datetime.timezone.utc)
kubanczyk

9

Це кодифікує відповіді @Sergio та @ unutbu . Він буде "просто працювати" або з pytz.timezoneоб'єктом, або з рядком часової зони IANA .

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, якщо не вказано часовий пояс.


1
Дякую, це допомогло мені "брендувати" необроблений об'єкт дати як "UTC", не спершу система вважала, що це місцевий час, а потім перераховує значення!
Nikhil VJ

2

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'

Перелік доступних часових поясів у Вікіпедії


Існує резервний порт, що дозволяє використовувати в Python 3.6 до 3.8 :

sudo pip install backports.zoneinfo

Тоді:

from backports.zoneinfo import ZoneInfo

0

У форматі відповіді унутбу; Я зробив модуль утиліти, який обробляє подібні речі, з більш інтуїтивним синтаксисом. Можна встановити за допомогою pip.

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


0

зовсім нове для Python, і я зіткнувся з тим же питанням. Я вважаю це рішення досить простим, і для мене воно прекрасно працює (Python 3.6):

unaware=parser.parse("2020-05-01 0:00:00")
aware=unaware.replace(tzinfo=tz.tzlocal()).astimezone(tz.tzlocal())

0

Зміна між часовими поясами

import pytz
from datetime import datetime

other_tz = pytz.timezone('Europe/Madrid')

# From random aware datetime...
aware_datetime = datetime.utcnow().astimezone(other_tz)
>> 2020-05-21 08:28:26.984948+02:00

# 1. Change aware datetime to UTC and remove tzinfo to obtain an unaware datetime
unaware_datetime = aware_datetime.astimezone(pytz.UTC).replace(tzinfo=None)
>> 2020-05-21 06:28:26.984948

# 2. Set tzinfo to UTC directly on an unaware datetime to obtain an utc aware datetime
aware_datetime_utc = unaware_datetime.replace(tzinfo=pytz.UTC)
>> 2020-05-21 06:28:26.984948+00:00

# 3. Convert the aware utc datetime into another timezone
reconverted_aware_datetime = aware_datetime_utc.astimezone(other_tz)
>> 2020-05-21 08:28:26.984948+02:00

# Initial Aware Datetime and Reconverted Aware Datetime are equal
print(aware_datetime1 == aware_datetime2)
>> True
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.