No se pueden restar las horas de fecha ingenua y consciente de la compensación

305

Tengo un timestamptzcampo consciente de la zona horaria en PostgreSQL. Cuando extraigo datos de la tabla, quiero restar el tiempo ahora mismo para poder obtener su edad.

El problema que estoy teniendo es que tanto datetime.datetime.now()y datetime.datetime.utcnow()parecen volver de zona horaria marcas de tiempo sin darse cuenta, lo que da lugar a mí conseguir este error:

TypeError: can't subtract offset-naive and offset-aware datetimes 

¿Hay alguna manera de evitar esto (preferiblemente sin un módulo de terceros que se utiliza)

EDITAR: Gracias por las sugerencias, sin embargo, tratar de ajustar la zona horaria parece darme errores ... así que solo voy a usar marcas de tiempo inconscientes de la zona horaria en PG y siempre inserto usando:

NOW() AT TIME ZONE 'UTC'

De esa manera, todas mis marcas de tiempo son UTC por defecto (aunque es más molesto hacer esto).

Ian
fuente

Respuestas:

316

¿Has intentado eliminar la conciencia de zona horaria?

de http://pytz.sourceforge.net/

naive = dt.replace(tzinfo=None)

Es posible que también deba agregar la conversión de zona horaria.

editar: Tenga en cuenta la antigüedad de esta respuesta. Una respuesta de Python 3 está abajo.

phillc
fuente
32
Esta parece ser la única forma de hacerlo. Parece bastante lamentable que Python tenga un soporte tan malo para las zonas horarias que necesita un módulo de terceros para funcionar correctamente con las marcas de tiempo ...
Ian
33
(Solo para el registro) Agregar información sobre la zona horaria puede ser una mejor idea: stackoverflow.com/a/4530166/548696
Tadeck
77
Los objetos de fecha y hora ingenuos son intrínsecamente ambiguos y, por lo tanto, deben evitarse. Es fácil agregar tzinfo en su lugar
jfs
1
@Kylotan: UTC es una zona horaria en este contexto (como lo representa la clase tzinfo). Mira datetime.timezone.utco pytz.utc. Por ejemplo, 1970-01-01 00:00:00es ambiguo y hay que agregar una zona horaria para eliminar la ambigüedad: 1970-01-01 00:00:00 UTC. Ya ves, tienes que agregar nueva información; la marca de tiempo en sí misma es ambigua.
jfs
1
@JFSebastian: El problema es que utcnowno debería devolver un objeto ingenuo o una marca de tiempo sin una zona horaria. De los documentos, "Un objeto consciente se utiliza para representar un momento específico en el tiempo que no está abierto a interpretación". Cualquier momento en UTC cumple con este criterio, por definición.
Kylotan
214

La solución correcta es agregar la información de la zona horaria, por ejemplo, para obtener la hora actual como un objeto de fecha y hora consciente en Python 3:

from datetime import datetime, timezone

now = datetime.now(timezone.utc)

En versiones anteriores de Python, puede definir el utcobjeto tzinfo usted mismo (ejemplo de documentos de fecha y hora):

from datetime import tzinfo, timedelta, datetime

ZERO = timedelta(0)

class UTC(tzinfo):
  def utcoffset(self, dt):
    return ZERO
  def tzname(self, dt):
    return "UTC"
  def dst(self, dt):
    return ZERO

utc = UTC()

luego:

now = datetime.now(utc)
jfs
fuente
10
Es mejor que eliminar el tz, ya que la respuesta aceptada aboga por mi humilde opinión.
Shautieh
3
Aquí hay una lista de zonas horarias de Python: stackoverflow.com/questions/13866926/…
cómodo
61

Sé que algunas personas usan Django específicamente como interfaz para abstraer este tipo de interacción con la base de datos. Django proporciona utilidades que pueden usarse para esto:

from django.utils import timezone
now_aware = timezone.now()

Debe configurar una infraestructura básica de configuración de Django, incluso si solo está utilizando este tipo de interfaz (en la configuración, debe incluirla USE_TZ=Truepara obtener una fecha y hora consciente).

Por sí solo, esto probablemente no sea lo suficientemente cercano como para motivarte a usar Django como interfaz, pero hay muchas otras ventajas. Por otro lado, si tropezó aquí porque estaba destrozando su aplicación Django (como lo hice), entonces quizás esto ayude ...

sabio
fuente
1
necesita USE_TZ=True, para obtener una fecha y hora consciente aquí.
jfs
2
Sí, olvidé mencionar que necesita configurar su settings.py como describe JFSebastian (supongo que esta fue una instancia de 'establecer y olvidar').
sabio
Y esto también se puede convertir fácilmente a otras zonas horarias, como + timedelta(hours=5, minutes=30)IST
ABcDexter
25

Esta es una solución muy simple y clara.
Dos líneas de código.

# First we obtain de timezone info o some datatime variable    

tz_info = your_timezone_aware_variable.tzinfo

# Now we can subtract two variables using the same time zone info
# For instance
# Lets obtain the Now() datetime but for the tz_info we got before

diff = datetime.datetime.now(tz_info)-your_timezone_aware_variable

Conclusión: debe administrar sus variables de fecha y hora con la misma información de hora

ePi272314
fuente
¿Incorrecto? El código que escribí fue probado y lo estoy usando en un proyecto de django. Es mucho más claro y simple
ePi272314
el "incorrecto" se refiere a la última oración en su respuesta: "... debe agregar ... no UTC" : la zona horaria UTC funciona aquí y, por lo tanto, la declaración es incorrecta.
jfs
para ser claro, quise decir: diff = datetime.now(timezone.utc) - your_timezone_aware_variablefunciona (y la (a - b)fórmula anterior es la explicación de por qué (a - b)puede funcionar incluso si a.tzinfono es así b.tzinfo).
jfs
6

El módulo psycopg2 tiene sus propias definiciones de zona horaria, así que terminé escribiendo mi propio contenedor alrededor de utcnow:

def pg_utcnow():
    import psycopg2
    return datetime.utcnow().replace(
        tzinfo=psycopg2.tz.FixedOffsetTimezone(offset=0, name=None))

y utilícelo pg_utcnowsiempre que necesite la hora actual para comparar con un PostgreSQLtimestamptz

erjiang
fuente
Cualquier objeto tzinfo que devuelva un desplazamiento de utc cero funcionará, por ejemplo .
jfs
6

También enfrenté el mismo problema. Luego encontré una solución después de mucha búsqueda.

El problema era que cuando obtenemos el objeto de fecha y hora del modelo o formulario, es consciente de la compensación y si obtenemos la hora por sistema, es una compensación ingenua .

Entonces, lo que hice es obtener la hora actual usando timezone.now () e importar la zona horaria desde django.utils import timezone y poner USE_TZ = True en el archivo de configuración del proyecto.

Ashok Joshi
fuente
2

Se me ocurrió una solución ultra simple:

import datetime

def calcEpochSec(dt):
    epochZero = datetime.datetime(1970,1,1,tzinfo = dt.tzinfo)
    return (dt - epochZero).total_seconds()

Funciona con valores de fecha y hora que reconocen la zona horaria y no son ingenuos. Y no se requieren bibliotecas adicionales ni soluciones alternativas a la base de datos.

John L. Stanley
fuente
1

He encontrado que timezone.make_aware(datetime.datetime.now())es útil en django (estoy en 1.9.1). Desafortunadamente, no puede simplemente hacer que un datetimeobjeto sea consciente del desplazamiento, entonces timetz(). Tienes que hacer una datetimey hacer comparaciones basadas en eso.

Joseph Coco
fuente
1

¿Hay alguna razón apremiante por la que no puede manejar el cálculo de la edad en PostgreSQL? Algo como

select *, age(timeStampField) as timeStampAge from myTable
Nic Gibson
fuente
2
Sí, lo hay ... pero principalmente preguntaba porque quiero evitar hacer todos los cálculos en postgre.
Ian
0

Sé que esto es viejo, pero pensé que agregaría mi solución en caso de que alguien la encuentre útil.

Quería comparar la fecha y hora local ingenua con una fecha y hora consciente de un servidor de tiempo. Básicamente, creé un nuevo objeto de fecha y hora ingenuo usando el objeto de fecha y hora consciente. Es un truco y no se ve muy bonito, pero hace el trabajo.

import ntplib
import datetime
from datetime import timezone

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

try:
    ntpt = ntplib.NTPClient()
    response = ntpt.request('pool.ntp.org')
    date = utc_to_local(datetime.datetime.utcfromtimestamp(response.tx_time))
    sysdate = datetime.datetime.now()

... aquí viene el dulce de azúcar ...

    temp_date = datetime.datetime(int(str(date)[:4]),int(str(date)[5:7]),int(str(date)[8:10]),int(str(date)[11:13]),int(str(date)[14:16]),int(str(date)[17:19]))
    dt_delta = temp_date-sysdate
except Exception:
    print('Something went wrong :-(')
I_do_python
fuente
FYI, utc_to_local()de mi respuesta devuelve la hora local como un objeto de fecha y hora consciente (es el código Python 3.3+)
jfs
No está claro qué intenta hacer su código. Podrías reemplazarlo con delta = response.tx_time - time.time().
jfs