Working Days Calculator

Ministry of Justice Digital & Technology
Just Tech
Published in
4 min readJul 9, 2019

by Rob Nichols (Software Development Profession)

Photo by Estée Janssens on Unsplash

Working out how many working days there are between two dates requires not only knowledge of when weekends fall, but also when bank holidays occur. As bank holidays are moved or created to coincide with key national events (Royal Weddings for example), a simple calculation or static data lookup is not a good long term solution.

In this article, a solution is described that gathers bank holiday data from a GOV-UK API, and uses that data to calculate working day intervals.

A Ruby Solution

There are a number of gems that will do working day calculations, but to keep them up-to-date they require a list of bank holidays to be passed into them. They make the calculation simpler, but a system to gather bank holiday data is still required.

The API

There is a GOV-UK API that returns up-to-date bank holiday data.

Gem selection

There were three main gems considered:

Of these, business_time was last update two years ago. The other two gems both appear to be more regularly maintained. Both business_time and working_hours monkey patch the core Ruby time objects Time, Date and DateTime. As the scope of usage for this project was limited (there was currently only one calculation to be made), using a gem that didn’t monkey patch core objects was a better fit. That left the business gem — and this was selected.

Retrieving the data

The first task was to create a service that would retrieve the data from the API. The solution was BankHolidayRetriever, which called the API via Net::HTTP.get_response, parsed the resulting JSON and extracted the array of dates from the required section of the data.

Storing the data

The next task was to store the data to locally cache it. After some consideration as to whether to store the data in memory, or in a memcache, it was decided to use the simplest solution of storing the data in the database via an ActiveRecord Model.

The model was created with a generator:

rails g model BankHoliday dates:text

It was then modified to add serialization of dates to store the array coming from BankHolidayRetriever.dates, some simple validation, and a callback that populated dates on creation.

A class method BankHoliday.dates was added that would retrieve the latest updated instance of the model and return its dates, or create an instance if none exists.

Also added to the dates class method call, was a call out to a worker that would update the current data. In this way the bank holiday data will be updated as it is used.

The update worker

As Sidekiq was available in the current project, a worker was added that will update the Bank Holiday data. Its first task is to see when the last update occurred. If the last update is less than a certain interval old (currently two days), the worker would just close without doing an update.

If an update is required, the worker uses a new instance of BankHoliday to gather the latest data. It then compares that data with the latest stored data. If the two match, the “updated at” date of the last persisted BankHoliday instance is updated (via touch).

If the latest stored data does not match the retrieved data, then the new instance is saved and it becomes the latest stored data in the database.

Note that failure to retrieve the data or save it to the database, raises an exception. When this happens within a worker, this will cause the worker to be put into the retry queue for it to be run again later. In this way short term API and or data errors can be handled without adding additional code.

The Calculator

With the system now able to return an up-to-date array of bank holiday dates, it was a straightforward task to create a working day calculator using the business gem.

As this gem accepts the dates as strings, there was no need to convert them to dates first, which simplifies the code.

At the time of writing all that was required was a way of returning a date a number of days ahead of today. As the business gem provides a number of working day calculations, it will be fairly simple to extend the functionality as required.

The end result

With these changes in place, within the app the date ten working days from now can be returned with the call :

WorkingDayCalculator.working_days_from_now(10)

Making that call will trigger a worker that will check that the data is up-to-date for the next call.

The complete set of code with specs, within the context it was used can be seen in the Pull Request.

If you enjoyed this article, please feel free to hit the👏 clap button and leave a response below. You also can follow us on Twitter, read our other blog or check us out on LinkedIn.

If you’d like to come and work with us, please check current vacancies on our job board!

--

--

Ministry of Justice Digital & Technology
Just Tech

We design, build and support user-centred digital and technology services for the justice system.