Convertir cadena de fecha y hora UTC a fecha y hora local

207

Nunca he tenido que convertir el tiempo hacia y desde UTC. Recientemente tuve una solicitud para que mi aplicación tuviera en cuenta la zona horaria, y me he estado ejecutando en círculos. Mucha información sobre la conversión de la hora local a UTC, que encontré bastante elemental (tal vez estoy haciendo eso mal también), pero no puedo encontrar ninguna información sobre cómo convertir fácilmente la hora UTC a la zona horaria de los usuarios finales.

En pocas palabras, y la aplicación de Android me envía datos (aplicación appengine) y dentro de esos datos hay una marca de tiempo. Para almacenar esa marca de tiempo a la hora utc que estoy usando:

datetime.utcfromtimestamp(timestamp)

Eso parece estar funcionando. Cuando mi aplicación almacena los datos, se almacena con 5 horas de anticipación (estoy EST -5)

Los datos se almacenan en BigTable de appengine y, cuando se recuperan, salen como una cadena de este modo:

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

¿Cómo convierto esta cadena a DateTime en la zona horaria correcta de los usuarios?

Además, ¿cuál es el almacenamiento recomendado para la información de zona horaria de un usuario? (¿Cómo almacena típicamente información tz, es decir: "-5: 00" o "EST", etc., etc.?) Estoy seguro de que la respuesta a mi primera pregunta puede contener un parámetro que responda a la segunda.

MattoTodd
fuente

Respuestas:

403

Si no desea proporcionar sus propios tzinfoobjetos, consulte la biblioteca python-dateutil . Proporciona tzinfoimplementaciones en la parte superior de una base de datos zoneinfo (Olson) para que pueda consultar las reglas de zona horaria con un nombre algo canónico.

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)

Editar ejemplo ampliado para mostrar el strptimeuso

Editar 2 Uso de API fijo para mostrar un mejor método de punto de entrada

Editar 3 Métodos de detección automática incluidos para zonas horarias (Yarin)

Joe Holloway
fuente
1
En mi comentario anterior, pude importar dateutil.tzy usar tz.tzutc()y tz.tzlocal()como objetos de zona horaria que estaba buscando. Parece que la base de datos de zona horaria en mi sistema es buena (me registré /usr/share/zoneinfo). No estoy seguro de lo que estaba pasando.
Ben Kreeger
1
@Benjamin Estás en el camino correcto. El tzmódulo es el punto de entrada correcto que se utilizará para esta biblioteca. He actualizado mi respuesta para reflejar esto. El dateutil.zoneinfomódulo que estaba mostrando anteriormente es utilizado internamente por el tzmódulo como una reserva si no puede ubicar la base de datos zonezone del sistema. Si mira dentro de la biblioteca, verá que hay un tarball DB de zoneinfo en el paquete que usa si no puede encontrar la base de datos de su sistema. Mi antiguo ejemplo estaba tratando de golpear esa base de datos directamente y supongo que tenía problemas para cargar esa base de datos privada (¿no está en la ruta de Python de alguna manera?)
Joe Holloway
1
@JFSebastian, esto se soluciona en # 225
Rohmer
2
@briankip Depende de cuán sofisticada deba ser su aplicación, pero en aplicaciones generales, sí, es un problema. En primer lugar, según la época del año, la cantidad de horas para sumar / restar puede cambiar, ya que algunas zonas horarias cumplen con el horario de verano y otras no. En segundo lugar, las reglas de zona horaria se cambian arbitrariamente a lo largo del tiempo, por lo que si está trabajando con una fecha / hora pasada, debe aplicar las reglas que eran válidas en ese momento, no la hora actual. Es por eso que es mejor usar una API que aproveche la base de datos Olson TZ detrás de escena.
Joe Holloway
45

Aquí hay un método resistente que no depende de ninguna biblioteca externa:

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

Esto evita los problemas de tiempo en el ejemplo de DelboyJay. Y los problemas menores de tiempo en la enmienda de Erik van Oosten.

Como nota al pie interesante, el desfase de la zona horaria calculado anteriormente puede diferir de la siguiente expresión aparentemente equivalente, probablemente debido a cambios en la regla del horario de verano:

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

Actualización: este fragmento tiene la debilidad de utilizar el desplazamiento UTC de la hora actual, que puede diferir del desplazamiento UTC de la fecha y hora de entrada. Vea los comentarios sobre esta respuesta para otra solución.

Para conocer los diferentes tiempos, tome el tiempo de época del tiempo transcurrido. Esto es lo que hago:

def utc2local (utc):
    epoch = time.mktime(utc.timetuple())
    offset = datetime.fromtimestamp (epoch) - datetime.utcfromtimestamp (epoch)
    return utc + offset
David Foster
fuente
Esto funciona bien y no requiere nada más que hora y fecha y hora.
Mike_K
66
@Mike_K: pero es incorrecto. No es compatible con DST o zonas horarias que tuvieron diferentes desplazamientos utc en el pasado por otras razones. El soporte completo sin pytzdb similar es imposible, ver PEP-431. Aunque puede escribir una solución solo stdlib que funcione en tales casos en sistemas que ya tienen una zona horaria histórica db, por ejemplo, Linux, OS X, vea mi respuesta .
jfs
@JFSebastian: Usted dice que este código no funciona en general, pero también dice que funciona en OS X y Linux. ¿Quieres decir que este código no funciona en Windows?
David Foster
2
@DavidFoster: su código también puede fallar en Linux y OS X. La diferencia entre las soluciones suyas y las mías stdlib-only es que la suya utiliza el desplazamiento actual que podría ser diferente del desplazamiento utc en el utc_datetimemomento.
jfs
1
Buena llamada. Esa es una diferencia muy sutil. El desplazamiento UTC también puede cambiar con el tiempo. suspiro
David Foster
23

Consulte la documentación de fecha y hora en tzinfo objetos . Tiene que implementar las zonas horarias que desea soportar. Estos son ejemplos al final de la documentación.

Aquí hay un ejemplo simple:

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)

Salida

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
Mark Tolonen
fuente
¡Gracias por aceptar! Lo actualicé un poco para traducir su tiempo de ejemplo específico. Analizar el tiempo crea lo que los documentos llaman un tiempo "ingenuo". Use replacepara configurar la zona horaria a GMT y hacer que la zona horaria sea "consciente", luego use astimezonepara convertir a otra zona horaria.
Mark Tolonen
Perdón por cambiar la respuesta de Joe. Técnicamente, su respuesta explica exactamente cómo hacerlo con Python sin procesar (que es bueno saber), pero la biblioteca que sugirió me lleva a una solución mucho más rápido.
MattoTodd
44
No parece que maneje automáticamente el horario de verano.
Gringo Suave
@GringoSuave: no estaba destinado a hacerlo. Las reglas para eso son complicadas y cambian a menudo.
Mark Tolonen
19

Si desea obtener el resultado correcto incluso para la hora que corresponde a una hora local ambigua (p. Ej., Durante una transición de horario de verano) y / o el desplazamiento utc local es diferente en diferentes momentos de su zona pytzhoraria local, entonces use zonas horarias:

#!/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)
jfs
fuente
10

Esta respuesta debería ser útil si no desea utilizar otros módulos además datetime.

datetime.utcfromtimestamp(timestamp)devuelve un datetimeobjeto ingenuo (no consciente). Los conscientes son conscientes de la zona horaria y los ingenuos no. Desea uno consciente si desea convertir entre zonas horarias (por ejemplo, entre UTC y hora local).

Si no es usted quien crea la fecha para comenzar, pero aún puede crear un datetimeobjeto ingenuo en tiempo UTC, puede probar este código Python 3.x para 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

Tenga cuidado de no asumir erróneamente que si su zona horaria es actualmente MDT, ese horario de verano no funciona con el código anterior, ya que imprime MST. Notará que si cambia el mes a agosto, imprimirá MDT.

Otra forma fácil de obtener un datetimeobjeto consciente (también en Python 3.x) es crearlo con una zona horaria especificada para comenzar. Aquí hay un ejemplo, 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))

Si usa Python 2.x, probablemente tendrá que subclasificar datetime.tzinfoy usar eso para ayudarlo a crear un datetimeobjeto consciente , ya datetime.timezoneque no existe en Python 2.x.

Brōtsyorfuzthrāx
fuente
8

Si usa Django, puede usar el timezone.localtimemétodo:

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>)
frmdstryr
fuente
2

Puedes usar flecha

from datetime import datetime
import arrow

now = datetime.utcnow()

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

Puedes alimentarte arrow.get()con cualquier cosa. marca de tiempo, cadena iso, etc.

d21d3q
fuente
1

Tradicionalmente difiero esto a la interfaz de usuario: envíe las horas desde el backend como marcas de tiempo o algún otro formato de fecha y hora en UTC, luego dejo que el cliente descubra el desplazamiento de la zona horaria y procese estos datos en la zona horaria adecuada.

Para una aplicación web, esto es bastante fácil de hacer en JavaScript: puede calcular el desplazamiento de la zona horaria del navegador con bastante facilidad utilizando métodos incorporados y luego representar los datos del back-end correctamente.

Matt Billenstein
fuente
3
Esto funciona bien en muchos casos en los que simplemente está localizando para mostrar. Ocasionalmente, sin embargo, deberá realizar alguna lógica de negocios basada en la zona horaria del usuario y deberá poder realizar la conversión del nivel del servidor de aplicaciones.
Joe Holloway el
1

Puede usar calendar.timegmpara convertir su tiempo a segundos desde la época de Unix y time.localtimevolver a convertir:

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)

Da Fri Jan 21 05:37:21 2011(porque estoy en la zona horaria UTC + 03: 00).

myaut
fuente
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

por ejemplo, mi zona horaria es ' +08: 00 '. input utc = 2018-10-17T00: 00: 00.111Z , luego obtendré output = 2018-10-17T08: 00: 00 + 08: 00

Eureka Bing
fuente
1
Debe dar una mejor descripción de su problema en texto plano, donde se explica el resto de los usuarios ¿Qué está tratando de lograr y que es su problema
m33n
1

De la respuesta aquí , puede usar el módulo de tiempo para convertir de utc a la hora local establecida en su computadora:

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)
franksands
fuente
0

Aquí hay una versión rápida y sucia que utiliza la configuración del sistema local para resolver la diferencia horaria. NOTA: Esto no funcionará si necesita convertir a una zona horaria en la que su sistema actual no se está ejecutando. He probado esto con la configuración del Reino Unido en la zona horaria 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
DelboyJay
fuente
debería ser datetime.fromtimestamp(ts): marca de tiempo posix (segundos flotantes) -> objeto de fecha y hora en hora local (funciona bien si el sistema operativo recuerda las compensaciones de utc pasadas para la zona horaria local, es decir, en Unix pero no en Windows para fechas anteriores)). De lo contrario, se podría usar pytz.
jfs
¿Puedo sugerir hacer lo siguiente? offset = datetime.utcnow().replace(minute=0, second=0, microsecond=0) - datetime.now().replace(minute=0, second=0, microsecond=0) Obtuve diferencias extrañas de microsegundos sin él.
Erik van Oosten
@ErikvanOosten: ¿has probado en datetime.fromtimestamp(ts)lugar de la respuesta?
jfs
0

Consolidando la respuesta de franksands en un método 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()))
Martlark
fuente