Jak uświadamiać nieświadomą strefę czasową datetime w Pythonie

Co muszę zrobić

Mam obiekt datetime nieświadomy strefy czasowej, do którego muszę dodać strefę czasową, aby móc porównać go z innymi obiektami datetime znającymi strefę czasową. Nie chcę przekonwertować całej mojej aplikacji na strefę czasową.

Czego próbowałem

Po pierwsze, aby zademonstrować problem:

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

Najpierw próbowałem 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
>>>
Nie dziwi mnie to, że się nie udało, ponieważ jest próbuję dokonać konwersji. Replace wydawało się lepszym wyborem (zgodnie z Python: jak uzyskać wartość datetime.dzisiaj() czyli"strefa czasowa"?):
>>> 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
>>> 

Ale jak widzisz, replace wydaje się ustawiać tzinfo, ale nie uświadamiać obiektu. Przygotowuję się do powrotu do doctoringu łańcucha wejściowego, aby mieć strefę czasową przed parsowaniem (używam dateutil do parsowania, jeśli to ma znaczenie), ale wydaje się to niesamowicie kludgy.

Również, próbowałem tego w obu python 2.6 i python 2.7, z tymi samymi wynikami.

Context

Piszę parser dla niektórych plików danych. Istnieje stary format, który muszę obsługiwać, gdzie ciąg daty nie ma wskaźnika strefy czasowej. Naprawiłem już źródło danych, ale nadal muszę obsługiwać stary format danych. Jednorazowa konwersja starszych danych nie jest opcją z różnych powodów biznesowych. Chociaż ogólnie rzecz biorąc, nie podoba mi się pomysł hard-kodowania domyślnej strefy czasowej, w tym sprawa wydaje się najlepszą opcją. Wiem z rozsądną pewnością, że wszystkie starsze dane, o których mowa, są w UTC, więc jestem gotów zaakceptować ryzyko niewykonania tego w tym przypadku.

Author: Community, 2011-08-15

9 answers

Ogólnie rzecz biorąc, aby uświadomić sobie naiwną strefę czasową datetime, użyj metody 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

Dla strefy czasowej UTC nie jest konieczne używanie localize, ponieważ nie ma obliczeń czasu letniego do obsługi:

now_aware = unaware.replace(tzinfo=pytz.UTC)
Działa. (.replace zwraca nową datetime; nie modyfikuje unaware.)
 407
Author: unutbu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-08 02:47:03

Wszystkie te przykłady używają zewnętrznego modułu, ale możesz osiągnąć ten sam wynik używając tylko modułu datetime.

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'

Mniej zależności i brak problemów z pytz.

Uwaga: jeśli chcesz użyć tego z python3 i python2, możesz użyć tego również do importu strefy czasowej (na twardo dla 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()
 66
Author: kang,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-10 13:18:31

Użyłem z dt_aware do dt_unware

dt_unaware = dt_aware.replace(tzinfo=None)

I dt_unware do dt_aware

from pytz import timezone
localtz = timezone('Europe/Lisbon')
dt_aware = localtz.localize(dt_unware)
Ale odpowiedź przed jest również dobrym rozwiązaniem.
 61
Author: Sérgio,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-15 21:00:12

Używam tego stwierdzenia w Django do konwersji nieświadomego czasu na świadomego:

from django.utils import timezone

dt_aware = timezone.make_aware(dt_unaware, timezone.get_current_timezone())
 34
Author: Googol,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-19 10:10:01

To kodyfikuje odpowiedzi @Sérgio i @unutbu . Będzie "po prostu działać" z obiektem pytz.timezone lub łańcuchemIANA 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) 

Wygląda na to, co datetime.localize() (lub .inform() lub .awarify()) powinno robić, akceptować zarówno ciągi znaków, jak i Obiekty strefy czasowej dla argumentu tz i domyślnie ustawiać na UTC, jeśli nie podano strefy czasowej.

 8
Author: hobs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:25

Zgadzam się z poprzednimi odpowiedziami i jest w porządku, jeśli możesz zacząć w UTC. Ale myślę, że jest to również powszechny scenariusz dla ludzi do pracy z wartością świadomą TZ, która ma datetime, która ma lokalną strefę czasową non UTC.

Jeśli miałbyś używać nazwy, prawdopodobnie można by wywnioskować, że replace() będzie miało zastosowanie i wytworzy właściwy obiekt DateTime aware. Tak nie jest.

Replace (tzinfo=... ) wydaje się być przypadkowy w swoim zachowaniu . Jest zatem bezużyteczne. Nie używaj tego!

Localize jest właściwą funkcją do użycia. Przykład:

localdatetime_aware = tz.localize(datetime_nonaware)

Lub bardziej kompletny przykład:

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

Podaje mi wartość DateTime strefy czasowej bieżącego czasu lokalnego:

datetime.datetime(2017, 11, 3, 7, 44, 51, 908574, tzinfo=<DstTzInfo 'Australia/Melbourne' AEDT+11:00:00 DST>)
 4
Author: paolov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-02 21:29:06

W celu dodania lokalnej strefy czasowej; (używając 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())
 2
Author: Ahmet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-22 00:15:14

Two method I know purely

from datetime import datetime
import pytz

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

Lub

aware = pytz.UTC.localize(naive)

Oczywiście możesz użyć dowolnej strefy czasowej zamiast UTC,

 2
Author: Doom,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-22 02:46:44

W formacie odpowiedzi unutbu; zrobiłem moduł narzędziowy, który obsługuje takie rzeczy, z bardziej intuicyjną składnią. Może być zainstalowany za pomocą 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
Author: Turtles Are Cute,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-25 23:31:51