Converti la stringa datetime UTC in datetime locale


206

Non ho mai dovuto convertire il tempo in e da UTC. Di recente ho ricevuto una richiesta per rendere la mia app in grado di riconoscere il fuso orario e mi sono messo in giro in cerchio. Molte informazioni sulla conversione dell'ora locale in UTC, che ho trovato abbastanza elementare (forse sto sbagliando anch'io), ma non riesco a trovare alcuna informazione su come convertire facilmente l'ora UTC nel fuso orario degli utenti finali.

In poche parole, e l'app Android mi invia i dati (app appine) e all'interno di tali dati c'è un timestamp. Per memorizzare il timestamp nel tempo utc che sto usando:

datetime.utcfromtimestamp(timestamp)

Sembra funzionare. Quando la mia app memorizza i dati, viene archiviata con 5 ore di anticipo (sono EST -5)

I dati vengono archiviati sulla BigTable di appengine e quando vengono recuperati vengono visualizzati come una stringa in questo modo:

"2011-01-21 02:37:21"

Come posso convertire questa stringa in un DateTime nel fuso orario corretto degli utenti?

Inoltre, qual è la memoria consigliata per le informazioni sul fuso orario di un utente? (Come in genere memorizzi informazioni tz, ad esempio: "-5: 00" o "EST", ecc.?) Sono sicuro che la risposta alla mia prima domanda potrebbe contenere un parametro e le risposte alla seconda.



Questa risposta mostra come risolverlo in modo semplice.
juan,

Risposte:


402

Se non vuoi fornire i tuoi tzinfooggetti, controlla la libreria python-dateutil . Fornisce tzinfoimplementazioni su un database zoneinfo (Olson) in modo tale da poter fare riferimento alle regole del fuso orario con un nome un po 'canonico.

from datetime import datetime
from dateutil import tz

# METHOD 1: Hardcode zones:
from_zone = tz.gettz('UTC')
to_zone = tz.gettz('America/New_York')

# METHOD 2: Auto-detect zones:
from_zone = tz.tzutc()
to_zone = tz.tzlocal()

# utc = datetime.utcnow()
utc = datetime.strptime('2011-01-21 02:37:21', '%Y-%m-%d %H:%M:%S')

# Tell the datetime object that it's in UTC time zone since 
# datetime objects are 'naive' by default
utc = utc.replace(tzinfo=from_zone)

# Convert time zone
central = utc.astimezone(to_zone)

Modifica esempio espanso per mostrare l' strptimeutilizzo

Modifica 2 Risolto il problema con l'utilizzo dell'API per mostrare il metodo del punto di ingresso migliore

Modifica 3 Metodi di rilevamento automatico inclusi per i fusi orari (Yarin)


1
Nel mio commento precedente, sono stato in grado di importare dateutil.tze utilizzare tz.tzutc()e tz.tzlocal()come oggetti fuso orario che stavo cercando. Sembra che il database dei fusi orari sul mio sistema sia buono (ho fatto il check-in /usr/share/zoneinfo). Non sono sicuro di cosa fosse successo.
Ben Kreeger,

1
@Benjamin Sei sulla strada giusta. Il tzmodulo è il punto di ingresso corretto da utilizzare per questa libreria. Ho aggiornato la mia risposta per riflettere questo. Il dateutil.zoneinfomodulo che stavo mostrando in precedenza viene utilizzato internamente dal tzmodulo come fallback se non riesce a individuare il DB zoneinfo del sistema. Se guardi all'interno della libreria vedrai che c'è un tarball DB zoneinfo nel pacchetto che usa se non riesce a trovare il DB del tuo sistema. Il mio vecchio esempio stava cercando di colpire direttamente quel DB e suppongo che avessi problemi a caricare quel DB privato (non è in qualche modo sul percorso Python?)
Joe Holloway,


1
@JFSebastian questo è stato risolto in # 225
Rohmer

2
@briankip Dipende da quanto deve essere sofisticata la tua applicazione, ma in applicazioni generali, sì, è un problema. Prima di tutto, a seconda del periodo dell'anno, il numero di ore da aggiungere / sottrarre potrebbe cambiare poiché alcuni fusi orari onorano l'ora legale e altri no. In secondo luogo, le regole del fuso orario vengono modificate arbitrariamente nel tempo, quindi se si lavora con una data / ora passata è necessario applicare le regole valide in quel momento, non l'ora attuale. Ecco perché è meglio usare un'API che sfrutta il database Olson TZ dietro le quinte.
Joe Holloway,

45

Ecco un metodo resiliente che non dipende da nessuna libreria esterna:

from datetime import datetime
import time

def datetime_from_utc_to_local(utc_datetime):
    now_timestamp = time.time()
    offset = datetime.fromtimestamp(now_timestamp) - datetime.utcfromtimestamp(now_timestamp)
    return utc_datetime + offset

Questo evita i problemi di temporizzazione nell'esempio di DelboyJay. E le minori questioni temporali nell'emendamento di Erik van Oosten.

Come nota a piè di pagina interessante, l'offset del fuso orario calcolato sopra può differire dalla seguente espressione apparentemente equivalente, probabilmente a causa delle modifiche alle regole dell'ora legale:

offset = datetime.fromtimestamp(0) - datetime.utcfromtimestamp(0) # NO!

Aggiornamento: questo frammento presenta il punto debole dell'utilizzo dell'offset UTC del momento attuale, che può differire dall'offset UTC del datetime di input. Vedi i commenti su questa risposta per un'altra soluzione.

Per aggirare i diversi tempi, prendi il tempo in cui è passato. Ecco cosa faccio:

def utc2local (utc):
    epoch = time.mktime(utc.timetuple())
    offset = datetime.fromtimestamp (epoch) - datetime.utcfromtimestamp (epoch)
    return utc + offset

Funziona bene e non richiede altro che tempo e data.
Mike_K,

6
@Mike_K: ma non è corretto. Non supporta DST o fusi orari con offset utc diverso in passato per altri motivi. Il supporto completo senza pytzdb-like è impossibile, vedere PEP-431. Sebbene sia possibile scrivere una soluzione solo stdlib che funziona in questi casi su sistemi che hanno già un fuso orario storico, ad esempio Linux, OS X, vedere la mia risposta .
jfs,

@JFSebastian: dici che questo codice non funziona in generale, ma dici anche che funziona su OS X e Linux. Vuoi dire che questo codice non funziona su Windows?
David Foster,

2
@DavidFoster: il tuo codice potrebbe non riuscire anche su Linux e OS X. La differenza tra la tua e le mie soluzioni solo stdlib è che la tua utilizza l' offset corrente che potrebbe essere diverso dall'offset utc al utc_datetimemomento.
jfs,

1
Ottima scelta. Questa è una differenza molto sottile. Anche l'offset UTC può cambiare nel tempo. sospiro
David Foster il

23

Vedi la documentazione datetime sugli oggetti tzinfo . Devi implementare i fusi orari che vuoi supportare. Sono esempi in fondo alla documentazione.

Ecco un semplice esempio:

from datetime import datetime,tzinfo,timedelta

class Zone(tzinfo):
    def __init__(self,offset,isdst,name):
        self.offset = offset
        self.isdst = isdst
        self.name = name
    def utcoffset(self, dt):
        return timedelta(hours=self.offset) + self.dst(dt)
    def dst(self, dt):
            return timedelta(hours=1) if self.isdst else timedelta(0)
    def tzname(self,dt):
         return self.name

GMT = Zone(0,False,'GMT')
EST = Zone(-5,False,'EST')

print datetime.utcnow().strftime('%m/%d/%Y %H:%M:%S %Z')
print datetime.now(GMT).strftime('%m/%d/%Y %H:%M:%S %Z')
print datetime.now(EST).strftime('%m/%d/%Y %H:%M:%S %Z')

t = datetime.strptime('2011-01-21 02:37:21','%Y-%m-%d %H:%M:%S')
t = t.replace(tzinfo=GMT)
print t
print t.astimezone(EST)

Produzione

01/22/2011 21:52:09 
01/22/2011 21:52:09 GMT
01/22/2011 16:52:09 EST
2011-01-21 02:37:21+00:00
2011-01-20 21:37:21-05:00a

Grazie per aver accettato! L'ho aggiornato leggermente per tradurre il tuo tempo di esempio specifico. L'analisi del tempo crea ciò che i documenti chiamano un tempo "ingenuo". Utilizzare replaceper impostare il fuso orario su GMT e renderlo "consapevole", quindi utilizzare astimezoneper convertire in un altro fuso orario.
Mark Tolonen,

Ci scusiamo per lo scambio con la risposta di Joe. Tecnicamente la tua risposta spiega esattamente come farlo con raw Python (che è buono a sapersi), ma la libreria che ha suggerito mi porta a una soluzione molto più veloce.
MattoTodd,

4
Non sembra che gestisca automaticamente l'ora legale.
Gringo Suave,

@GringoSuave: non era previsto. Le regole per questo sono complicate e cambiano spesso.
Mark Tolonen,

19

Se si desidera ottenere il risultato corretto anche per l'ora che corrisponde a un'ora locale ambigua (ad esempio, durante una transizione di ora legale) e / o l'offset utc locale è diverso in orari diversi nel proprio fuso orario locale, utilizzare i pytzfusi orari:

#!/usr/bin/env python
from datetime import datetime
import pytz    # $ pip install pytz
import tzlocal # $ pip install tzlocal

local_timezone = tzlocal.get_localzone() # get pytz tzinfo
utc_time = datetime.strptime("2011-01-21 02:37:21", "%Y-%m-%d %H:%M:%S")
local_time = utc_time.replace(tzinfo=pytz.utc).astimezone(local_timezone)

10

Questa risposta dovrebbe essere utile se non si desidera utilizzare altri moduli oltre datetime.

datetime.utcfromtimestamp(timestamp)restituisce un datetimeoggetto ingenuo (non consapevole). Quelli consapevoli sono consapevoli del fuso orario e ingenui no. Ne vuoi uno consapevole se vuoi convertire tra fusi orari (ad es. Tra UTC e ora locale).

Se non sei tu a creare un'istanza della data per iniziare, ma puoi comunque creare un datetimeoggetto ingenuo in tempo UTC, potresti provare questo codice Python 3.x per convertirlo:

import datetime

d=datetime.datetime.strptime("2011-01-21 02:37:21", "%Y-%m-%d %H:%M:%S") #Get your naive datetime object
d=d.replace(tzinfo=datetime.timezone.utc) #Convert it to an aware datetime object in UTC time.
d=d.astimezone() #Convert it to your local timezone (still aware)
print(d.strftime("%d %b %Y (%I:%M:%S:%f %p) %Z")) #Print it with a directive of choice

Fare attenzione a non assumere erroneamente che se il fuso orario è attualmente MDT, l'ora legale non funziona con il codice sopra poiché stampa MST. Noterai che se cambi il mese in agosto, stamperà MDT.

Un altro modo semplice per ottenere un datetimeoggetto consapevole (anche in Python 3.x) è crearlo con un fuso orario specificato per iniziare. Ecco un esempio, usando UTC:

import datetime, sys

aware_utc_dt_obj=datetime.datetime.now(datetime.timezone.utc) #create an aware datetime object
dt_obj_local=aware_utc_dt_obj.astimezone() #convert it to local time

#The following section is just code for a directive I made that I liked.
if sys.platform=="win32":
    directive="%#d %b %Y (%#I:%M:%S:%f %p) %Z"
else:
    directive="%-d %b %Y (%-I:%M:%S:%f %p) %Z"

print(dt_obj_local.strftime(directive))

Se usi Python 2.x, probabilmente dovrai sottoclassare datetime.tzinfoe usarlo per aiutarti a creare un datetimeoggetto consapevole , dato datetime.timezoneche non esiste in Python 2.x.


8

Se si utilizza Django, è possibile utilizzare il timezone.localtimemetodo:

from django.utils import timezone
date 
# datetime.datetime(2014, 8, 1, 20, 15, 0, 513000, tzinfo=<UTC>)

timezone.localtime(date)
# datetime.datetime(2014, 8, 1, 16, 15, 0, 513000, tzinfo=<DstTzInfo 'America/New_York' EDT-1 day, 20:00:00 DST>)

2

Puoi usare la freccia

from datetime import datetime
import arrow

now = datetime.utcnow()

print(arrow.get(now).to('local').format())
# '2018-04-04 15:59:24+02:00'

puoi nutrirti arrow.get()con qualsiasi cosa. timestamp, stringa iso ecc


1

Tradizionalmente rimando questo al frontend - invio dei tempi dal backend come timestamp o qualche altro formato datetime in UTC, quindi consento al client di capire l'offset del fuso orario e renderizzare questi dati nel fuso orario corretto.

Per una webapp, questo è abbastanza facile da fare in javascript: puoi capire abbastanza facilmente l'offset del fuso orario del browser usando i metodi integrati e quindi rendere correttamente i dati dal back-end.


3
Funziona bene in molti casi in cui ti stai semplicemente localizzando per la visualizzazione. Occasionalmente, tuttavia, è necessario eseguire alcune logiche aziendali in base al fuso orario dell'utente e si dovrà essere in grado di eseguire la conversione al livello del server delle applicazioni.
Joe Holloway,

1

Puoi usare calendar.timegmper convertire il tuo tempo in secondi dall'epoca di Unix e time.localtimeper riconvertire:

import calendar
import time

time_tuple = time.strptime("2011-01-21 02:37:21", "%Y-%m-%d %H:%M:%S")
t = calendar.timegm(time_tuple)

print time.ctime(t)

Fri Jan 21 05:37:21 2011(perché sono nel fuso orario UTC + 03: 00).


1
import datetime

def utc_str_to_local_str(utc_str: str, utc_format: str, local_format: str):
    """
    :param utc_str: UTC time string
    :param utc_format: format of UTC time string
    :param local_format: format of local time string
    :return: local time string
    """
    temp1 = datetime.datetime.strptime(utc_str, utc_format)
    temp2 = temp1.replace(tzinfo=datetime.timezone.utc)
    local_time = temp2.astimezone()
    return local_time.strftime(local_format)

utc = '2018-10-17T00:00:00.111Z'
utc_fmt = '%Y-%m-%dT%H:%M:%S.%fZ'
local_fmt = '%Y-%m-%dT%H:%M:%S+08:00'
local_string = utc_str_to_local_str(utc, utc_fmt, local_fmt)
print(local_string)   # 2018-10-17T08:00:00+08:00

ad esempio, il mio fuso orario è " +08: 00 ". input utc = 2018-10-17T00: 00: 00.111Z , quindi otterrò output = 2018-10-17T08: 00: 00 + 08: 00


1
Dovresti fornire una descrizione migliore del tuo problema in testo semplice, in cui spieghi agli altri utenti cosa stai cercando di ottenere e qual è il tuo problema
m33n

1

Dalla risposta qui , puoi usare il modulo orario per convertire da utc all'ora locale impostata sul tuo computer:

utc_time = time.strptime("2018-12-13T10:32:00.000", "%Y-%m-%dT%H:%M:%S.%f")
utc_seconds = calendar.timegm(utc_time)
local_time = time.localtime(utc_seconds)

0

Ecco una versione rapida e sporca che utilizza le impostazioni dei sistemi locali per calcolare la differenza di orario. NOTA: Questo non funzionerà se è necessario convertire in un fuso orario in cui il sistema attuale non è in esecuzione. Ho verificato questo con le impostazioni del Regno Unito in fuso orario BST

from datetime import datetime
def ConvertP4DateTimeToLocal(timestampValue):
   assert isinstance(timestampValue, int)

   # get the UTC time from the timestamp integer value.
   d = datetime.utcfromtimestamp( timestampValue )

   # calculate time difference from utcnow and the local system time reported by OS
   offset = datetime.now() - datetime.utcnow()

   # Add offset to UTC time and return it
   return d + offset

dovrebbe essere datetime.fromtimestamp(ts): posix timestamp (float seconds) -> oggetto datetime nell'ora locale (funziona bene se il sistema operativo ricorda gli offset dell'utc passati per il fuso orario locale, cioè su Unix ma non su Windows per le date passate)). Altrimenti potrebbe essere usato pytz.
jfs,

Posso suggerire di fare quanto segue: offset = datetime.utcnow().replace(minute=0, second=0, microsecond=0) - datetime.now().replace(minute=0, second=0, microsecond=0) senza di esso ho avuto strane differenze di microsecondi.
Erik van Oosten,

@ErikvanOosten: hai provato datetime.fromtimestamp(ts)invece della risposta?
jfs

0

Consolidare la risposta di Franksands in un metodo conveniente.

import calendar
import datetime

def to_local_datetime(utc_dt):
    """
    convert from utc datetime to a locally aware datetime according to the host timezone

    :param utc_dt: utc datetime
    :return: local timezone datetime
    """
    return datetime.datetime.fromtimestamp(calendar.timegm(utc_dt.timetuple()))
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.