Skip to content

Instantly share code, notes, and snippets.

@icyveins7
Last active January 26, 2023 04:04
Show Gist options
  • Save icyveins7/44bfd9b2fc835b61061456cff141eee8 to your computer and use it in GitHub Desktop.
Save icyveins7/44bfd9b2fc835b61061456cff141eee8 to your computer and use it in GitHub Desktop.
Dealing with Unix timestamps in Python/C

Introduction

This gist may be helpful to those who often construct and/or deal with datetimes in different timezones using Unix epoch seconds.

The Somewhat Obscure Problem in Python

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:

  1. 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.
  2. 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:

  1. 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.
  2. 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.

Mirroring this in C

In C, we have the following:

  1. tm struct: Contains fields for year, month, day, hour, min, second etc.
  2. time_t typedef: Number of seconds since Unix epoch. Integer type.
  3. gmtime function and flavours: Converts a time_t into a tm object, at UTC (no additions).
  4. localtime function and flavours: Converts a time_t into a tm object, adding a local timezone offset.
  5. asctime function: Prints a tm struct.
  6. mktime function: Converts a tm struct into a time_t variable.

Some pseudo-code is the following:

struct tm t;

To be completed...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment