Pretty Print Relative Dates in Python

| programming | python |

In this post, we will learn how to “pretty print” relative dates using the dateutil library in Python. Specifically, given a relativedelta object, we will print a string that represents the number of years, months, and days that have passed between two dates.

Introduction

I am working on a project called ManagerManager that helps me keep track of the people I work for. As my team has grown, it has become important to me to have a quick reminder to celebrate milestones such as work anniversaries.

PostgreSQL age Function

PostgreSQL has a useful built-in age function that allows you to get a human readable relative date from a date or datetime column.

For example, If I was in a database somewhere, I could use the age() function to find out how old I am.

select name, birthday, age(birthday) from person;
namebirthdayage
Lev1988-06-0931 years 11 mons 6 days

Abstracting Away the SQL

In the first iteration of this project, I relied on “raw” SQL for all database operations. This made it easy to take advantage of the age() function in PostgreSQL. As the project became more complex, working in SQL got out of hand.

I recently switched to SQLAlchemy to make working with the database simpler. Although SQLAlchemy allows you to call out to native database functions, I didn’t want to have a hard dependency on PostgreSQL for this application, so I started to search for a quick and dirty way to do the same thing in Python.

Exploring Python datetime

My initial investigation led me to the familiar datetime module in Python. This built-in module is great for manipulating date and time objects. It includes a timedelta class which allows you to compare two datetimes at up to microseconds of resolution. This is generally useful, but unfortunately the maximum interval that this class provides is days.

import datetime

dob = datetime.date.fromisoformat('1988-06-09')
today = datetime.date.today()
today - dob

>>> datetime.timedelta(days=11662)

It turns out converting days to years, months, and weeks is much more complicated than I initially thought since you need to account for leap years and other nuances.

The dateutil Library

Luckily, the dateutil library provides several enhancements to the built-in datetime module. Most importantly for this use case, it has the relativedelta class which breaks the timedelta into years, months, days, all the way down to microseconds.

import datetime
from dateutil.relativedelta import relativedelta

relativedelta(today, dob)
dob = datetime.date.fromisoformat('1988-06-09')
today = datetime.date.today()

>>> relativedelta(years=+31, months=+11, days=+6)

The only thing missing from dateutil.relativedelta is a nice way to “pretty print” the result. For example, what if years, months, or days is equal to 0? I don’t want a string that says “0 years, 4 months, and 1 day”.

Ternary Operators to the Rescue

This was the first time that I’ve used a ternary operator in python and it actually feels like a good use case here.

def tenure(self):
    rd = relativedelta(datetime.now(), self.start_date)
    years = f'{rd.years} years, ' if rd.years > 0 else ''
    months = f'{rd.months} months, ' if rd.months > 0 else ''
    days = f'{rd.days} days' if rd.days > 0 else ''
    return f'{years}{months}{days}'

A ternary operator is a shortcut to write an if/else statement. In my opinion, it is useful for simple situations like this. Let’s break this down a little bit.

In the tenure() function described above, the code for determining years is equivalent to the following:

if years > 0:
    years = f'{rd.years} years, '
else:
    years = ''

Or, you could also write it like this:

years = ''

if years > 0:
    years = f'{rd.years} years, '

However, I think the simple one-liner ternary operator makes the entire function a bit cleaner.

Going back to the birthday example, the final output of this function unsurprisingly looks like this:

31 years, 11 months, 6 days

Conclusion

To sum things up, we can use the dateutil library coupled with your choice of if/else syntax to provide a human readable way to show the difference between two dates.

Thank you for reading! Share your thoughts with me on mastodon or via email.

Check out some more stuff to read down below.

Most popular posts this month

Recent Favorite Blog Posts

This is a collection of the last 8 posts that I bookmarked.

Articles from blogs I follow around the net

Script Doctoring

I’ve been having a number of communications problems in my interactions with my doctors at Kaiser lately, and it’s becoming one of those things where the burden and onus entirely is placed upon me to sort out, and that’s exhausting for the actually autist…

via Bix Dot Blog October 22, 2024

Blockchain company Forte acquires games studios, demands secrecy, shuts them down

Sometime in 2023, blockchain firm Forte acquired game studios Phoenix Labs and Rumble Games. However, it would be a year before this came to light, because according to a report from Game Developer, Forte demanded secrecy from employ…

via Web3 is Going Just Great October 22, 2024

Initial explorations of Anthropic's new Computer Use capability

Two big announcements from Anthropic today: a new Claude 3.5 Sonnet model and a new API mode that they are calling computer use. (They also pre-announced Haiku 3.5, but that's not available yet so I'm ignoring it until I can try it out myself.) Comp…

via Simon Willison's Weblog: Entries October 22, 2024

Generated by openring