Dates And Time In Python

Page Contents

References

Introduction

The referenced article, "The Science Of Timekeeping", gives a really nice introduction to time and the history of how we measure time. Combined with the reference WC3 article we can get an idea of how our concept of time originated from observable phenomena like sunrise and sunset, the seasons etc, and how now it is based on more abstract standards like the the transition between the two hyperfine levels of the ground state of the cesium-133 atom.

All of this is important for us to realise that there is no really universal definition of time that is the same all over the world, except for something called UTC.

Countries have their own timezones, not all of which differ in a regular pattern, where daylight saving time (DST) may be active or not depending on the time of year, and worse, there are crazy things like leap days end even leap seconds!

In 1884, ... the Greenwich observatory was accepted as source of the world’s standard time ...

... GMT has evolved to UTC ... GMT was based on mean solar time. UTC is based on a definition of the second that is nearly a million times more accurate ... based on a quantum resonance within a caesium atom ...

... UTC is established by the Bureau International des Poids et Mesures (BIPM) based on an a aggregate of data from timing laboratories throughout the world, and from input from the International Earth Rotation Service (IERS).

"Computerphile" gives a really good summary:

Time Standards

Note that these are NOT time zones!

Solar Time

  • Time set based on the local position of the sun.
  • E.g. sundial time.
  • Day lengths vary depending on season - effects accumulate.

UT1

  • Based on the Earth's rotation, from which the mean solar day is derived.
  • Modern continuation of GMT.

TAI

  • Atomic clock based standard based on the average of hundres of atmoic clocks throught the world.
  • Basis for UTC.
  • Never adjusted and always continuous. I.e., no leap seconds.

UTC

  • Based on TAI with leap seconds added to keep it within 0.9 seconds of UT1.
  • Modified to be related to UT1 using leap seconds. UTC ticks with the same frequency as TAI - very accurate and stable - but is phase aligned in jumps of leap-seconds to UT1
  • Related to UT1 so that the "wall clock" relates to the human day - i.e., when it is day and night.
  • Not continuous because of leap seconds.

Unix Epoch

  • A.k.a. just the "Epoch".
  • Seconds elapsed since 00:00:00 UTC on January 1, 1970

Time Zones & Daylight Saving Hours

The world uses UTC. By agreement we've all said we'll use UTC to agree on the same standard of time. The added complexity to the mix is time zones. At a UTC time of X in some parts of the world it will be day and in others it will be night, so in regions of the world the UTC time is offset so that 12pm is roughly what we would perceive as midday in terms of sun position.

For historical reasons UTC+0 is GMT - the mean solar time at the Royal Observatory in Greenwich. The following is an image of the world timezones as found on Wikipedia.

From the above, we can see that if we lived in, for example, Malta, we would use UTC+1 as our time base. We can also see that timezones are as much political as they are geographic.

To make life even more complicated there are daylight saving hours to consider. In countries where the potion of the dat that is light moves depending on season an offset of +/- 1 hour (or fractional or multiple) can be applied to the time. This is purely a human thing so that we get more of the "day" time in the light!

Figuring out your machine's local time zone can be suprisingly difficult. The same authot's first blog about the problems with Python and timezones is a good read too.

Of particular note is the TZ Database.

Using Dates & Times in Python

The referenced articles by Julien Danjou and Armin Ronacher both make the following very poinient point: Python datetime objects, by default are not timezone aware!. This can be read directly from the Python docs, but wasn't something I'd taken enough notice of until reading those articles!

An aware object has sufficient knowledge of applicable algorithmic and political time adjustments, such as time zone and daylight saving time information, to locate itself relative to other aware objects. An aware object is used to represent a specific moment in time that is not open to interpretation.

...

For applications requiring aware objects, datetime and time objects have an optional time zone information attribute, tzinfo, that can be set to an instance of a subclass of the abstract tzinfo class. These tzinfo objects capture information about the offset from UTC time, the time zone name, and whether Daylight Saving Time is in effect.

Thus, when using dates/time in Python always use aware objects so that there is no ambiguity in what time is being represented and always store time in UTC: An aware current UTC datetime can be obtained by calling datetime.now(pytz.utc). Why do we want to do this? As Danjou points out, without this the datetime object will have no timezone information, so later on in the code, can we be sure it is representing a time in UTC? There would be no way to tell! He suggests treating any datetime without a timezone as a bug!

Never use unware datetime objects. Always make sure you get timezone aware objects and even better objects using UTC with the timezone information set to UTC: datetime.now(pytz.utc)!

As Danjou recommends, as well as using aware datetime objects you should also store and retreive dates/times in ISO 8601 format by using datetime.datetime.isoformat().

To store and retreive date/time use ISO 8601 format, supported by the Python iso8601 package.

Examples

Get The Current Time as UTC

>>> import datetime
>>> import pytz
>>> datetime.datetime.now(pytz.utc)
datetime.datetime(2018, 3, 18, 12, 36, 52, 315868, tzinfo=<UTC>)

Output Current Time In ISO 8601 Format

>>> import datetime
>>> import pytz
>>> datetime.datetime.now(pytz.utc).isoformat()
'2018-03-18T12:41:07.591469+00:00'

Input Current Time In ISO 8601 Format

>>> import datetime
>>> import pytz
>>> import iso8601
>>> a = datetime.datetime.now(pytz.utc).isoformat()
>>> a
'2018-03-19T07:53:48.225000+00:00'
>>> iso8601.parse_date(a)
datetime.datetime(2018, 3, 19, 7, 53, 48, 225000, tzinfo=<FixedOffset '+00:00' datetime.timedelta(0)>)

Convert Local Time To UTC

Copied verbatim from this SO answer.

import pytz, datetime
local = pytz.timezone ("America/Los_Angeles") # See the TZ Database...
naive = datetime.datetime.strptime ("2001-2-3 10:11:12", "%Y-%m-%d %H:%M:%S")
local_dt = local.localize(naive, is_dst=None)
utc_dt = local_dt.astimezone(pytz.utc)
utc_dt.strftime ("%Y-%m-%d %H:%M:%S")

Convert DateTime To Epoch

Summarised from here.

(datetime.datetime(year,month,day[,hours[,mins[,secs[,usecs[,tzinfo]]]]]) - datetime.datetime(1970,1,1)).total_seconds() # Py 2
datetime.datetime(year,month,day[,hours[,mins[,secs[,usecs[,tzinfo]]]]]).strftime('%s') # Py 3