¿Convertir una fecha y hora UTC de Python en una fecha y hora local usando solo la biblioteca estándar de Python?

189

Tengo una instancia de fecha y hora de Python que se creó usando datetime.utcnow () y persistió en la base de datos.

Para la visualización, me gustaría convertir la instancia de fecha y hora recuperada de la base de datos a la fecha y hora local usando la zona horaria local predeterminada (es decir, como si la fecha y hora se hubiera creado usando datetime.now ()).

¿Cómo puedo convertir la fecha y hora UTC en una fecha y hora local usando solo la biblioteca estándar de Python (por ejemplo, sin dependencia de Pytz)?

Parece que una solución sería usar datetime.astimezone (tz), pero ¿cómo obtendría la zona horaria local predeterminada?

Nitro Zark
fuente
¿En qué formato persistió el tiempo de la base de datos? Si es un formato estándar, es posible que no necesite hacer ninguna conversión.
Apalala
1
Esta respuesta muestra una manera simple de usar pytz .
juan

Respuestas:

275

En Python 3.3+:

from datetime import datetime, timezone

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

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

Usando pytz(ambos 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

Ejemplo

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

Salida

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 en cuenta el horario de verano y el cambio reciente de compensación de utc para la zona horaria de MSK.

No sé si las soluciones que no son de Pytz funcionan en Windows.

jfs
fuente
1
¿Qué hace 'normalizar'?
avi
44
@avi: en general: pytz: ¿Por qué se necesita normalizar al convertir entre zonas horarias? . Nota: No es necesario con la implementación actual de .normalize()porque la zona horaria de origen .astimezone()es UTC.
jfs
1
Me sale TypeError: replace() takes no keyword argumentsal probar la solución pytz.
Craig
@Craig, el código funciona como está, como lo demuestra la salida en la respuesta. Cree un ejemplo de código mínimo completo que conduzca a TypeError, mencione las versiones de Python y Pytz que usa y publíquelo como una pregunta separada de desbordamiento de pila.
jfs
Consejo rápido para la solución Python 3.3+. Para ir en la dirección opuesta (nativo de UTC), esto funciona: local_dt.replace(tzinfo=None).astimezone(tz=timezone.utc)
jasonrhaas
50

No puede hacerlo solo con la biblioteca estándar, ya que la biblioteca estándar no tiene zonas horarias. Necesitas 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 bien, puede hacerlo sin pytz o dateutil implementando sus propias zonas horarias. Pero eso sería una tontería.

Lennart Regebro
fuente
Gracias, lo tendré a mano si alguna vez necesito una solución completa. ¿Dateutil admite Python 3.1 (dice Python 2.3+ pero es sospechoso)?
Nitro Zark
Pytz maneja mejor los cambios de horario de verano.
Lennart Regebro
2
Actualización: pytz y dateutil son compatibles con Python 3 hoy en día.
Lennart Regebro
1
@JFSebastian: dateutil no puede distinguir entre las dos 1:30 veces que suceden durante un cambio de horario de verano. Si necesita poder distinguir entre esas dos veces, la solución es usar pytz, que puede manejarlo.
Lennart Regebro
8

No puedes hacerlo con la biblioteca estándar. Usando el módulo pytz puedes convertir cualquier objeto datetime ingenuo / consciente a cualquier otra zona horaria. Veamos algunos ejemplos usando Python 3.

Objetos ingenuos creados a través del método de clase. utcnow()

Para convertir un objeto ingenuo a cualquier otra zona horaria, primero debe convertirlo en un objeto de fecha y hora consciente . Puede usar el replacemétodo para convertir un objeto datetime ingenuo en un objeto datetime consciente . Luego, para convertir un objeto datetime consciente a cualquier otra zona horaria, puede usar el astimezonemétodo.

La variable pytz.all_timezonesle proporciona la lista de todas las zonas horarias disponibles en el módulo 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)

Objetos ingenuos creados a través del método de clase. now()

Debido a que el nowmétodo devuelve la fecha y la hora actuales, primero debe tener en cuenta la zona horaria del objeto datetime. La localize función convierte un objeto datetime ingenuo en un objeto datetime con reconocimiento de zona horaria. Luego puede usar el astimezonemétodo para convertirlo en otra zona horaria.

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

Creo que lo descubrí: calcula el número de segundos desde la época, luego lo convierte en un timzeone local usando time.localtime, y luego convierte la estructura del tiempo en una fecha y hora ...

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 )

Aplica el horario de verano / invierno correctamente:

>>> 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
fuente
1
Ugh Pero eso debería funcionar en Unix, al menos. Para Windows puede ser incorrecto si sus reglas de horario de verano han cambiado desde la fecha de conversión. ¿Por qué no quieres usar una biblioteca?
Lennart Regebro
Solo necesito la conversión utc / local, por lo que arrastrar esa biblioteca parece ser exagerado. Como soy el único usuario de la aplicación, puedo vivir con algunas limitaciones. También evita administrar los problemas relacionados con la dependencia (¿admite Python 3 ?, calidad Windows® ...) que sería más costoso que trabajar en mi pequeño script.
Nitro Zark
1
Bien. si necesitas Python3, entonces no tienes suerte. Pero de lo contrario, investigar y hacer su propia solución es excesivo en comparación con el uso de una biblioteca.
Lennart Regebro
1
+1 (para compensar el voto negativo: puede ser un requisito válido buscar soluciones solo para stdlib) con la advertencia @Lennart mencionada. Dado que dateutil puede fallar tanto en Unix como en Windows. por cierto, puede extraer utctotimestamp(utc_dt) -> (seconds, microseconds)de su código para mayor claridad, vea la implementación de Python 2.6-3.x
jfs
1
Un pytz (y dateutil) funciona en Python 3 desde hace algún tiempo, por lo que los problemas de Python3 / Windows / Calidad ya no son un problema.
Lennart Regebro
6

Sobre la base del comentario de Alexei. Esto también debería funcionar para el horario de verano.

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

La biblioteca estándar de Python no viene con ninguna tzinfoimplementación en absoluto. Siempre he considerado esto como una deficiencia sorprendente del módulo de fecha y hora.

La documentación para la clase tzinfo viene con algunos ejemplos útiles. Busque el bloque de código grande al final de la sección.

Mark Ransom
fuente
1
Supongo que la implementación principal se debe a que la base de datos de zonas horarias debe actualizarse regularmente ya que algunos países no tienen reglas fijas para determinar los períodos de horario de verano. Si no me equivoco, la JVM, por ejemplo, debe actualizarse para obtener la última base de datos de zonas horarias ...
Nitro Zark
@Nitro Zark, al menos deberían haber tenido uno para UTC. El osmódulo podría haber proporcionado uno para la hora local en función de las funciones del sistema operativo.
Mark Ransom
1
Estaba revisando la lista de nuevas características en 3.2 y agregaron la zona horaria UTC en 3.2 ( docs.python.org/dev/whatsnew/3.2.html#datetime ). Sin embargo, no parece haber una zona horaria local ...
Nitro Zark
2

Python 3.9 agrega el zoneinfomódulo, por lo que ahora se puede hacer de la siguiente manera (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

Europa Central está 1 o 2 horas por delante de UTC, por lo que local_awarees:

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

como str:

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

Windows no tiene una base de datos de zona horaria del sistema, por lo que aquí se necesita un paquete adicional:

pip install tzdata  

Hay un backport para permitir su uso en Python 3.6 a 3.8 :

sudo pip install backports.zoneinfo

Luego:

from backports.zoneinfo import ZoneInfo
xjcl
fuente
ni siquiera necesita zoneinfoaquí: vea la primera parte de la respuesta de jfs (la parte Python3.3 +) ;-)
MrFuppes
@MrFuppes creo que ZoneInfo('localtime')es mucho más claro que tz=None(después de todo, uno podría esperar tz=Noneeliminar la zona horaria o devolver UTC en lugar de localtime).
xjcl
@MrFuppes Plus la pregunta podría implicar que OP tenga un sitio web y desee mostrar a los usuarios las marcas de tiempo en su hora local. Para eso, tendría que convertir a la zona horaria explícita del usuario en lugar de localtime.
xjcl
0

Una forma simple (pero tal vez defectuosa) que funciona en Python 2 y 3:

import time
import datetime

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

Su ventaja es que es trivial escribir una función inversa

Alexei Averchenko
fuente
1
Esto da el resultado incorrecto, ya time.timezoneque no tiene en cuenta el horario de verano.
guyarad el
0

La forma más fácil que he encontrado es obtener el desplazamiento de tiempo de donde estás , luego restar eso de la hora.

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

Esto funciona para mí, en Python 3.5.2.

Producciones Zld
fuente
0

Aquí hay otra forma de cambiar la zona horaria en formato de fecha y hora (sé que desperdicié mi energía en esto, pero no vi esta página, así que no sé cómo) sin min. y sec. porque no lo necesito para mi proyecto:

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)
Invitado
fuente
Use timedelta para cambiar entre zonas horarias. Todo lo que necesita es el desplazamiento en horas entre zonas horarias. No tiene que jugar con los límites de los 6 elementos de un objeto de fecha y hora. timedelta maneja los años bisiestos, los siglos bisiestos, etc., también, con facilidad. Debe 'from datetime import timedelta'. Entonces, si el desplazamiento es una variable en horas: tiempo de espera = timein + timedelta (horas = desplazamiento), donde timein y timeout son objetos de fecha y hora. Por ejemplo, timein + timedelta (horas = -8) convierte de GMT a PST.
Karl Rudnick
0

Esta es una forma terrible de hacerlo, pero evita crear una definición. Cumple el requisito de seguir con la biblioteca básica de 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
fuente
Si bien esto es interesante y voy a usar este método, no está usando solo el stdlib. Está utilizando Pandas para hacer las conversiones pandas.pydata.org/pandas-docs/version/0.25/reference/api/…
Tim Hughes
-3

Use timedelta para cambiar entre zonas horarias. Todo lo que necesita es el desplazamiento en horas entre zonas horarias. No tiene que jugar con los límites de los 6 elementos de un objeto de fecha y hora. timedelta maneja los años bisiestos, los siglos bisiestos, etc., también, con facilidad. Primero debes

from datetime import datetime, timedelta

Entonces, si offsetes la zona horaria delta en horas:

timeout = timein + timedelta(hours = offset)

donde timein y timeout son objetos datetime. p.ej

timein + timedelta(hours = -8)

convierte de GMT a PST.

Entonces, ¿cómo determinarlo offset? Aquí hay una función simple, siempre que solo tenga algunas posibilidades de conversión sin usar objetos de fecha y hora que sean "conscientes" de la zona horaria, lo que algunas otras respuestas hacen muy bien. Un poco manual, pero a veces la claridad es mejor.

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)

Después de mirar las numerosas respuestas y jugar con el código más ajustado que se me ocurre (por ahora) parece mejor que todas las aplicaciones, donde el tiempo es importante y se deben tener en cuenta las zonas horarias mixtas, deben hacer un esfuerzo real para hacer todos los objetos de fecha y hora "consciente". Entonces parece que la respuesta más simple es:

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

para convertir a GMT por ejemplo. Por supuesto, para convertir a / desde cualquier otra zona horaria que desee, local o de otro tipo, simplemente use la cadena de zona horaria apropiada que Pytz entiende (de pytz.all_timezones). El horario de verano también se tiene en cuenta.

Karl Rudnick
fuente
1
Esta es / no / una buena idea. El horario de verano no cambia en la misma fecha para todo el mundo. Use las funciones de fecha y hora para hacer esto.
Gabe
@gabe: comprenda que la tabla no es una buena idea, aunque puede adaptarse a alguien que solo cubre zonas horarias de EE. UU. Como dije al final, la mejor manera es SIEMPRE hacer que los objetos de fecha y hora sean "conscientes" para que pueda usar fácilmente las funciones de fecha y hora.
Karl Rudnick