83

What is the most idiomatic/efficient way to convert from a modification time retrieved from stat() call to a datetime object? I came up with the following (python3):

from datetime import datetime, timedelta, timezone
from pathlib import Path

path = Path('foo')
path.touch()
statResult = path.stat()
epoch = datetime(1970, 1, 1, tzinfo=timezone.utc)
modified = epoch + timedelta(seconds=statResult.st_mtime)
print('modified', modified)

Seems round a bout, and a bit surprising that I have to hard code the Unix epoch in there. Is there a more direct way?

Travis Griggs
  • 21,522
  • 19
  • 91
  • 167

3 Answers3

101

You can use datetime.datetime.fromtimestamp, i.e.

from datetime import datetime, timezone
...
stat_result = path.stat()
modified = datetime.fromtimestamp(stat_result.st_mtime, tz=timezone.utc)
print('modified', modified)
Christian Long
  • 10,385
  • 6
  • 60
  • 58
Take_Care_
  • 1,957
  • 1
  • 22
  • 24
  • i suggest to add reference for timezone – Zaman Oct 14 '21 at 17:22
  • How can you justify converting st_mtime, a "naive" POSIX timestamp, into a timezone-aware `datetime.datetime`, with a specific TZ, namely UTC? A file can be saved on one OS and then copied/moved to another... So wouldn't this imply that every way of saving a file in every possible OS took the trouble to stamp with a UTC value? – mike rodent Jun 16 '23 at 17:43
  • @mikerodent, my initial answer doesn't include any information about time-zones. https://stackoverflow.com/posts/39359270/revisions at all. If you check the question that has been asked, you will see that the author is operating in the UTC TZ, so I guess the editor flying-sheep added these parts during edit. – Take_Care_ Jun 19 '23 at 12:02
  • Rolled back a big and somewhat misleading edit on this highly-voted answer. See the answer below from user mike rodent for the argument (that was previously in this answer) about not converting mtime to a timezone-aware object localized in UTC. – Christian Long Jul 24 '23 at 21:28
  • 1
    See [this answer](https://stackoverflow.com/questions/23062515/do-unix-timestamps-change-across-timezones/23062640#23062640) for more about why it's ok to convert a file mtime to a UTC datetime. In short, the mtime is measured in seconds-since-the-epoch (no timezone involved in that calculation), and the epoch is defined as being in UTC. – Christian Long Jul 24 '23 at 21:30
30

This works for me if you want a readable string:

import datetime
mtime = path.stat().st_mtime
timestamp_str = datetime.datetime.fromtimestamp(mtime).strftime('%Y-%m-%d %H:%M')
Kirill Bulygin
  • 3,658
  • 1
  • 17
  • 23
Sam Shleifer
  • 1,716
  • 2
  • 18
  • 29
  • 4
    nice to change it to standard as ISO 8601 and RFC 3339 '%Y-%m-%d %H:%M:%S' or '%Y-%m-%dT%H:%M:%S' more see https://medium.com/easyread/understanding-about-rfc-3339-for-datetime-formatting-in-software-engineering-940aa5d5f68a – Zaman Oct 14 '21 at 17:20
0

I believe that file.stat().st_time is a POSIX timestamp (i.e. a number, whether int or float).

Therefore it would seem to me that it is the very definition of a timezone-naive value.

Unless... bearing in mind that a file can be saved on one OS and then copied/moved to another (and from one timezone to another for that matter)... So unless you can be sure that every way of saving a file in every possible OS takes the trouble to stamp with a UTC value... which seems not to be the case: see here, for example. Windoze (surprise!) does not consistently seem to save with UTC values.

I note that the questioner references the "Unix epoch", although it is not clear what his OS is. But as I say, even a Unix OS user can receive a file from somewhere else.

So it seems to me that there is no justification to transform this value into a timezone-aware value. Instead:

naive_last_modif_datetime = datetime.fromtimestamp(path.stat().st_mtime)

It is in fact terribly misleading to let a false validation (in this case of tz-awareness of a datetime) be slipped in inadvertently like this, and could lead to some very strange and baffling anomalies down the line.

mike rodent
  • 14,126
  • 11
  • 103
  • 157