Calculation of time intervals with Luxon Business Days

Not long ago, in May 2021, I wrote about Luxon’s status as the heir apparent to Moment.js as it went into maintenance mode. Since then, creators of plugins for Moment.js have been scrambling to migrate their libraries to Luxon, as more and more organizations adopt it as their de facto JS date library.

One of these plugins is Luxon Business Days. As the name suggests, it is a business day calculation and manipulation library based on its forerunner Moment Business Days.

In this tutorial, we’ll use Luxon Business Days to access a chart of daily stock prices without having to scroll through each item ourselves to find a specific day.

The task explained

Suppose you have an object storing a daily time series and associated stock prices:

const DAY_TIMESERIES = {
  close: [
    174.92, 
    172, 
    172.17, 
    172.19, 
    175.08, 
    175.53, 
    172.19, 
    173.07, 
    173.07, 
    169.8, 
    166.23, 
    164.51, 
    162.41, 
    161.62, 
    159.78, 
    159.69, 
    159.22, 
    170.33, 
    174.78, 
    174.61, 
    175.84, 
    172.9, 
    172.39, 
    171.66, 
    174.83, 
    176.28, 
    172.12, 
    168.64, 
    168.88, 
    172.79, 
    172.55, 
    168.88, 
    167.3, 
    167.3, 
    169.32, 
    170.07, 
    162.74
  ],
  timestamps: [
    "2022-01-05T00:00:00",
    "2022-01-06T00:00:00",
    "2022-01-07T00:00:00",
    "2022-01-10T00:00:00",
    "2022-01-11T00:00:00",
    "2022-01-12T00:00:00",
    "2022-01-13T00:00:00",
    "2022-01-14T00:00:00",
    "2022-01-17T00:00:00",
    "2022-01-18T00:00:00",
    "2022-01-19T00:00:00",
    "2022-01-20T00:00:00",
    "2022-01-21T00:00:00",
    "2022-01-24T00:00:00",
    "2022-01-25T00:00:00",
    "2022-01-26T00:00:00",
    "2022-01-27T00:00:00",
    "2022-01-28T00:00:00",
    "2022-01-31T00:00:00",
    "2022-02-01T00:00:00",
    "2022-02-02T00:00:00",
    "2022-02-03T00:00:00",
    "2022-02-04T00:00:00",
    "2022-02-07T00:00:00",
    "2022-02-08T00:00:00",
    "2022-02-09T00:00:00",
    "2022-02-10T00:00:00",
    "2022-02-11T00:00:00",
    "2022-02-14T00:00:00",
    "2022-02-15T00:00:00",
    "2022-02-16T00:00:00",
    "2022-02-17T00:00:00",
    "2022-02-18T00:00:00",
    "2022-02-21T00:00:00",
    "2022-02-22T00:00:00",
    "2022-02-23T00:00:00",
    "2022-02-24T00:00:00"
  ]
};

The beauty of parallel networks at Luxon

For this tutorial, we’ll keep the length of the time series brief and cap it at around two months. In a real world scenario, the time series could span years, so it’s nice to avoid iterating on both the timestamps and close tables. Fortunately, we don’t have to. To understand why, let’s imagine we were looking for the stock price on February 21, 2022. To do this, we must first find what timestamps the element contained the date we are looking for. From there we could recover the price of the close array using the same element index, because both the timestamps and close are Parallel networks. Also known as Array structure (SoA)it is multiple arrays of the same size such as nth element of each array is related so that all nth the elements together represent an object or entity. You’ll find them all over application code, but an example of a parallel array consists of two arrays that represent X and there coordinates of not points.

Therefore, finding the index of one element automatically directs us to the associated data in the other array.

Lily: Time zone conversion with Luxon and JavaScript

Calculating the number of days between two dates using Luxon

One of the things that Luxon is extremely good at is calculating the length of time between dates. In fact, it would only take three lines of code to find the number of days between the first timestamp and the February 21, 2022 target date we are looking for:

const firstClosingPriceDate = DateTime.fromISO(DAY_TIMESERIES.timestamps[0]); //"2022-01-05T00:00:00"
const targetPriceCalcDate = DateTime.fromISO("2022-02-21T00:00:00");

let diffInDays = targetPriceCalcDate.diff(firstClosingPriceDate, 'days').days;  //47

the difference() returns a Duration object, so we need to access the days property if we only want days and no other intervals, like weeks or months.

Calculation of the number of working days between two dates

While the result above (47) is absolutely correct, there is one problem: daily stock quotes are only provided on business days! If you look closely at the timestamps, you will notice that there is a two-day interval every five days. It just so happens that the Moment Business Days library has the businessDiff() method, which conveniently skips non-working days in its calculations. Of course, since we’re using Luxon, it makes sense to pair it with the Luxon Business Days plugin.

This is probably a good time to mention that the Luxon Business Days plugin is not a full port of its Moment.js counterpart. And, as fate would have it, the businessDiff() method is conspicuously missing from the documentation, global DateTime namespace object, and source code. Does that mean our plan is dead in the water? Far from there. We can port on the Moment.js businessDiff() method to our own project. Of course, this won’t work immediately with Luxon, but, thanks to Luxon’s For Moment Users page, we can easily replace the Luxon equivalents with the Moment method invocations. Introducing the brand new Luxon compatible businessDiff() method:

DateTime.prototype.businessDiff = function(d2, relative) {
  var d1 = this;
  var positive = d1 >= d2;
  var start = d1 < d2 ? d1 : d2;
  var end = d2 > d1 ? d2 : d1;
  var daysBetween = 0;

  if (start.hasSame(end, 'day')) {
    return daysBetween;
  }

  while (start.startOf('day') < end.startOf('day')) {
    if (start.isBusinessDay()) {
      daysBetween += 1;
    }
    start = start.plus({ days: 1 })
  }

  if (!end.isBusinessDay()) {
    daysBetween -= 1;
  }

  if (relative) {
    return (positive ? daysBetween : -daysBetween);
  }

  return daysBetween;
};

the businessDiff() the method returns 33which is equivalent to the index to retrieve price data:

let diffInDays = targetPriceCalcDate.businessDiff(firstClosingPriceDate); // 33

document.write(pricesFromTargetPriceCalcDate[diffInDays]); //172.79
document.write(
  DateTime.fromISO(daysFromTargetPriceCalcDate[diffInDays]).toLocaleString(DateTime.DATE_MED)
); //Feb 15, 2022

Lily: Parsing Dates and Times Using Luxon

The demo

In the codepen.io demo, the businessDiff() method is used to decide close and timestamps paintings the day after targetPriceCalcDateso that we can find the first day that the closing price of the stock meets or exceeds the target price, using the native Array.findIndex() method:

const targetPriceAchievedIndex 
  = pricesFromTargetPriceCalcDate.findIndex(price => price >= targetPrice) + 1;

Conclusion

Not a fan of looping? Why not let someone else handle the burden of array iteration for you? Through methods like difference(), businessDiff(), to find()and findIndex()you can forget all about loops and code in a more method-oriented way, closer to functional programming than your typical JavaScript.

Learn more about JavaScript programming and web development tutorials.

Comments are closed.