Czy konwertować datetime UTC Pythona na lokalną datę i godzinę, używając tylko standardowej biblioteki Pythona?

189

Mam instancję datetime Pythona, która została utworzona przy użyciu datetime.utcnow () i trwała w bazie danych.

Do wyświetlania chciałbym przekonwertować instancję datetime pobraną z bazy danych na lokalną datetime przy użyciu domyślnej lokalnej strefy czasowej (tj. Tak jakby data została utworzona przy użyciu datetime.now ()).

Jak przekonwertować datę / godzinę UTC na lokalną datę / godzinę, używając tylko standardowej biblioteki Pythona (np. Brak zależności Pytz)?

Wydaje się, że jednym rozwiązaniem byłoby użycie datetime.astimezone (tz), ale jak uzyskać domyślną lokalną strefę czasową?

Nitro Zark
źródło
W jakim formacie trwał czas do bazy danych? Jeśli jest to format standardowy, być może nie trzeba wykonywać żadnej konwersji.
Apalala,
1
Ta odpowiedź pokazuje prosty sposób używania pytza .
Juan

Odpowiedzi:

275

W Python 3.3+:

from datetime import datetime, timezone

def utc_to_local(utc_dt):
    return utc_dt.replace(tzinfo=timezone.utc).astimezone(tz=None)

W Pythonie 2/3:

import calendar
from datetime import datetime, timedelta

def utc_to_local(utc_dt):
    # get integer timestamp to avoid precision lost
    timestamp = calendar.timegm(utc_dt.timetuple())
    local_dt = datetime.fromtimestamp(timestamp)
    assert utc_dt.resolution >= timedelta(microseconds=1)
    return local_dt.replace(microsecond=utc_dt.microsecond)

Za pomocą pytz(oba Python 2/3):

import pytz

local_tz = pytz.timezone('Europe/Moscow') # use your local timezone name here
# NOTE: pytz.reference.LocalTimezone() would produce wrong result here

## You could use `tzlocal` module to get local timezone on Unix and Win32
# from tzlocal import get_localzone # $ pip install tzlocal

# # get local timezone    
# local_tz = get_localzone()

def utc_to_local(utc_dt):
    local_dt = utc_dt.replace(tzinfo=pytz.utc).astimezone(local_tz)
    return local_tz.normalize(local_dt) # .normalize might be unnecessary

Przykład

def aslocaltimestr(utc_dt):
    return utc_to_local(utc_dt).strftime('%Y-%m-%d %H:%M:%S.%f %Z%z')

print(aslocaltimestr(datetime(2010,  6, 6, 17, 29, 7, 730000)))
print(aslocaltimestr(datetime(2010, 12, 6, 17, 29, 7, 730000)))
print(aslocaltimestr(datetime.utcnow()))

Wynik

Python 3.3
2010-06-06 21:29:07.730000 MSD+0400
2010-12-06 20:29:07.730000 MSK+0300
2012-11-08 14:19:50.093745 MSK+0400
Python 2
2010-06-06 21:29:07.730000 
2010-12-06 20:29:07.730000 
2012-11-08 14:19:50.093911 
pytz
2010-06-06 21:29:07.730000 MSD+0400
2010-12-06 20:29:07.730000 MSK+0300
2012-11-08 14:19:50.146917 MSK+0400

Uwaga: bierze pod uwagę czas letni i ostatnią zmianę przesunięcia utc dla strefy czasowej MSK.

Nie wiem, czy rozwiązania inne niż pytz działają w systemie Windows.

jfs
źródło
1
co robi „normalizacja”?
avi
4
@avi: ogólnie: pytz: Dlaczego normalizacja jest potrzebna podczas konwersji między strefami czasowymi? . Uwaga: Przy obecnej implementacji nie jest to konieczne, .normalize()ponieważ źródłową strefą czasową dla .astimezone()jest UTC.
jfs
1
TypeError: replace() takes no keyword argumentsPojawia się, gdy próbuję rozwiązania pytz.
Craig
@Craig kod działa tak, jak jest, jak pokazuje wynik w odpowiedzi. Utwórz kompletny przykład minimalnego kodu, który prowadzi do błędu typu, napisz o Pythonie, używanych wersjach pytz i opublikuj jako osobne pytanie o przepełnienie stosu.
jfs
Szybka wskazówka dotycząca rozwiązania Python 3.3+. Aby przejść w przeciwnym kierunku (natywny dla UTC), działa to: local_dt.replace(tzinfo=None).astimezone(tz=timezone.utc)
jasonrhaas
50

Nie można tego zrobić tylko ze standardową biblioteką, ponieważ standardowa biblioteka nie ma żadnych stref czasowych. Potrzebujesz pytza lub dateutil .

>>> from datetime import datetime
>>> now = datetime.utcnow()
>>> from dateutil import tz
>>> HERE = tz.tzlocal()
>>> UTC = tz.gettz('UTC')

The Conversion:
>>> gmt = now.replace(tzinfo=UTC)
>>> gmt.astimezone(HERE)
datetime.datetime(2010, 12, 30, 15, 51, 22, 114668, tzinfo=tzlocal())

Lub cóż, możesz to zrobić bez pytza lub dateutil, wdrażając własne strefy czasowe. Ale to byłoby głupie.

Lennart Regebro
źródło
Dzięki, będę mieć to pod ręką, jeśli kiedykolwiek będę potrzebować pełnego rozwiązania. Czy dateutil obsługuje Python 3.1 (mówi Python 2.3+, ale jest podejrzany)?
Nitro Zark,
pytz rzeczywiście lepiej radzi sobie z przełączaniem DST.
Lennart Regebro
2
Aktualizacja: Pytz i dateutil obsługują obecnie Python 3.
Lennart Regebro
1
@JFSebastian: dateutil nie może rozróżnić dwóch 1:30 razy, które mają miejsce podczas zmiany DST. Jeśli musisz być w stanie rozróżnić te dwa razy, obejdź to, aby użyć pytza, który może to obsłużyć.
Lennart Regebro
8

Nie możesz tego zrobić ze standardową biblioteką. Za pomocą modułu pytz możesz przekonwertować dowolny naiwny / świadomy obiekt datetime na dowolną inną strefę czasową. Zobaczmy przykłady użycia Pythona 3.

Naiwne obiekty tworzone metodą klasową utcnow()

Aby przekonwertować naiwny obiekt do jakiejkolwiek innej strefie czasowej, najpierw trzeba przekształcić go w świadomy obiektu datetime. Możesz użyć tej replacemetody do konwersji naiwnego obiektu datetime na znany obiekt datetime. Następnie, aby przekonwertować świadomy obiekt datetime na dowolną inną strefę czasową, możesz użyć astimezonemetody.

Zmienna pytz.all_timezonespodaje listę wszystkich dostępnych stref czasowych w module pytz.

import datetime,pytz

dtobj1=datetime.datetime.utcnow()   #utcnow class method
print(dtobj1)

dtobj3=dtobj1.replace(tzinfo=pytz.UTC) #replace method

dtobj_hongkong=dtobj3.astimezone(pytz.timezone("Asia/Hong_Kong")) #astimezone method
print(dtobj_hongkong)

Naiwne obiekty tworzone metodą klasową now()

Ponieważ nowmetoda zwraca bieżącą datę i godzinę, dlatego najpierw należy ustawić strefę czasową obiektu datetime. Ta localize funkcja przekształca naiwny obiekt daty i godziny w obiekt daty i godziny zgodny ze strefą czasową. Następnie możesz użyć tej astimezonemetody do przekształcenia go w inną strefę czasową.

dtobj2=datetime.datetime.now()

mytimezone=pytz.timezone("Europe/Vienna") #my current timezone
dtobj4=mytimezone.localize(dtobj2)        #localize function

dtobj_hongkong=dtobj4.astimezone(pytz.timezone("Asia/Hong_Kong")) #astimezone method
print(dtobj_hongkong)
N Randhawa
źródło
6

Wydaje mi się, że to rozgryzłem: oblicza liczbę sekund od epoki, a następnie konwertuje na lokalną strefę czasową za pomocą time.localtime, a następnie konwertuje strukturę czasu z powrotem na datę i godzinę ...

EPOCH_DATETIME = datetime.datetime(1970,1,1)
SECONDS_PER_DAY = 24*60*60

def utc_to_local_datetime( utc_datetime ):
    delta = utc_datetime - EPOCH_DATETIME
    utc_epoch = SECONDS_PER_DAY * delta.days + delta.seconds
    time_struct = time.localtime( utc_epoch )
    dt_args = time_struct[:6] + (delta.microseconds,)
    return datetime.datetime( *dt_args )

Poprawnie stosuje letni / zimowy czas letni:

>>> utc_to_local_datetime( datetime.datetime(2010, 6, 6, 17, 29, 7, 730000) )
datetime.datetime(2010, 6, 6, 19, 29, 7, 730000)
>>> utc_to_local_datetime( datetime.datetime(2010, 12, 6, 17, 29, 7, 730000) )
datetime.datetime(2010, 12, 6, 18, 29, 7, 730000)
Nitro Zark
źródło
1
Ugh. Ale to powinno działać przynajmniej na Uniksie. W systemie Windows może być niepoprawne, jeśli od czasu konwersji daty zmieniły się reguły dotyczące czasu letniego. Dlaczego nie chcesz korzystać z biblioteki?
Lennart Regebro
Potrzebuję tylko konwersji utc / local, więc przeciąganie takiej biblioteki wydaje się przesadą. Ponieważ jestem jedynym użytkownikiem aplikacji, mogę żyć z pewnymi ograniczeniami. Pozwala także uniknąć problemów związanych z zależnościami (obsługa Python 3 ?, jakość Windows? ...), co byłoby bardziej kosztowne niż praca nad moim małym skryptem.
Nitro Zark,
1
Dobrze. jeśli potrzebujesz Python3, nie masz szczęścia. Ale w przeciwnym razie badanie i tworzenie własnego rozwiązania jest przesadą w porównaniu do korzystania z biblioteki.
Lennart Regebro
1
+1 (aby zrównoważyć negatywną opinię: może być ważnym wymogiem poszukiwanie rozwiązań tylko dla stdlib) z wymienionym zastrzeżeniem @ Lennart. Biorąc pod uwagę, że dateutil może zawieść zarówno w systemie Unix, jak i Windows. btw, możesz wyodrębnić utctotimestamp(utc_dt) -> (seconds, microseconds)ze swojego kodu dla przejrzystości, patrz implementacja Python 2.6-3.x
jfs
1
Pytz (i dateutil) działa na Pythonie 3 od pewnego czasu, więc problemy z Python3 / Windows / Quality nie są już problemem.
Lennart Regebro
6

Opierając się na komentarzu Aleksieja. To też powinno działać dla DST.

import time
import datetime

def utc_to_local(dt):
    if time.localtime().tm_isdst:
        return dt - datetime.timedelta(seconds = time.altzone)
    else:
        return dt - datetime.timedelta(seconds = time.timezone)
John Kerns
źródło
4

Standardowa biblioteka Pythona nie zawiera żadnych tzinfoimplementacji. Zawsze uważałem to za zaskakującą wadę modułu datetime.

Dokumentacji dla klasy tzinfo ma pochodzić z kilku przydatnych przykładów. Poszukaj dużego bloku kodu na końcu sekcji.

Mark Ransom
źródło
1
Sądzę, że podstawowa implementacja jest podobna, ponieważ baza danych stref czasowych musi być regularnie aktualizowana, ponieważ niektóre kraje nie mają ustalonych reguł określających okresy czasu letniego. Jeśli się nie mylę, na przykład JVM musi zostać zaktualizowany, aby uzyskać bazę danych ostatniej strefy czasowej ...
Nitro Zark
@Nitro Zark, przynajmniej powinni mieć jeden dla UTC. osModuł mógł pod warunkiem, jeden dla czasu lokalnego na podstawie funkcji systemu operacyjnego.
Mark Ransom,
1
Sprawdzałem listę nowych funkcji w 3.2 i dodałem strefę czasową UTC w 3.2 ( docs.python.org/dev/whatsnew/3.2.html#datetime ). Wydaje się, że nie ma lokalnej strefy czasowej ...
Nitro Zark,
2

Python 3.9 dodaje zoneinfomoduł, więc teraz można to zrobić w następujący sposób (tylko stdlib):

from zoneinfo import ZoneInfo
from datetime import datetime

utc_unaware = datetime(2020, 10, 31, 12)  # loaded from database
utc_aware = utc_unaware.replace(tzinfo=ZoneInfo('UTC'))  # make aware
local_aware = utc_aware.astimezone(ZoneInfo('localtime'))  # convert

Europa Środkowa jest o 1 lub 2 godziny przed UTC, więc local_awarejest:

datetime.datetime(2020, 10, 31, 13, 0, tzinfo=backports.zoneinfo.ZoneInfo(key='localtime'))

jako str:

2020-10-31 13:00:00+01:00

System Windows nie ma systemowej bazy danych stref czasowych, dlatego potrzebny jest dodatkowy pakiet:

pip install tzdata  

Istnieje backport pozwalający na użycie w Pythonie od 3.6 do 3.8 :

sudo pip install backports.zoneinfo

Następnie:

from backports.zoneinfo import ZoneInfo
xjcl
źródło
nawet zoneinfotu nie potrzebujesz - zobacz pierwszą część odpowiedzi JFS (część Python3.3 +) ;-)
MrFuppes
@MrFuppes Myślę, że ZoneInfo('localtime')jest o wiele jaśniejszy niż tz=None(w końcu można oczekiwać tz=Noneusunięcia strefy czasowej lub zwrócenia UTC zamiast czasu lokalnego).
xjcl
@MrFuppes Plus pytanie może obejmować OP posiadające stronę internetową i chcące pokazywać użytkownikom znaczniki czasu w czasie lokalnym. W tym celu musiałbyś przekonwertować się na jawną strefę czasową użytkownika, a nie czas lokalny.
xjcl
0

Prosty (ale może wadliwy) sposób, który działa w Pythonie 2 i 3:

import time
import datetime

def utc_to_local(dt):
    return dt - datetime.timedelta(seconds = time.timezone)

Jego zaletą jest to, że napisanie funkcji odwrotnej jest banalne

Aleksiej Averchenko
źródło
1
Daje to zły wynik, ponieważ time.timezonenie uwzględnia oszczędności światła dziennego.
guyarad
0

Najłatwiejszym sposobem, jaki znalazłem, jest uzyskanie przesunięcia czasowego w miejscu, w którym się znajdujesz, a następnie odjęcie go od godziny.

def format_time(ts,offset):
    if not ts.hour >= offset:
        ts = ts.replace(day=ts.day-1)
        ts = ts.replace(hour=ts.hour-offset)
    else:
        ts = ts.replace(hour=ts.hour-offset)
    return ts

To działa dla mnie w Python 3.5.2.

Zld Productions
źródło
0

Oto inny sposób zmiany strefy czasowej w formacie daty i godziny (wiem, że zmarnowałem na to energię, ale nie widziałem tej strony, więc nie wiem jak) bez min. i ust. bo nie potrzebuję tego do mojego projektu:

def change_time_zone(year, month, day, hour):
      hour = hour + 7 #<-- difference
      if hour >= 24:
        difference = hour - 24
        hour = difference
        day += 1
        long_months = [1, 3, 5, 7, 8, 10, 12]
        short_months = [4, 6, 9, 11]
        if month in short_months:
          if day >= 30:
            day = 1
            month += 1
            if month > 12:
              year += 1
        elif month in long_months:
          if day >= 31:
            day = 1
            month += 1
            if month > 12:
              year += 1
        elif month == 2:
          if not year%4==0:
            if day >= 29:
              day = 1
              month += 1
              if month > 12:
                year += 1
          else:
            if day >= 28:
              day = 1
              month += 1
              if month > 12:
                year += 1
      return datetime(int(year), int(month), int(day), int(hour), 00)
Gość
źródło
Użyj timedelta, aby przełączać się między strefami czasowymi. Wszystko czego potrzebujesz to przesunięcie w godzinach między strefami czasowymi. Nie musisz bawić się granicami dla wszystkich 6 elementów obiektu datetime. timedelta z łatwością radzi sobie z latami przestępnymi, stuleciami przestępnymi itp. Musisz „od daty importu timedelta”. Następnie, jeśli przesunięcie jest zmienną w godzinach: timeout = timein + timedelta (godziny = offset), gdzie timein i timeout są obiektami datetime. np. timein + timedelta (godziny = -8) konwertuje z GMT na PST.
Karl Rudnick,
0

Jest to okropny sposób na zrobienie tego, ale unika tworzenia definicji. Spełnia wymóg trzymania się podstawowej biblioteki Python3.

# Adjust from UST to Eastern Standard Time (dynamic)
# df.my_localtime should already be in datetime format, so just in case
df['my_localtime'] = pd.to_datetime.df['my_localtime']

df['my_localtime'] = df['my_localtime'].dt.tz_localize('UTC').dt.tz_convert('America/New_York').astype(str)
df['my_localtime'] = pd.to_datetime(df.my_localtime.str[:-6])
Arthur D. Howland
źródło
Chociaż jest to interesujące i zamierzam użyć tej metody, nie używa ona tylko stdlib. Wykorzystuje Pandy do konwersji pandas.pydata.org/pandas-docs/version/0.25/reference/api/…
Tim Hughes
-3

Użyj timedelta, aby przełączać się między strefami czasowymi. Wszystko czego potrzebujesz to przesunięcie w godzinach między strefami czasowymi. Nie musisz bawić się granicami dla wszystkich 6 elementów obiektu datetime. timedelta z łatwością radzi sobie z latami przestępnymi, stuleciami przestępnymi itp. Najpierw musisz

from datetime import datetime, timedelta

Jeśli offsetwięc delta strefy czasowej w godzinach:

timeout = timein + timedelta(hours = offset)

gdzie timein i timeout są obiektami datetime. na przykład

timein + timedelta(hours = -8)

konwertuje z GMT na PST.

Jak więc ustalić offset? Oto prosta funkcja, pod warunkiem, że masz tylko kilka możliwości konwersji bez użycia obiektów daty-godziny, które są „świadome” strefy czasowej, co robią niektóre inne odpowiedzi. Trochę manualnie, ale czasem klarowność jest najlepsza.

def change_timezone(timein, timezone, timezone_out):
    '''
    changes timezone between predefined timezone offsets to GMT
    timein - datetime object
    timezone - 'PST', 'PDT', 'GMT' (can add more as needed)
    timezone_out - 'PST', 'PDT', 'GMT' (can add more as needed)
    ''' 
    # simple table lookup        
    tz_offset =  {'PST': {'GMT': 8, 'PDT': 1, 'PST': 0}, \
                  'GMT': {'PST': -8, 'PDT': -7, 'GMT': 0}, \
                  'PDT': {'GMT': 7, 'PST': -1, 'PDT': 0}}
    try:
        offset = tz_offset[timezone][timezone_out]
    except:
        msg = 'Input timezone=' + timezone + ' OR output time zone=' + \
            timezone_out + ' not recognized'
        raise DateTimeError(msg)

    return timein + timedelta(hours = offset)

Po zapoznaniu się z licznymi odpowiedziami i sprawdzeniu najściślejszego kodu, jaki mogę sobie wyobrazić (na razie) najlepiej wydaje się, że wszystkie aplikacje, w których czas jest ważny i należy uwzględnić mieszane strefy czasowe, powinny podjąć prawdziwy wysiłek, aby wszystkie obiekty datetime "świadomy". Wtedy wydaje się, że najprostszą odpowiedzią jest:

timeout = timein.astimezone(pytz.timezone("GMT"))

na przykład na konwersję do GMT. Oczywiście, aby przekonwertować na dowolną inną strefę czasową, lokalną lub inną, wystarczy użyć odpowiedniego ciągu strefy czasowej, który rozumie Pytz (z pytz.all_timezones). Uwzględniany jest także czas letni.

Karl Rudnick
źródło
1
To jest / nie / dobry pomysł. Czas letni nie zmienia się w tym samym dniu dla całego świata. Aby to zrobić, użyj funkcji datetime.
Gabe
@ gabe - Zrozum, że stół nie jest dobrym pomysłem, chociaż może pasować do kogoś, kto obejmuje tylko amerykańskie strefy czasowe. Jak powiedziałem na końcu, najlepszym sposobem jest ZAWSZE sprawienie, aby obiekty datetime były „świadome”, aby można było łatwo korzystać z funkcji datetime.
Karl Rudnick