Lo que necesito hacer

Tengo un objeto de fecha y hora que no reconoce la zona horaria, al que necesito agregar una zona horaria para poder compararlo con otros objetos de fecha y hora que reconocen la zona horaria. No quiero convertir toda mi aplicación a la zona horaria sin saberlo para este caso heredado.

lo que he probado

Primero, para demostrar el problema:

Python 2.6.1 (r261:67515, Jun 24 2010, 21:47:49) 
[GCC 4.2.1 (Apple Inc. build 5646)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import datetime
>>> import pytz
>>> unaware = datetime.datetime(2011,8,15,8,15,12,0)
>>> unaware
datetime.datetime(2011, 8, 15, 8, 15, 12)
>>> aware = datetime.datetime(2011,8,15,8,15,12,0,pytz.UTC)
>>> aware
datetime.datetime(2011, 8, 15, 8, 15, 12, tzinfo=<UTC>)
>>> aware == unaware
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't compare offset-naive and offset-aware datetimes

Primero, probé una zona horaria:

>>> unaware.astimezone(pytz.UTC)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: astimezone() cannot be applied to a naive datetime
>>>

No es terriblemente sorprendente que haya fallado, ya que en realidad está tratando de hacer una conversión. Reemplazar parecía una mejor opción (según ¿Cómo obtengo un valor de datetime.today() en Python que es "consciente de la zona horaria"? ):

>>> unaware.replace(tzinfo=pytz.UTC)
datetime.datetime(2011, 8, 15, 8, 15, 12, tzinfo=<UTC>)
>>> unaware == aware
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't compare offset-naive and offset-aware datetimes
>>> 

Pero como puede ver, replace parece establecer el tzinfo, pero no hace que el objeto sea consciente. Me estoy preparando para volver a manipular la cadena de entrada para tener una zona horaria antes de analizarla (estoy usando dateutil para analizar, si eso importa), pero eso parece increíblemente torpe.

Además, probé esto tanto en Python 2.6 como en Python 2.7, con los mismos resultados.

Contexto

Estoy escribiendo un analizador para algunos archivos de datos. Hay un formato antiguo que debo admitir donde la cadena de fecha no tiene un indicador de zona horaria. Ya arreglé la fuente de datos, pero aún necesito admitir el formato de datos heredado. Una conversión de una sola vez de los datos heredados no es una opción por varias razones comerciales de BS. Si bien, en general, no me gusta la idea de codificar una zona horaria predeterminada, en este caso parece ser la mejor opción. Sé con razonable confianza que todos los datos heredados en cuestión están en UTC, por lo que estoy preparado para aceptar el riesgo de no hacerlo en este caso.

respuesta

En general, para hacer que una fecha y hora ingenua reconozca la zona horaria, utilice el método de localización :

import datetime
import pytz

unaware = datetime.datetime(2011, 8, 15, 8, 15, 12, 0)
aware = datetime.datetime(2011, 8, 15, 8, 15, 12, 0, pytz.UTC)

now_aware = pytz.utc.localize(unaware)
assert aware == now_aware

Para la zona horaria UTC, no es realmente necesario usarla localizeya que no hay ningún cálculo de horario de verano que manejar:

now_aware = unaware.replace(tzinfo=pytz.UTC)

obras. ( .replacedevuelve una nueva fecha y hora; no modifica unaware).

Todos estos ejemplos usan un módulo externo, pero puede lograr el mismo resultado usando solo el módulo de fecha y hora, como también se presenta en esta respuesta SO :

from datetime import datetime, timezone

dt = datetime.now()
dt = dt.replace(tzinfo=timezone.utc)

print(dt.isoformat())
# '2017-01-12T22:11:31+00:00'

Menos dependencias y sin problemas de pytz.

NOTA: Si desea usar esto con python3 y python2, también puede usarlo para la importación de la zona horaria (codificada para UTC):

try:
    from datetime import timezone
    utc = timezone.utc
except ImportError:
    #Hi there python2 user
    class UTC(tzinfo):
        def utcoffset(self, dt):
            return timedelta(0)
        def tzname(self, dt):
            return "UTC"
        def dst(self, dt):
            return timedelta(0)
    utc = UTC()

Escribí este script de Python 2 en 2011, pero nunca verifiqué si funciona en Python 3.

Me había mudado de dt_aware a dt_unaware:

dt_unaware = dt_aware.replace(tzinfo=None)

y dt_unware a dt_aware:

from pytz import timezone
localtz = timezone('Europe/Lisbon')
dt_aware = localtz.localize(dt_unware)

Uso esta declaración en Django para convertir un tiempo inconsciente en uno consciente:

from django.utils import timezone

dt_aware = timezone.make_aware(dt_unaware, timezone.get_current_timezone())

Python 3.9 agrega el zoneinfomódulo, ¡así que ahora solo se necesita la biblioteca estándar!

from zoneinfo import ZoneInfo
from datetime import datetime
unaware = datetime(2020, 10, 31, 12)

Adjunte una zona horaria:

>>> unaware.replace(tzinfo=ZoneInfo('Asia/Tokyo'))
datetime.datetime(2020, 10, 31, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='Asia/Tokyo'))
>>> str(_)
'2020-10-31 12:00:00+09:00'

Adjunte la zona horaria local del sistema:

>>> unaware.replace(tzinfo=ZoneInfo('localtime'))
datetime.datetime(2020, 10, 31, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='localtime'))
>>> str(_)
'2020-10-31 12:00:00+01:00'

Posteriormente se convierte correctamente a otras zonas horarias:

>>> unaware.replace(tzinfo=ZoneInfo('localtime')).astimezone(ZoneInfo('Asia/Tokyo'))
datetime.datetime(2020, 10, 31, 20, 0, tzinfo=backports.zoneinfo.ZoneInfo(key='Asia/Tokyo'))
>>> str(_)
'2020-10-31 20:00:00+09:00'

Lista de Wikipedia de zonas horarias disponibles


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 el uso zoneinfoen Python 3.6 a 3.8 :

pip install backports.zoneinfo

Entonces:

from backports.zoneinfo import ZoneInfo

Estoy de acuerdo con las respuestas anteriores, y está bien si está bien para comenzar en UTC. Pero creo que también es un escenario común para las personas trabajar con un valor consciente de tz que tiene una fecha y hora que tiene una zona horaria local no UTC.

Si solo tuviera que ir por nombre, uno probablemente inferiría que replace() será aplicable y producirá el objeto consciente de fecha y hora correcto. Este no es el caso.

el replace(tzinfo=...) parece ser aleatorio en su comportamiento . Por lo tanto, es inútil. ¡No uses esto!

localizar es la función correcta a utilizar. Ejemplo:

localdatetime_aware = tz.localize(datetime_nonaware)

O un ejemplo más completo:

import pytz
from datetime import datetime
pytz.timezone('Australia/Melbourne').localize(datetime.now())

me da un valor de fecha y hora consciente de la zona horaria de la hora local actual:

datetime.datetime(2017, 11, 3, 7, 44, 51, 908574, tzinfo=<DstTzInfo 'Australia/Melbourne' AEDT+11:00:00 DST>)

Úselo dateutil.tz.tzlocal()para obtener la zona horaria en su uso de datetime.datetime.now()y datetime.datetime.astimezone():

from datetime import datetime
from dateutil import tz

unlocalisedDatetime = datetime.now()

localisedDatetime1 = datetime.now(tz = tz.tzlocal())
localisedDatetime2 = datetime(2017, 6, 24, 12, 24, 36, tz.tzlocal())
localisedDatetime3 = unlocalisedDatetime.astimezone(tz = tz.tzlocal())
localisedDatetime4 = unlocalisedDatetime.replace(tzinfo = tz.tzlocal())

Tenga en cuenta que datetime.astimezoneprimero convertirá su datetimeobjeto a UTC y luego a la zona horaria, que es lo mismo que llamar datetime.replacecon la información de la zona horaria original como None.

Esto codifica las respuestas de @Sérgio y @unutbu . "Simplemente funcionará" con un pytz.timezoneobjeto o una cadena de zona horaria de IANA .

def make_tz_aware(dt, tz='UTC', is_dst=None):
    """Add timezone information to a datetime object, only if it is naive."""
    tz = dt.tzinfo or tz
    try:
        tz = pytz.timezone(tz)
    except AttributeError:
        pass
    return tz.localize(dt, is_dst=is_dst) 

Esto parece lo que datetime.localize()(o .inform()o .awarify()) debería hacer, aceptar cadenas y objetos de zona horaria para el argumento tz y establecer de forma predeterminada UTC si no se especifica ninguna zona horaria.

para aquellos que solo quieren hacer una fecha y hora consciente de la zona horaria

import datetime

datetime.datetime(2019, 12, 7, tzinfo=datetime.timezone.utc)

para aquellos que quieren una fecha y hora con una zona horaria no utc a partir de python 3.9 stdlib

import datetime
from zoneinfo import ZoneInfo

datetime.datetime(2019, 12, 7, tzinfo=ZoneInfo("America/Los_Angeles")) 

Otra forma más de tener un datetimeobjeto NO ingenuo:

>>> from datetime import datetime, timezone
>>> datetime.now(timezone.utc)
datetime.datetime(2021, 5, 1, 22, 51, 16, 219942, tzinfo=datetime.timezone.utc)

bastante nuevo en Python y me encontré con el mismo problema. Encuentro esta solución bastante simple y para mí funciona bien (Python 3.6):

unaware=parser.parse("2020-05-01 0:00:00")
aware=unaware.replace(tzinfo=tz.tzlocal()).astimezone(tz.tzlocal())

Aquí hay una solución simple para minimizar los cambios en su código:

from datetime import datetime
import pytz

start_utc = datetime.utcnow()
print ("Time (UTC): %s" % start_utc.strftime("%d-%m-%Y %H:%M:%S"))

Time (UTC): 09-01-2021 03:49:03

tz = pytz.timezone('Africa/Cairo')
start_tz = datetime.now().astimezone(tz)
print ("Time (RSA): %s" % start_tz.strftime("%d-%m-%Y %H:%M:%S"))

Time (RSA): 09-01-2021 05:49:03

En el formato de la respuesta de unutbu; Creé un módulo de utilidad que maneja cosas como esta, con una sintaxis más intuitiva. Se puede instalar con pip.

import datetime
import saturn

unaware = datetime.datetime(2011, 8, 15, 8, 15, 12, 0)
now_aware = saturn.fix_naive(unaware)

now_aware_madrid = saturn.fix_naive(unaware, 'Europe/Madrid')

Cambiar entre zonas horarias

import pytz
from datetime import datetime

other_tz = pytz.timezone('Europe/Madrid')

# From random aware datetime...
aware_datetime = datetime.utcnow().astimezone(other_tz)
>> 2020-05-21 08:28:26.984948+02:00

# 1. Change aware datetime to UTC and remove tzinfo to obtain an unaware datetime
unaware_datetime = aware_datetime.astimezone(pytz.UTC).replace(tzinfo=None)
>> 2020-05-21 06:28:26.984948

# 2. Set tzinfo to UTC directly on an unaware datetime to obtain an utc aware datetime
aware_datetime_utc = unaware_datetime.replace(tzinfo=pytz.UTC)
>> 2020-05-21 06:28:26.984948+00:00

# 3. Convert the aware utc datetime into another timezone
reconverted_aware_datetime = aware_datetime_utc.astimezone(other_tz)
>> 2020-05-21 08:28:26.984948+02:00

# Initial Aware Datetime and Reconverted Aware Datetime are equal
print(aware_datetime1 == aware_datetime2)
>> True

Por encima de todos los enfoques mencionados, cuando se trata de una marca de tiempo de Unix . Esta es una solución muy simple usando pandas.

import pandas as pd
unix_timestamp = 1513393355
pst_tz = pd.Timestamp(unix_timestamp, unit='s', tz='US/Pacific')
utc_tz = pd.Timestamp(unix_timestamp , unit='s', tz='UTC')