46

Is it possible, and if yes, how, to get the time zone (i.e. the UTC offset or a datetime.timezone instance with that offset) that is used by datetime.datetime.fromtimestamp() to convert a POSIX timestamp (seconds since the epoch) to a datetime object?

datetime.datetime.fromtimestamp() converts a POSIX timestamp to a naive datetime object (i.e. without a tzinfo), but does so using the system's locale to adjust it to the local timezone and the UTC offset that was in effect at that time.

For example, using the date 2008-12-27 midnight UTC (40 * 356 * 86400 seconds since the epoch):

>>> datetime.datetime.fromtimestamp(40 * 356 * 86400)
datetime.datetime(2008, 12, 27, 1, 0)

That timestamp is converted to a datetime object at 1 o'clock in the morning (which it was at that time, here in an CET/CEST timezone). 100 days later, this is the result:

>>> datetime.datetime.fromtimestamp((40 * 356 + 100) * 86400)
datetime.datetime(2009, 4, 6, 2, 0)

Which is 2 o'clock in the morning. This is because by then, DST was active.

I'd expected that datetime.datetime.fromtimestamp() would set the tzinfo it uses in the returned datetime instance, but it doesn't.

Feuermurmel
  • 9,490
  • 10
  • 60
  • 90

4 Answers4

41

datetime.fromtimestamp(ts) converts "seconds since the epoch" to a naive datetime object that represents local time. tzinfo is always None in this case.

Local timezone may have had a different UTC offset in the past. On some systems that provide access to a historical timezone database, fromtimestamp() may take it into account.

To get the UTC offset used by fromtimestamp():

utc_offset = fromtimestamp(ts) - utcfromtimestamp(ts)

See also, Getting computer's utc offset in Python.

Community
  • 1
  • 1
jfs
  • 399,953
  • 195
  • 994
  • 1,670
  • 1
    This is actually what I would have needed back when I asked the question. Knowing the time zone rules is not important, just the UTC offset is enough for what I was doing. – Feuermurmel Mar 16 '15 at 12:18
28

From the Python documentation:

classmethod datetime.fromtimestamp(timestamp, tz=None)

Return the local date and time corresponding to the POSIX timestamp, such as is returned by time.time(). If optional argument tz is None or not specified, the timestamp is converted to the platform’s local date and time, and the returned datetime object is naive.

Else tz must be an instance of a class tzinfo subclass, and the timestamp is converted to tz‘s time zone. In this case the result is equivalent to tz.fromutc(datetime.utcfromtimestamp(timestamp).replace(tzinfo=tz)).

The key part of this description as it relates to your question is that when you don't specify a time zone, not only does it use the local time zone, but the result is naive. You seem to want it to be aware.

This is a particular distinction made by Python, and is discussed right at the very top of the datetime documentation.

If what you want is a datetime that is aware of the local time zone, try the tzlocal library. It is focused on that particular problem. See also this question.

Community
  • 1
  • 1
Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
  • 4
    Yes, I want an aware `datetime` object, but I am looking for a way to do this without using any additional libraries, as Python _obviously_ already has access to the necessary logic for calculating the correct offset from a POSIX timestamp. – Feuermurmel Sep 16 '13 at 11:31
  • 1
    It doesn't actually have it on its own. It relies on the host environment to provide the translation. Since that is platform-specific, and different platforms have different tz database implementations, it can't copy the conversion rules back into a tzinfo object. – Matt Johnson-Pint Sep 16 '13 at 15:01
  • I know this is the case. But through the C library it gets access to the logic that can assign a UTC offset to a POSIX timestamp using the OS's settings (otherwise the code in the question wouldn't work). – Feuermurmel Sep 16 '13 at 16:11
  • Exactly. It just can't copy that logic back to a tzinfo python object, so it can never represent that as an *aware* datetime. That's why the tzlocal library tries to guess at the timezone rules. It interrogates the OS, but then assigns its own value back to tzinfo. There's nothing built-in that would do that. – Matt Johnson-Pint Sep 16 '13 at 16:54
  • @Feuermurmel: .tm_zone, .tm_gmtoff are not available on all platforms. And time.timezone, time.tzname may fail for some timezones. See the last link in [my answer](http://stackoverflow.com/a/27918285/4279). tzlocal returns pytz timezone that is the best option for cross-platform solutions. Moreover different versions of the tz database may provide *different* definitions for *the same* timezone -- installing pure Python pytz module allows you to control the version used. – jfs Mar 14 '15 at 23:07
  • `s/not available on all/available only on some/` – jfs Jun 30 '20 at 18:37
14

If you know the timezone of the timestamp you want to convert, you can simply send it in while calling fromtimestamp:

>>> from datetime import datetime
>>> import pytz
>>>
>>> datetime.fromtimestamp(1562684265, pytz.timezone("Europe/Stockholm"))
datetime.datetime(2019, 7, 9, 16, 57, 45, tzinfo=<DstTzInfo 'Europe/Stockholm' CEST+2:00:00 DST>)
>>>
>>> datetime.fromtimestamp(1562684265, pytz.timezone("UTC"))
datetime.datetime(2019, 7, 9, 14, 57, 45, tzinfo=<UTC>)
Emil Stenström
  • 13,329
  • 8
  • 53
  • 75
3

Using time.gmtime you can extract the timezone as described in this previous answer: Get TZ information of the system in Python?.

>>> from __future__ import print_function
>>> from time import gmtime, strftime
>>> print(strftime("%z", gmtime()))
-0600

Prints -06:00 for my CST laptop in both python-2.7 and python-3.3 You can also use localtime() to get a local time struct.

>>> from __future__ import print_function
>>> from time import localtime
>>> lt = localtime()
>>> print(lt.tm_zone)
"CDT"
>>> print(lt.tm_gmtoff/(60*60))
-5.0
>>> print(lt.tm_gmtoff/(60*60) - (1 if lt.tm_isdst == 1 else 0)) # Adjusted for DST
-6.0

Hope this helps

Community
  • 1
  • 1
AChampion
  • 29,683
  • 4
  • 59
  • 75
  • You haven't liked to a specific answer (you can do so by clicking _share_ below an answer and copying the url), which do you mean? – Feuermurmel Sep 15 '13 at 15:46
  • You probably meant `time.gmtime`. In Python 3.3, it prints `time.struct_time(tm_year=2008, tm_mon=12, tm_mday=27, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=5, tm_yday=362, tm_isdst=0)`. I see no time zone information. – Feuermurmel Sep 15 '13 at 15:47
  • Oh, you probably meant `time.gmtime().tm_gmtoff`! It doesn't work. It returns `0` on a system that is set to CEST. – Feuermurmel Sep 15 '13 at 15:52
  • Two notes: Shouldn't your first example print `+0000`, as `gmtimes()`'s documentation states that the returned `struct_time` is in UTC? And `lt.tm_gmtoff` seems to be the only way I've found so far, but this means that `localtime()` has to be called twice (once by `datetime.fromtimestamp()`) to create an aware `datetime` instance. – Feuermurmel Sep 16 '13 at 12:15
  • `strftime("%z")` prints the current UTC offset for the local timezone whatever the argument i.e., `gmtime()` has nothing to do with it – jfs Jan 13 '15 at 09:03
  • 1
    Also UTC offset at `40 * 356 * 86400` timestamp may be different from the current UTC offset. And`tm_gmtoff` is not available on all systems, see [my answer](http://stackoverflow.com/a/27918285/4279). – jfs Jan 13 '15 at 09:18