This gist may be helpful to those who often construct and/or deal with datetimes in different timezones using Unix epoch seconds.
There is a distinct difference between the following two snippets of code in Python:
import datetime as dt
dt.datetime.utcfromtimestamp(0)
# datetime.datetime(1970, 1, 1, 0, 0)
dt.datetime.fromtimestamp(0)
# datetime.datetime(1970, 1, 1, 7, 30)
Okay, so far so sensible; constructing 0 epoch seconds with utcfromtimestamp
results in the start of the unix epoch at 1970-1-1 0:0:0. Constructing it with just fromtimestamp
includes the current timezone (not sure why it's GMT+7.5 instead of GMT+8), which then results in an offset. Strictly speaking, the following is happening:
- The first line appears to be constructing a GMT+0 datetime by interpreting the argument as the number of Unix epoch seconds that have passed.
- The second line is constructing a local timezone datetime also by interpreting the argument as the number of Unix epoch seconds that have passed.
But this is not (always) trivially reversible:
dt.datetime.utcfromtimestamp(0).timestamp()
# -27000.0
dt.datetime.fromtimestamp(0).timestamp()
# 0.0
Clearly, the act of calling timestamp()
does not reflect the knowledge of the timezone when the utcfromtimestamp()
call was used. What has happened is:
- Datetime is constructed as a local GMT+7.5 datetime object, but with values corresponding to the correct datetime if you were situated at the GMT+0 timezone.
- Hence when
timestamp()
is invoked, the datetime object attempts to remove the GMT+7.5, resulting in the -27000 value.
This is due to the objects being constructed as naive datetime objects as opposed to aware datetime objects. See https://docs.python.org/3/library/datetime.html for details.
In the first case, utcfromtimestamp
does not offer a method to construct an aware datetime object. But in the second case, fromtimestamp
allows us a choice.
We can (and should) actually rewrite the first line as
dt.datetime.fromtimestamp(0, tz=dt.timezone.utc)
# datetime.datetime(1970, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)
dt.datetime.fromtimestamp(0, tz=dt.timezone.utc).timestamp()
# 0.0
If you have had problems with Python datetimes and Unix epoch seconds conversions back and forth, you have likely found StackOverflow articles already covering this. I wrote this more as a reminder to myself.
So far we have discussed using the datetime
import, but what about the more common time
import? It turns out that the most common timing command is exactly equivalent to the UTC time we want
# These 2 outputs are the same!
time.time()
dt.datetime.now(tz=dt.timezone.utc).timestamp()
# But this is not!
dt.datetime.utcnow().timestamp() # This has your timezone hours included as an offset!
# And more importantly, utcnow() has no option to specify the aware datetimes by using a timezone parameter.
In C, we have the following:
tm
struct: Contains fields for year, month, day, hour, min, second etc.time_t
typedef: Number of seconds since Unix epoch. Integer type.gmtime
function and flavours: Converts a time_t into atm
object, at UTC (no additions).localtime
function and flavours: Converts a time_t into atm
object, adding a local timezone offset.asctime
function: Prints atm
struct.mktime
function: Converts atm
struct into atime_t
variable.
Some pseudo-code is the following:
struct tm t;
To be completed...