Digesting Python Date and Time at Lightspeed ⚡
Tags: python, codenewbie
Published: 2019-12-10 20:00:01

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:

```python
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"

```

![xkcd: ISO 8601](https://imgs.xkcd.com/comics/iso_8601.png "ISO 8601 was published on 06/05/88 and most recently amended on 12/01/04.")

# 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](https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior). (`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.

{% stackoverflow 46053159 %}

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. :satisfied:

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](https://zachholman.com/talk/utc-is-enough-for-everyone-right) 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.