getmtime () vs datetime.now ():

8

Este código imprime una advertencia falsa una vez al año, en la noche del cambio de reloj (horario de verano de Europa central a horario de Europa central):

import os
import datetime

now = datetime.datetime.now()
age = now - datetime.datetime.fromtimestamp(os.path.getmtime(file_name))
if (age.seconds + age.days * 24 * 3600) < -180:
    print('WARN: file has timestap from future?: %s' % age)

¿Cómo hacer que este código funcione incluso durante este turno anual de una hora?

Actualizar

Solo me importa la edad, no la fecha y hora.

guettli
fuente

Respuestas:

6

El fragmento publicado se puede mejorar fácilmente al cambiar de hora local a UTC. No hay cambios de horario de verano (horario de verano) en UTC. Simplemente reemplace estas dos funciones de fecha y hora now()-> utcnow()( docs ) y fromtimestamp()-> utcfromtimestamp()( docs ).

Sin embargo, si el único resultado esperado es una antigüedad del archivo en segundos, podemos usar directamente las marcas de tiempo (segundos de una "época") sin ninguna conversión:

import time
import os.path

...
age = time.time() - os.path.getmtime(file_name)
VPfB
fuente
3
Usar UTC en primer lugar es el enfoque universalmente correcto.
konstantin el
@konstantin ¿por qué es el enfoque correcto? Me gusta esta solución simple, ya que (en este contexto) solo me importa la edad (timedelta), no la fecha y hora.
guettli
@guettli, diría que esta es probablemente la mejor y más simple respuesta para su caso de uso. Lo más importante cuando se compara el tiempo es que se compara igual por igual, en este ejemplo es una marca de tiempo UTC versus una marca de tiempo UTC, por lo que siempre funcionará. La razón por la que su código no funcionó originalmente es porque estaba comparando objetos que ya no tenían relevancia para una marca de tiempo UTC, ya que no son conscientes de la zona horaria. Si tiene la intención de hacer cosas más complejas, entonces mi respuesta puede ser más útil, ya que es más fácil trabajar con objetos de fecha y hora, pero para una comparación simple, esto funciona.
KillerKode
1
@guettli Lo califiqué como "enfoque excesivamente correcto" porque pasé demasiadas horas, si no días, depurando sistemas e interfaces que solo funcionaban con algunas suposiciones a priori sobre las fechas y zonas horarias recibidas como entrada. Si, por ejemplo, su servidor no se ejecuta en la misma zona horaria que el cliente y las fechas y horas se pasan sin un desplazamiento UTC explícito e interpretadas como fechas y horas locales, las cosas podrían funcionar de alguna manera (por ejemplo, al calcular deltas), pero es una tarea difícil de depurar que podría ser fácilmente evitado si todos se apegaron a UTC solo en primer lugar / lo antes posible.
konstantin
1
@guettli Gracias por aceptar mi respuesta. Espero que haya sido útil, porque me temo que mi breve respuesta no vale una generosidad tan generosa y me pagaste en exceso. Saludos cordiales (Schöne Grüße nach Chemnitz)
VPfB
3

ambos objetos datetime son 'ingenuos', lo que significa que no saben acerca del horario de verano. datetime.now()devuelve la hora actual en que se ejecuta su máquina, y eso podría incluir DST. Lo mismo vale para datetime.fromtimestamp(os.path.getmtime()).

# 1: localizar sus objetos de fecha y hora podría ser una opción; algo como

from datetime import datetime
import tzlocal
now_aware = tzlocal.get_localzone().localize(datetime.now())
file_mtime = datetime.fromtimestamp(os.path.getmtime(file))
# assuming the file was created on a machine in the same timezone (!):
file_mtime_aware = now_aware.tzinfo.localize(file_mtime)
age = now_aware - file_mtime_aware

# 2 - otra opción, usando la conversión UTC con datetime:

now = datetime.utcnow()
age = now - datetime.utcfromtimestamp(os.path.getmtime(file_name))
if (age.seconds + age.days * 24 * 3600) < -180:
    print(f'WARN: file has timestamp from future?: {age} s')

# 3 - como VPfB señala en su respuesta, os.path.getmtimedevuelve una marca de tiempo UTC (verifique los documentos del módulo os y los documentos del módulo horario ). Entonces, la solución más fácil podría ser omitir la conversión datetimeen primer lugar y usar solo marcas de tiempo UTC; por ejemplo, obtener la marca de tiempo UTC actual como time.time().

Trabajar con zonas horarias puede volverte loco ... pero hay algunos buenos recursos, por ejemplo, esta publicación mediana .

MrFuppes
fuente
1

Su problema es que está obteniendo su tiempo sin que sea consciente de la zona horaria. Entonces, cuando cambian los relojes, terminas de comparar una marca de tiempo anterior al cambio del reloj y otra después del cambio del reloj y tu código no ve esto.

En cambio, debe hacer que sus objetos de fecha y hora se basen en una zona horaria específica para que no tenga problemas con el cambio de relojes, le recomiendo usar el módulo pytz para ayudarlo con esto. Puede ver una lista de zonas horarias disponibles en esta respuesta: ¿Hay una lista de zonas horarias de Pytz?

Aquí hay un ejemplo de código simple de cómo puede hacer esto con objetos conscientes de la zona horaria:

import os
from datetime import datetime
import pytz


def get_datetime_now(timezone):
    """
    Returns timezone aware datetime object for right now
    """
    if timezone not in pytz.all_timezones:
        return None
    tz = pytz.timezone(timezone)
    dt = datetime.now().astimezone()
    return dt.astimezone(tz)


def timestamp_to_datetime(timestamp, timezone):
    """
    Returns a datetime object from a timestamp
    """
    if timezone not in pytz.all_timezones:
        return None
    tz = pytz.timezone(timezone)
    dt = datetime.fromtimestamp(timestamp).astimezone()
    return dt.astimezone(tz)


timezone = 'CET'

file_timestamp = os.path.getmtime(file_name)

now = get_datetime_now(timezone)
file_datetime = timestamp_to_datetime(file_timestamp, timezone)
age = now - file_datetime

if (age.seconds + age.days * 24 * 3600) < -180:
    print('WARN: file has timestap from future?: %s' % age)
KillerKode
fuente
¿Por qué es tu solución mejor que age = time.time() - os.path.getmtime(file_name). Solo estoy interesado en la edad (hora delta) no en la fecha y hora.
guettli
1
Si solo está interesado en el delta de tiempo, entonces no lo está. La razón por la que lo abordé de esta manera es porque usted mencionó que se encuentra en la zona horaria CET y mostró que estaba trabajando con objetos de fecha y hora, este enfoque puede ser útil si compara tiempos entre dos zonas horarias diferentes. Si sus zonas horarias son las mismas, simplemente comparar las marcas de tiempo debería ser suficiente. La única otra consideración es asegurarse de que la hora de su sistema esté sincronizada con un servidor NTP.
KillerKode