Don't worry, it's ... Probably Fine

The unexpected joy of small libraries

03 Nov 2019

nablopomo19 java

I recently picked up an idea from some stuff I’m been doing as part of Democracy Club - we’re interested in knowing when the lists of candidates should be published given the date of an election.

To solve this problem, I built a small python library using the data library pandas for calendar arithmetic, because the solution is tied to calculating working days between dates.

This seemed too interesting to leave alone so I decided to have a crack at these calculations myself!

A well-defined problem statement

I wanted to create a small library that gives a answer to the following question

Given a starting date, what is the date N working days later in the UK?

Simples.

Apart from the fact I started with the wrong API and tried to calculate the working days between two dates first.

Well, everyone makes mistakes.

Start small and build up

The easiest place to start was weekends. Weekends don’t count towards working days, easy peasy.

private boolean isAWeekDay(LocalDate date) {
    return !(date.getDayOfWeek() == SATURDAY || date.getDayOfWeek() == SUNDAY);
}

The next thing on my list to tackle was Christmas Day/Boxing Day, which shouldn’t be too problematic.

Oh ho ho, how little I knew.

Tackle the edge-cases one by one

In the UK we take a day “in lieu” when a fixed-date public holiday like Christmas falls on the weekend (or God forbid, another public holiday). This is traditionally the next working day and in almost all cases is a Monday.

But what if we have two public holidays consecutively, like Christmas Day and Boxing Day (26th December)? We shuffle everything along.

If Christmas Day falls on Saturday or Sunday, then Boxing Day will always be the following Tuesday because Christmas Day’s holiday “in lieu” bumps Boxing Day along.

private boolean isBoxingDay(LocalDate date) {
    if (date.getMonth() != Month.DECEMBER) {
        return false;
    }

    boolean christmasDuringTheWeek = date.getDayOfMonth() == 26 && !isTheWeekend(date);
    boolean christmasOnSunday = date.getDayOfMonth() == 27 && date.getDayOfWeek() == TUESDAY;
    boolean christmasOnSaturday = date.getDayOfMonth() == 28 && date.getDayOfWeek() == TUESDAY;
    boolean christmasOnFriday = date.getDayOfMonth() == 28 && date.getDayOfWeek() == MONDAY;

    return christmasDuringTheWeek
            || christmasOnSunday
            || christmasOnSaturday
            || christmasOnFriday;
}

I genuinely find this piece of code annoying, because I’ve yet to think of a way to simplify it other than encoding the four cases.

Think like a tester

What would result would you get if you tried to calculate 10 working days after 1st January, 0 AD?

Apparently, the 18th of January 0AD!

This answer does not make sense, for several reasons.

  • The Gregorian calendar, which we now use, didn’t exist until the 16th century.
  • Weeks as we know them now didn’t exist.
  • New Years Day as a Bank Holiday didn’t exist.

The sensible thing to do would be to set boundaries on input dates and return errors for requests outside those boundaries.

But, reader, it got worse.

Know when to stop digging

The wider we cast the net of acceptable inputs, the more edge-cases that we need to handle.

  • Public holidays that didn’t come into effect until specific years.
  • Labour laws that govern what counts as a “working day”
  • Public holidays that changed their dates.

Sometimes we just need to declare bankruptcy on something and restrict to a much smaller domain to still be useful.

The (still unreleased) version that I’d coded up has a lower bound of “0000-01-01” and upper bound of “2099-01-01”. This lower bound is wrong, as explained previously, but what value makes sense?

I will probably opt for “2000-01-01”.

This is almost 20 years in the past (tears silently flow down my face, remembering the halcyon days of my youth), which is close enough to be simple and far enough back to be useful.

In the real world, I would user-test this and find out how far users would actually need to go back to calculate these numbers.

Take pride in what you do

This bit of code is by no means perfect. It’s not even right.

However, I am pleased with the way I built it out. Starting with the small cases, nailing down the various edge cases, refactoring as I went, working in small commits.

I can thoroughly recommend coding up what appears to be a small problem domain and experiencing the unexpected joy of small libraries. I’ve learned far more than I wanted to about calendar arithmetic and managed to sharpen my tool-set along the way.


November is National Blog Posting Month, or NaBloPoMo. I’ll be endeavouring to write one blog post per day in the month of November 2019 - some short and sweet, others long and boring.