, dev
Wadu: Again and Again
March 2020, me and my ≈1,500 colleagues from Weta Digital were being sent home to work remotely due to the COVID-19 pandemic.
This also meant no more gym and the need to find a way to do some exercises somewhat regularly.
Surely, this sounded like a good pretext to build a terminal-based app that would remind me of doing specific exercises at specific times, right?
And, surely, setting up reminders that are to be triggered every day at a given time is no big deal, right?
But wait. How about reminders that occur only once every 2 weeks? Or only during the first week of each month?
It quickly became apparent that some more thinking needed to take place and that’s when I stumbled upon the iCalendar (RFC 5545) specification that describes recurrence rule objects, amongst other things.
I even found a popular Python package named dateutil that implements these recurrence rules, however...
... it wouldn’t be fun if it was too easy, or so I thought. And while skimming through the implementation of dateutil, I found some of its bits to be fairly difficult to decypher.
So, “y’know what, bud?”, I asked myself, “the best way to learn about something is to do it yourself”.
But that wasn’t all. The naïve person that I was decided that this wasn't challenging enough: “while y’er at it, make a simpler implementation. And why not a moar optimised one, too. And with a data-oriented approach, y’know?”.
At that time, I had no idea what I was getting into. I simply dived head first into the rabbit hole of recurring date events and what started as a fun exercise quickly turned into hell.
Hell
In retrospective, I can say this probably was the most challenging programming project that I’ve ever worked on. Not due to the code complexity, mind you. It’s all fairly straightforward. No, no, what made it truly difficult was making sense of that RFC 5545 specification.
Trying to implement the specification one piece at a time, without fully grasping the whole picture, was a truly inefficient approach but, at the same time, it also was the only way to build the required knowledge to move forward.
Whenever I’d end up successfully implementing one bit, I’d move onto the next one only to realize that my understanding of the specification felt apart and that my whole approach was thus wrong. Which led me to needing to rethink everything from the beginning. Every. Single. Time.
It really felt hopeless. How many times did I begin seeing the light at the end of the tunnel only to see it collapse right in front of me, forcing me to go all the way back to the starting point? I lost count.
Did I really have to be my usual stubborn masochist self and implement something that no one will ever care about, when I could have spent my time working on cool projects instead?
Wadu
The one thing that finally came to my rescue was stumbling upon the JSCalendar draft from IETF. At last a specification that is clear, well articulated and, most importantly, that doesn’t leave space for ambiguities like the RFC 5545 does.
Because of these ambiguities, different libraries such as dateutil, libical, lib-recur, and others, all advertise themselves to be RFC 5545 compliant—and rightfully so—while at the same time returning inconsistent results depending on how the specification is being interpreted.
So here I stand. With the only open source implementation that I’m aware of that is fully compliant with the JSCalendar draft while covering all the edge cases that I could find, and boasting a relatively straightforward-ish implementation.
I called it wadu and the source code is on GitHub, as always!
Hell, it’s even on pypi so you could go ahead and install it by running pip install wadu
if you too would like to get to play with these sexy curves:
# The 1st Friday of each month, for 10 occurrences.
rule = RecurrenceRule(MONTHLY,
on_week_days=(FRIDAY(1),),
count=10)
for dttm in rule:
print(dttm)
# Every other year on January and February, starting on a given date.
rule = RecurrenceRule(YEARLY,
interval=2,
on_months=(JANUARY, FEBRUARY),
count=10)
start = datetime(1997, 3, 10, hour=9)
for dttm in rule.iterate_from(start):
print(dttm)
Final Notes
If you’re wondering whether I got to implement the terminal-based app that I initially intended to write: of course not ¯\_(ツ)_/¯
And I didn’t end up doing any exercise neither.
― Jesus Fucking Christ. What did we learn, Palmer?
― I don’t know sir.
― I don’t fucking know either. I guess we learned not to do it again. I’m fucked if I know what we did.