DEV Community

Ali Sherief
Ali Sherief

Posted on

Digesting Python Date and Time at Lightspeed ⚡

Welcome to the second post in my lightspeed python series! Today I'm going to talk about the datetime, timedelta, timezone, date and time modules which continue to confuse a lot of programmers who get started at the language if only because there are a ton of modules available.

Some exotic oddities related to dates that are useful only to chronological wizards won't be discussed here. In particular, we won't look into different calendar systems or leap seconds. As far as I know, these may not even be supported in the current implementation (but feel free to call me out on this. Leap days are supported and work properly in Python).

Also this will only cover Python 3 as Python 2 is nearing the end of its life.

So, let's get to speed.

Quick reminder: In this post I'm going to use the term 'remainder' a few times. In case you do not know what it means, it's when you divide two numbers (9 / 2 for example) and get a number back (1 in the example).

Some quick hints you're probably looking for:

from datetime import datetime, timezone, timedelta, date, time
datetime.now()
#output: The current day and time
datetime.now(timezone.utc)
#output: The current day and time in the UTC timezone

# Assuming now() is April 20, 2019 01:03:58 PM
# Don't like leading zeros? try %-[letter] on Unix
# or %#[letter] on Windows to get rid of it.
"{:%d/%m/%y %H:%M:%S}".format(datetime.now())
#output: "20/4/19 13:03:58"
"{:%B %d, %Y}".format(datetime.now())
#output: "April 20, 2019"
"{:%a %b %d, %I:%M:%S %p}".format(datetime.now())
#output: "Sat Apr 20, 01:03:58 PM"
"{:%A, %B %d, %Y}".format(datetime.now())
#output: "Saturday, April 20, 2019"
"{:%m/%d/%Y}".format(datetime.now())
#output: "04/20/2019"

datetime(2019, 5, 18, 15, 17, 8, 132263).isoformat()
#output: "2019-05-18T15:17:08.132263"
datetime(2019, 5, 18, 15, 17, tzinfo=timezone.utc).isoformat()
#output: "2019-05-18T15:17:00+00:00"

# And who could forget:
"{:%Y-%m-%d}".format(datetime.now())
#output: "2019-04-20"

Enter fullscreen mode Exit fullscreen mode

xkcd: ISO 8601

timedelta

Timedeltas can represent date with days between -999999999 and 999999999 (which translates to between years -2739726 and 2739726. timedeltas can be as small as 1 microsecond, but don't expect your system clocks to be that accurate! (You don't fetch the current time with timedelta, anyway.

  • You can quickly instantiate a timedelta object by supplying the following arguments: (days, seconds, microseconds).

    • For more flexibility we have the keyword arguments days, seconds, microseconds, milliseconds, minutes, hours, and weeks.
  • With two timedelta objects, we can add and subtract them and the result will be another timedelta. Note that we cannot multiply, divide or take their remainder and expect to get a timedelta.

    • We can multiply, divide and take the remainder of a timedelta and a number, and the result will be another timedelta.
    • We can also do the above three operations on two timedelta objects, but instead of getting a timedelta, we'll get a number.

Numerical arguments in all of the above can be floats or ints, Python doesn't care which.

The timedelta class adjusts fields for you

Say you call constructor timetelta(milliseconds=1.5/1000). That is equal to timedelta(microseconds=2). All of the fields are normalized to be whole numbers.

datetime

Has the following fields: year, month, day, hour, minute, second, microsecond, tzinfo (a fancy name for timezone) and fold (see the timzone section for more info about this). Each of them can be replace()d.

The difference between this and timedelta is this class is for storing a real time of the day, instead of an offset or difference. A datetime can represent years from 1 to 9999.

It is possible to combine a date and time object into a datetime using the (datetime object).combine(date, time, tzinfo=self.tzinfo) method.

As of Python 3.8, datetime.fromisocalendar() can be used to turn ISO calendar dates to datetime objects.

Can be added or subtracted to a timedelta to get a datetime. Two datetime objects subtracted makes a timedelta.

datetime, date and time all have a strftime method that formats the date into a string. The complete list of formatting codes is out of the scope of this post, and you can find them in the Python docs. (datetime also a strptime method that turns said string back into a datetime object only.)

date

Has the following fields: year, month, day. Each of them can be replace()d.

As of Python 3.8, date.fromisocalendar() can be used to turn ISO calendar dates to date objects.

Can be added or subtracted to a timedelta to get a date. Two date objects subtracted makes a timedelta.

time

Has the following fields: hour, minute, second, microsecond, tzinfo (a fancy name for timezone) and fold (see the timezone section for more info about this). Each of them can be replace()d. It has a resolution of 1 microsecond, and it can be formatted with isoformat() as well as with strftime().

timezone

Spoiler alert: While it's nice that we get the implementation of timezones in Python, it doesn't actually define any timezones. So unless someone comes forward and subclass tzinfo (the superclass of timezone), the following theoretical section might become boring for some people and might be skipped over.

Python doesn't really implement well timezones. According to the documentation, tzinfo is just an abstract class meant to be implemented by the user.

I'd recommend the third party pytz module for dealing with timezones.

This answer was for python 2, but it also applies to python 3. Note: timezone is a subclass of tzinfo.

No worries, I can just make a PyPI package that will resolve this apparent lack of functionality, right? Well... that's a subject of another dev.to post. 😆

A timezone object allows datetime and time objects to be correct for other users' timezones. This is particularly important if you are building an app where multiple users need to communicate internationally. It used to have a granularity of minutes, but as of Python 3.7, it is less than that (possibly microsecond granularity but this is pure speculation).

Whereas datetime and time support timezones, a date does not support timezones. It doesn't need to, because it has no hours field.

To quickly turn off timezones for a datetime or time, call [your_datetime_object.replace](tzinfo=NULL). Likewise, to turn them on, call [your_datetime_object.replace](tzinfo=your_tzinfo_object).

An occasional problem is how will we recognize a time that has repeated itself, most likely for the next hour or two, because daylight savings just began or ended (or if some county decides to change it's UTC offset, like Sudan in 2017 to give an example, or Samoa in 2011 for a more extreme case). If you are using Python 3.6 or later, datetime and time objects have the fold field that can solve the ambiguity. This field is either 0 or 1. The value 0 represents the earlier of the two moments with the same wall time representation, and 1 represents the later one.

fold is also a keyword argument in the datetime constructor. It defaults to 0.

At any rate, when two time or datetime objects are compared, a copy of those objects are made and all the factors above are normalized into the hour and minute, before testing whether they are greater or less than each other.

Warning: If your Python version is older than 3.3, equality comparisons between a datetime with a timezone and one without a timezone will raise TypeError. The same holds for time objects. Since Python 3.3, these objects will never be equal. Less-than and greater-than comparisons still raise TypeError though.

Should I use local or timezone-aware datetimes?

Stick with local times.

It may be tempting to write everything in UTC and just forget everything else, but UTC is not a panacea.

Time is very hard to get right. There can be little edge cases which mess up your application. Worse, since it's time, it happens in real time and you probably haven't finished wrapping your head around the little details of the latest time changes by the time your users start filing bugs in your application which is not reporting the correct time for them (I can't blame you, I had to wait a couple days before Red Hat updated the system tzdata files too).

Fortunately, the vast majority of developers will make applications for users in their own local time, where responsibility to keep the time straight shifts to the operating system's clock. So use that.

This article is an excellent read about these issues.

TL;DR

You should use timedelta and datetime classes unless you have special reasons to use something else.

If you see any glaring mistakes in this post, be sure to notify me so I can fix them.

Top comments (0)