Today I came across this issue:

TypeError: can't subtract offset-naive and offset-aware datetimes

I had found a bug in my program where some calculations were not being done correctly because my datetime values were incorrect or more accurately… inconsistent.

I wanted to find the difference between 2 datetime objects. One of the datetime object originates from a datetime string which is passed to a pydantic Model, which is ultimately converted into a datetime object by the pydantic model.

The 2nd datetime object is generated by the function datetime.utcnow() function. At this point I had thought that I was doing the right thing by using utcnow() to generate a UTC time based datetime object…

Note: In this example we use Python 3.10 & Pydantic

from datetime import datetime
from pydantic import BaseModel


class InputTime(BaseModel):
    t: datetime


if __name__ == "__main__":

    # One of my Datetime objects is generated via pydantic
    model = InputTime(**{'t': '2023-04-04T21:13:44.520654Z'}).dict()
    datetime_obj_1 = model['t']
  
    # Get Datetime object of the time now
    datetime_obj_2 = datetime.utcnow()
  
    # Work out the Interval
    difference = datetime_obj_2 - datetime_obj_1

    print(difference)
  

The result would be:

Traceback (most recent call last):
  File "/home/user/PycharmProjects/PythonTestGround/timezones.py", line 17, in <module>
    difference = datetime_obj_2 - datetime_obj_1
TypeError: can't subtract offset-naive and offset-aware datetimes


Offset-aware datetime objects = happy days

I was frustrated and was unsure what the problem would be since they were both datetime objects…

Upon much research, I found out that although both were datetime object, datetime_obj_1 had a timezone set (was offset-aware) and datetime_obj_2 did not (was offset-naive).

The solution was to convert datetime_obj_2 to become an offset aware datetime object by assigning a timezone when the datetime object is generated:

datetime.now(timezone.utc)

Full Example:

from datetime import datetime, timezone
from pydantic import BaseModel


class InputTime(BaseModel):
    t: datetime


if __name__ == "__main__":

    # One of my Datetime objects is generated via pydantic
    model = InputTime(**{'t': '2023-04-04T21:13:44.520654Z'}).dict()
    datetime_obj_1 = model['t']

    # Get Datetime object of the time now
    datetime_obj_2 = datetime.now(timezone.utc)

    # Work out the Interval
    difference = datetime_obj_2 - datetime_obj_1

    print(difference)

Resulting in the calculated time difference:

21:43:56.272346


Conclusion

Here’s A few things I learnt today we it comes to handling time in python programs:

  • When passing datetime values around you program, pass it around as a datetime object - NOT in their string format, unless you really want to…
  • Make sure your datetime object are offset-aware, meaning set the timezone for them
  • Stick to one string format, at least within your program. I use ISO8601, datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%S.%f%z')
  • Only convert your datetime objects to datetime string when that data is being passed out of your program