2

Based on this related question and answer I asked, it has become obvious that datetime.fromtimestamp(os.path.getctime()) in Python 3.4 doesn't return a timezone-aware datetime object, However, based on some investigation, I've also discovered that OS X 10.9 on an HFS+ filesystem (for example) does seem to maintain the timezones along with ctimes (unless gls is inferring the timezone from my local timezone and daylight-savings time):

$ gls -l --full-time -c

-rw-------  1 myuser staff 538 2015-01-04 17:12:57.000000000 +0100 fileone
-rwxr-xr-x 17 myuser staff 578 2015-05-20 06:41:07.000000000 +0200 filetwo

(I am using the GNU version of ls)

How can I get the timezone from the ctime and insert/combine it into a datetime object?

(I'd also like the same answer for the mtime, I assume it will be similar).

Community
  • 1
  • 1
Andrew Ferrier
  • 16,664
  • 13
  • 47
  • 76
  • What version of OS X is this? As far as I can determine, HFS+ stores file timestamps as *UTC only*, and system calls translate to the current timezone. `ls` does not have a `--full-time` switch on my system (OS X 10.10), and the `-T` switch does not display a timezone either. – Martijn Pieters Jun 09 '15 at 11:31
  • See http://forensicswiki.org/wiki/Mac_OS_X#HFS.2FHFS.2B_date_and_time_values; perhaps you are using `HFS` rather than `HFS+`? – Martijn Pieters Jun 09 '15 at 11:38
  • @MartijnPieters: it is unrelated to filesystems. `ls` gets time (POSIX timestamp + nanoseconds) from `lstat(2)` and displays it in the terminal using `/etc/localtime` on my system. – jfs Jun 09 '15 at 12:38
  • @MartijnPieters I've edited answers to your Qs into my Q. – Andrew Ferrier Jun 09 '15 at 12:49

3 Answers3

6

Both ctime and mtime are available as "seconds since epoch" (values returned by time.time()).

To get the local timezone, you could use tzlocal module:

#!/usr/bin/env python
import os
from datetime import datetime
from tzlocal import get_localzone # $ pip install tzlocal

local_timezone = get_localzone()
aware_dt = datetime.fromtimestamp(os.path.getctime(path), local_timezone)

You might see the timezone info because ls converts the timestamps into corresponding broken-down time with timezone offset using the local timezone.

jfs
  • 399,953
  • 195
  • 994
  • 1,670
  • J.F. Sebastian, "Both ctime and mtime are available as "seconds since epoch". Are you sure this is true for all POSIX platforms? Is this defined somewhere? – Andrew Ferrier Jun 09 '15 at 12:53
  • @AndrewFerrier: it is true everywhere where Python works even on Windows. It is defined in the Python docs for the corresponding functions (`os.path.getctime()` and [`os.path.getmtime()`](https://docs.python.org/3/library/os.path.html#os.path.getmtime)) – jfs Jun 09 '15 at 12:55
  • OK makes sense. Is that the UTC epoch, or the local timezone epoch? – Andrew Ferrier Jun 09 '15 at 12:59
  • 2
    @AndrewFerrier: the number ("seconds since epoch") does not depends on timezone ("epoch" is the *same* time instant around the globe). Look at the `fromtimestamp()` call: you can pass *any* timezone as the second parameter. – jfs Jun 09 '15 at 13:03
  • That was really my question - are there local timezone epochs? It's pretty clear from what you say there aren't. – Andrew Ferrier Jun 09 '15 at 13:16
  • 1
    @AndrewFerrier: yes. If you change your local timezone then the output of `ls` may also change (different local time/utc offset) but the numbers returned by `getmtime()` will remain the same e.g., it is `1420387977` for `fileone` whatever your local timezone is. – jfs Jun 09 '15 at 13:23
  • Interesting. In that case, it would be trivial for Python to create a timezone-aware datetime object (http://stackoverflow.com/questions/30719734/does-datetime-fromtimestampos-path-getctime-in-python-give-me-a-timezone-awa/30720061#30720061), since the stored value is unambiguous. I wonder why it doesn't? – Andrew Ferrier Jun 09 '15 at 13:30
  • 1
    @AndrewFerrier: As [I said](http://stackoverflow.com/a/30732046/4279): `fromtimestamp()` by itself (no second parameter) returns a **naive** datetime object. And there is no `get_localzone()` (returns `pytz` timezone) in stdlib. It is not trivial to [get the utc offset for the local timezone in the general case](http://stackoverflow.com/a/3168394/4279). – jfs Jun 09 '15 at 13:42
  • The best you can do in stdlib `aware = datetime.now(timezone.utc).astimezone()` or `email.utils.localtime()` but these functions may fail. Use `pytz` as shown in my answer instead ([most computer systems use the tz database](https://www.iana.org/time-zones/repository/tz-link.html)). – jfs Jun 09 '15 at 13:43
  • Got it. Sorry I didn't get that first time. – Andrew Ferrier Jun 09 '15 at 14:22
1

If you only want to rely on Python Standard Library, you can only use the timezone subclass of tzinfo :

tz = datetime.timezone(datetime.timedelta(seconds=-time.timezone), time.tzname[0]) \
    if (time.daylight == 0 || time.localtime(os.path.getctime(path)).tm_isdst == 0) \
    else datetime.timezone(datetime.timedelta(seconds=-time.altzone), time.tzname[1])
dt = datetime.fromtimestamp(os.path.getctime(path), tz)

Then you could have (in France) :

>>> dt
datetime.datetime(2015, 6, 9, 13, 43, 3, 791255, tzinfo=datetime.timezone(datetime.timedelta(0, 7200), 'Paris, Madrid (heure d\x92été)'))
>>> dt.utctimetuple()
time.struct_time(tm_year=2015, tm_mon=6, tm_mday=9, tm_hour=11, tm_min=43, tm_sec=3, tm_wday=1, tm_yday=160, tm_isdst=0)

Of course, mtime would work exactly the same

You should consult J.F. Sebastian's post for references. Here is an extract :

To get the current UTC offset in a way that workarounds the time.daylight issue and that works even if tm_gmtoff is not available, [this] can be used:

import time
from datetime import datetime

ts = time.time()
utc_offset = (datetime.fromtimestamp(ts) -
              datetime.utcfromtimestamp(ts)).total_seconds()
Community
  • 1
  • 1
Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • no. stdlib does not provide access to the tz database (there is [pep-431](https://www.python.org/dev/peps/pep-0431/)). `time.timezone` and `time.altzone` may fail. See [Getting computer's utc offset in Python](http://stackoverflow.com/q/3168096/4279) – jfs Jun 09 '15 at 12:30
  • no. It is not about it. [Read the link](http://stackoverflow.com/a/3168394/4279). Also, your `time.daylight` usage is wrong. – jfs Jun 09 '15 at 12:47
  • it is still incorrect. `time.daylight` (when it works) says whether the current timezone observes DST -- it does *not* say whether DST is in effect *right now*. Notice that I've used `time.daylight and time.localtime().tm_isdst > 0` -- it is wrong to use just `time.daylight` here. Anyway, even that may break for timezones that had/will have different rules in the past/future. If you want a reasonable timezone support; use `pytz` module (`tzlocal.get_localzone()` returns `pytz` timezone corresponding to the local timezone). – jfs Jun 09 '15 at 13:10
  • @J.F.Sebastian : Thanks for the explaination. But I still do not understand how time.timezone and time.altzone may fail. I have read pep-431 and the edge cases cited in your post, but I'm afraid this is still beyond my current capacities :-( . I'll leave this post for future references unless you think it is more dangerous than useful – Serge Ballesta Jun 09 '15 at 13:44
  • `time.timezone` is a constant (a single number e.g., `18000`). It means that it remains the same whether you are handling dates from 2000 or 2015. utc offset for the local timezone in 2000 may be different from utc offset in 2015 (the rules are defined by local politicians -- logic does not apply) but `time.timezone` is just a single number: it is either wrong for 2000 or it is wrong for 2015. – jfs Jun 09 '15 at 13:59
  • The link in [my answer](http://stackoverflow.com/a/3168394/4279) points to [the related Python bug](http://bugs.python.org/issue1647654) that in turn points to [the bug in mercurial](http://bz.selenic.com/show_bug.cgi?id=2511) -- follow the links if you need a concrete example. – jfs Jun 09 '15 at 13:59
-1

This works for me:

import os, datetime, time

modified_time = os.path.getmtime(file)
mtime_obj = datetime.datetime(*time.localtime(modified_time)[:6])
# (or)
mtime_obj = datetime.datetime.fromtimestamp(modified_time)
print(mtime_obj.strftime('%Y-%m-%d_%H-%M-%S'))

No external packages. Just the standard python libraries. (Python 3.6)

Anjan
  • 1,613
  • 1
  • 19
  • 25