Convertire un datetime UTC di Python in un datetime locale usando solo la libreria standard di Python?


189

Ho un'istanza datetime python che è stata creata usando datetime.utcnow () e persistente nel database.

Per la visualizzazione, vorrei convertire l'istanza del datetime recuperata dal database in datetime locale utilizzando il fuso orario locale predefinito (ovvero, come se il datetime fosse stato creato utilizzando datetime.now ()).

Come posso convertire il datetime UTC in un datetime locale usando solo la libreria standard di Python (es. Nessuna dipendenza pytz)?

Sembra che una soluzione sarebbe usare datetime.astimezone (tz), ma come otterresti il ​​fuso orario locale predefinito?


In quale formato è stato il tempo persistito nel database? Se si tratta di un formato standard, è possibile che non sia necessario eseguire alcuna conversione.
Apalala,

1
Questa risposta mostra un modo semplice di usare pytz .
juan,

Risposte:


275

In Python 3.3+:

from datetime import datetime, timezone

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

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

Utilizzando pytz(entrambi 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

Esempio

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

Produzione

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

Nota: tiene conto dell'ora legale e della recente modifica dell'offset utc per il fuso orario MSK.

Non so se le soluzioni non pytz funzionano su Windows.


1
cosa fa "normalizzare"?
avi

4
@avi: in generale: pytz: perché è necessaria la normalizzazione durante la conversione tra fusi orari? . Nota: non è necessario con l'implementazione corrente di .normalize()perché il fuso orario di origine per .astimezone()è UTC.
jfs,

1
Ottengo TypeError: replace() takes no keyword argumentsquando provo la soluzione pytz.
Craig,

@Craig il codice funziona così com'è, come dimostra l'output nella risposta. Crea un esempio di codice minimo completo che porti a TypeError, menziona le tue versioni Python e pytz che usi e pubblicalo come domanda separata di StackTranslate.it.
jfs,

Suggerimento rapido per la soluzione Python 3.3+. Per andare nella direzione opposta (da Nativo a UTC), funziona: local_dt.replace(tzinfo=None).astimezone(tz=timezone.utc)
jasonrhaas

50

Non puoi farlo solo con la libreria standard poiché la libreria standard non ha fusi orari. Hai bisogno di Pytz o 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())

O bene, puoi farlo senza pytz o dateutil implementando i tuoi fusi orari. Ma sarebbe sciocco.


Grazie, lo terrò a portata di mano se avrò mai bisogno di una soluzione completa. Dateutil supporta Python 3.1 (dice Python 2.3+ ma è sospetto)?
Nitro Zark,


pytz in effetti gestisce meglio i cambi di ora legale.
Lennart Regebro,

2
Aggiornamento: pytz e dateutil supportano entrambi Python 3 al giorno d'oggi.
Lennart Regebro,

1
@JFSebastian: dateutil non può distinguere tra le due volte 1:30 che si verificano durante un passaggio all'ora legale. Se devi essere in grado di distinguere tra quelle due volte, la soluzione è di usare pytz, che può gestirlo.
Lennart Regebro,

8

Non puoi farlo con la libreria standard. Utilizzando il modulo pytz è possibile convertire qualsiasi oggetto datetime ingenuo / consapevole in qualsiasi altro fuso orario. Vediamo alcuni esempi usando Python 3.

Oggetti ingenui creati con il metodo class utcnow()

Per convertire un oggetto ingenuo in qualsiasi altro fuso orario, devi prima convertirlo in un oggetto datetime consapevole . È possibile utilizzare il replacemetodo per convertire un oggetto datetime ingenuo in un oggetto datetime consapevole . Quindi per convertire un oggetto datetime consapevole in qualsiasi altro fuso orario puoi usare il astimezonemetodo.

La variabile pytz.all_timezonesfornisce l'elenco di tutti i fusi orari disponibili nel modulo 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)

Oggetti ingenui creati con il metodo class now()

Poiché il nowmetodo restituisce la data e l'ora correnti, è necessario rendere prima consapevole il fuso orario dell'oggetto datetime. La localize funzione converte un oggetto datetime ingenuo in un oggetto datetime sensibile al fuso orario. Quindi è possibile utilizzare il astimezonemetodo per convertirlo in un altro fuso orario.

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)

6

Penso di averlo capito: calcola il numero di secondi dall'epoca, quindi si converte in un timzeone locale usando time.localtime, e quindi converte la struttura temporale in un datetime ...

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 )

Si applica correttamente l'ora legale estate / inverno:

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

1
Ugh. Ma questo dovrebbe funzionare su Unix, almeno. Per Windows può essere errato se le regole dell'ora legale sono cambiate dalla data di conversione. Perché non vuoi usare una biblioteca?
Lennart Regebro,

Ho solo bisogno della conversione utc / locale, quindi trascinare una libreria del genere sembra essere eccessivo. Dato che sono l'unico utente dell'applicazione, posso vivere con alcune limitazioni. Evita anche di gestire i problemi relativi alle dipendenze (supporto di Python 3? Windows? Qualità ...) che sarebbe più costoso che lavorare sul mio piccolo script.
Nitro Zark,

1
Bene. se hai bisogno di Python3, sei sfortunato. Ma altrimenti la ricerca e la realizzazione della propria soluzione è eccessiva rispetto all'utilizzo di una libreria.
Lennart Regebro,

1
+1 (per compensare il downvote: potrebbe essere un requisito valido per cercare soluzioni solo stdlib) con l'avvertenza di @Lennart menzionata. Dato che dateutil potrebbe fallire sia su Unix che su Windows. a proposito, potresti estrarre utctotimestamp(utc_dt) -> (seconds, microseconds)dal tuo codice per chiarezza, vedi l' implementazione di Python 2.6-3.x
jfs,

1
Un pytz (e dateutil) funziona su Python 3 da qualche tempo, quindi i problemi di Python3 / Windows / Quality non sono più un problema.
Lennart Regebro,

6

Basandosi sul commento di Alexei. Questo dovrebbe funzionare anche per l'ora legale.

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)

4

La libreria Python standard non include alcuna tzinfoimplementazione. Ho sempre considerato questo un difetto sorprendente del modulo datetime.

La documentazione per la classe tzinfo contiene alcuni esempi utili. Cerca il grande blocco di codice alla fine della sezione.


1
La mia ipotesi sull'implementazione di base è perché il database del fuso orario deve essere aggiornato regolarmente poiché alcuni paesi non hanno regole fisse per determinare i periodi di ora legale. Se non sbaglio, la JVM, ad esempio, deve essere aggiornata per ottenere l'ultimo database del fuso orario ...
Nitro Zark,

@Nitro Zark, almeno avrebbero dovuto averne uno per UTC. Il osmodulo potrebbe averne fornito uno per l'ora locale in base alle funzioni del sistema operativo.
Mark Ransom,

1
Stavo controllando l'elenco delle nuove funzionalità in 3.2 e hanno aggiunto il fuso orario UTC in 3.2 ( docs.python.org/dev/whatsnew/3.2.html#datetime ). Tuttavia, non sembra esserci un fuso orario locale ...
Nitro Zark,

2

Python 3.9 aggiunge il zoneinfomodulo così ora può essere fatto come segue (solo 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

L'Europa centrale è 1 o 2 ore avanti rispetto a UTC, quindi local_awareè:

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

come str:

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

Windows non ha un database di fuso orario di sistema, quindi qui è necessario un pacchetto aggiuntivo:

pip install tzdata  

Esiste un backport per consentire l'uso in Python da 3.6 a 3.8 :

sudo pip install backports.zoneinfo

Poi:

from backports.zoneinfo import ZoneInfo

non ti serve nemmeno zoneinfoqui - vedi la prima parte della risposta di jfs (la parte Python3.3 +) ;-)
MrFuppes

@MrFuppes Penso che ZoneInfo('localtime')sia molto più chiaro di tz=None(dopotutto, ci si potrebbe aspettare tz=Nonedi rimuovere il fuso orario o restituire UTC piuttosto che l'ora locale).
xjcl

@MrFuppes Plus la domanda potrebbe comportare che OP abbia un sito Web e desideri mostrare agli utenti i timestamp nella loro ora locale. Per questo dovresti convertirti nel fuso orario esplicito dell'utente piuttosto che nell'ora locale.
xjcl

0

Un modo semplice (ma forse imperfetto) che funziona in Python 2 e 3:

import time
import datetime

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

Il suo vantaggio è che è banale scrivere una funzione inversa


1
Questo dà il risultato sbagliato, in quanto time.timezonenon tiene conto dell'ora legale.
Guyarad,

0

Il modo più semplice che ho trovato è quello di ottenere la differenza di orario di dove ti trovi , quindi sottrarlo da un'ora.

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

Questo funziona per me, in Python 3.5.2.


0

Ecco un altro modo per cambiare il fuso orario nel formato datetime (so di aver sprecato la mia energia in questo, ma non ho visto questa pagina quindi non so come) senza min. e sec. perché non ne ho bisogno per il mio progetto:

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)

Utilizzare timedelta per alternare i fusi orari. Tutto ciò che serve è l'offset in ore tra i fusi orari. Non è necessario giocherellare con i limiti per tutti e 6 gli elementi di un oggetto datetime. timedelta gestisce facilmente anni bisestili, secoli bisestili, ecc. Devi "importare timetime data". Quindi se l'offset è una variabile in ore: timeout = timein + timedelta (hours = offset), dove timein e timeout sono oggetti datetime. ad es. timein + timedelta (ore = -8) converte da GMT a PST.
Karl Rudnick,

0

Questo è un modo terribile per farlo, ma evita di creare una definizione. Soddisfa il requisito di attenersi alla libreria Python3 di base.

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

Mentre questo è interessante e ho intenzione di usare questo metodo, non sta usando solo lo stdlib. Sta usando Panda per fare le conversioni pandas.pydata.org/pandas-docs/version/0.25/reference/api/…
Tim Hughes

-3

Utilizzare timedelta per alternare i fusi orari. Tutto ciò che serve è l'offset in ore tra i fusi orari. Non è necessario giocherellare con i limiti per tutti e 6 gli elementi di un oggetto datetime. timedelta gestisce facilmente anni bisestili, secoli bisestili, ecc. Devi prima

from datetime import datetime, timedelta

Quindi se offsetè il delta del fuso orario in ore:

timeout = timein + timedelta(hours = offset)

dove timein e timeout sono oggetti datetime. per esempio

timein + timedelta(hours = -8)

converte da GMT a PST.

Quindi, come determinare offset? Ecco una semplice funzione a patto che tu abbia solo poche possibilità di conversione senza usare oggetti datetime che sono "consapevoli" del fuso orario che alcune altre risposte fanno bene. Un po 'manuale, ma a volte la chiarezza è la cosa migliore.

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)

Dopo aver esaminato le numerose risposte e aver giocato con il codice più stretto che mi viene in mente (per ora), sembra che tutte le applicazioni, dove il tempo è importante e i fusi orari misti devono essere considerati, dovrebbero fare uno sforzo reale per realizzare tutti gli oggetti datetime "consapevole". Quindi sembrerebbe che la risposta più semplice sia:

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

per convertire in GMT, ad esempio. Naturalmente, per convertire in / da qualsiasi altro fuso orario desiderato, locale o di altro tipo, basta usare la stringa di fuso orario appropriata che pytz comprende (da pytz.all_timezones). Viene quindi presa in considerazione anche l'ora legale.


1
Questa non è una buona idea. L'ora legale non si sposta alla stessa data per tutto il mondo. Utilizzare le funzioni datetime per fare ciò.
Gabe,

@gabe - Comprendi che il tavolo non è una buona idea, sebbene possa adattarsi a qualcuno che copre solo i fusi orari statunitensi. Come ho detto alla fine, il modo migliore è SEMPRE rendere gli oggetti datetime "consapevoli" in modo da poter usare facilmente le funzioni datetime.
Karl Rudnick,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.