Pretty Print Relative Dates in 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;
name | birthday | age |
---|---|---|
Lev | 1988-06-09 | 31 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 bluesky, mastodon, or via email.
Check out some more stuff to read down below.
Most popular posts this month
- Now What?
- Setting up ANTLR4 on Windows
- SQLite DB Migrations with PRAGMA user_version
- Meritocracy?
- Possible Plagiarism Made me Cringe
Recent Favorite Blog Posts
This is a collection of the last 8 posts that I bookmarked.
- The Rise of Bluesky from Communications of the ACM
- Useful Bluesky Tools from Robb Knight • Posts • Atom Feed
- Re: Bluesky from Colin Devroe
- From the Red Hell to the Sky of Blue from Straphanger
- We don’t need to use what we make from Derek Sivers blog
- Ubuntu Summit 2024: A joyful experience filled with sorrow from Planet KDE | English
- Sabotage from jwz
- What if My Tribe Is Wrong? from Armin Ronacher's Thoughts and Writings
Articles from blogs I follow around the net
Storing times for human events
I've worked on various event websites in the past, and one of the unintuitively difficult problems that inevitably comes up is the best way to store the time that an event is happening. Based on that past experience, here's my current recommendati…
via Simon Willison's Weblog: Entries November 27, 2024Nothing is Something
There’s a post on htmx.org about why htmx wasn’t the right fit for a particular project (which is dope, we need more websites that admit their thing might not be the right thing all the time). The bit on AI being unfamiliar with their tool choice piqued my…
via Jim Nielsen’s Blog November 27, 2024Ella’s First Website
ULTRA PROUD DAD MOMENT: Ella made her first website! Melissa and I woke up on Saturday morning to our goofy 6-year-old daughter entering our bedroom making this obnoxious sound. It was impressively annoying, especially considering she hasn’t seen Dumb and…
via Blog – Brad Frost November 27, 2024Generated by openring