guettli
guettli

Reputation: 27855

getmtime() vs datetime.now():

This code prints a false warning once per year, in the night of the clock shift (central European summer time to central European time):

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)

How to make this code work even during this yearly one hour clock shift?

Update

I care only for the age, not the datetime.

Upvotes: 9

Views: 4255

Answers (3)

KillerKode
KillerKode

Reputation: 997

Your problem is you are getting your time without it being timezone aware. So when the clocks change, you end comparing one timestamp that is from before the clock change and another that is after the clock change and your code does not see this.

You should instead get your datetime objects to be based on a specific timezone so you don't have issues with clocks changing, I recommend using the pytz module to help you with this. You can see a list of available timezones in this answer: Is there a list of Pytz Timezones?

Here is a simple code example of how you can do this with timezone aware objects:

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)

Upvotes: 1

FObersteiner
FObersteiner

Reputation: 25564

both your datetime objects are 'naive', meaning that they don't know about DST. datetime.now() returns the current time your machine runs on, and that might include DST. Same goes for datetime.fromtimestamp(os.path.getmtime()).

#1 - localizing your datetime objects could be an option; something like

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 - another option, using UTC conversion with 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 - as VPfB points out in his answer, os.path.getmtime returns a UTC timestamp (check os module docs and time module docs). So the easiest solution could be to skip conversion to datetime in the first place and use only UTC timestamps; e.g. getting the current UTC timestamp as time.time().

Working with timezones can drive you mad... but there're some good resources out there, e.g. this medium post.

Upvotes: 3

VPfB
VPfB

Reputation: 17282

The posted fragment can be easily improved by switching from local to UTC time. There are no summer (daylight saving) time changes in UTC. Just replace these two datetime functions now() -> utcnow() (docs) and fromtimestamp() -> utcfromtimestamp() (docs).

However, if the only expected output is a file age in seconds, we can directly use the timestamps (seconds from an "epoch") without any conversion:

import time
import os.path

...
age = time.time() - os.path.getmtime(file_name)

Upvotes: 8

Related Questions