Search code examples
luxon

Luxon: faster calculation of fractional day


I need to calculate the fractional day: the fraction of the day that has passed since midnight. I also need an arbitrary time's year and month. These need to be in the context of UTC time. My app uses Luxon, so I used the following to calculate them, starting from DateTime.now() as an arbitrary example:

const luxonNow = DateTime.now();
const gt = luxonNow.setZone('utc');
const luxonY = gt.year;
const luxonM = gt.month;
const luxonMidnight = gt.startOf('day');
// Create an Interval beginning at midnight, ending now
// Find the decimal hours that have passed. Divide by 24 to find the fractional day passed
const luxonFrac = Interval.fromDateTimes(luxonMidnight, gt).length('hours') / 24;
luxonT = gt.day + luxonFrac;

This is in an area of code where performance is important. The code is fast: benchmarks show it takes anywhere up to 0.3ms, averaging 0.1ms.

Can I make it yet faster?


Solution

  • The main bottlenecks are .setZone and Interval.fromDateTimes().length().

    .setZone doesn't have a single bottleneck that can be easily pointed out as a culprit. It looks like, when .setZone('utc') is called for the first time, Luxon creates a singleton UTC instance here that is then cached. Creating it for the first time seems to slow down .setZone by about 30%.

    Interval.fromDateTimes().length() need to wait for the Intl API to return results here so that the correct time zone can be defined during the creation of the DateTime.

    The native Date object supports conversion to UTC. Since this need is heavy on math and light on the need to define the local time zone, the native Date object can be used to achieve the same results:

    const dtNow = luxonNow.toJSDate();
    // Get UTC version of date
    const utc = new Date(
      dtNow.getUTCFullYear(),
      dtNow.getUTCMonth(),
      dtNow.getUTCDate(),
      dtNow.getUTCHours(),
      dtNow.getUTCMinutes(),
      dtNow.getUTCSeconds(),
      dtNow.getUTCMilliseconds()
    );
    const dtY = utc.getFullYear();
    // Date months are 0-11
    const dtM = utc.getMonth() + 1;
    // For midnight:
    // 1: Set to UTC now
    const dtMidnight = new Date(utc.getTime());
    // 2: Set hours, minutes, seconds, milliseconds to 0 to obtain UTC midnight
    dtMidnight.setHours(0, 0, 0, 0);
    // 1 hour is 3.6e6 milliseconds.
    // Milliseconds / 3.6e6 = equivalent hours.
    // Equivalent hours / 24 = fractional day.
    // Formula is ms/3.6e6/24. Slightly more efficient: ms/(3.6e6*24=86400000)
    const fracDay = (utc.getTime() - dtMidnight.getTime()) / 86400000;
    dtT = utc.getDate() + fracDay;
    

    Benchmarks show that this method is anywhere up to 0.2ms (approaching Luxon's max), averaging just 0.008ms. It is 90-95% faster than Luxon.