Home > Software design >  Migrating from Moment to Luxon - (t) => moment(t).format('hh:mm') equivalent
Migrating from Moment to Luxon - (t) => moment(t).format('hh:mm') equivalent

Time:03-23

I'm migrating an old piece of code from Moment to Luxon and bumped into the function below:

export const TIME_FORMATTER = {
  HOUR: (t) => moment(t).format('hh:mm'),
  DAY: (t) => moment(t).format('M/DD'),
  HOUR_PERIOD: (t) => moment(t).format('hh:mm A'),
  DAY_HOUR_PERIOD: (t) => moment(t).format('hh:mm A M/DD'),
  SEC: (t) => moment(t).format('HH:mm:ss')
};

which gets called as follows:

export const getFormatterByTimeInterval = (timeInterval: number) => {
  if (timeInterval <= 240) {
    return TIME_FORMATTER.DAY_HOUR_PERIOD;
  }
  else if (timeInterval > 480) {
    return TIME_FORMATTER.HOUR_PERIOD;
  }
  else {
    return TIME_FORMATTER.DAY;
  }
}

My initial thought was to use the same call format with Luxon:


const TIME_FORMATTER_LUXON = {
  HOUR: (t) => DateTime.local(t).toFormat("hh:mm"),
  DAY: (t) => DateTime.local(t).toFormat("M/d"),
  HOUR_PERIOD: (t) => DateTime.local(t).toFormat("t"),
  DAY_HOUR_PERIOD: (t) => DateTime.local(t).toFormat("t M/d"),
  SEC: (t) => DateTime.local(t).toFormat("HH:mm:ss")
};

This gets called on another file const formatter = getFormatterByTimeInterval(timeInterval); but it's returning Invalid DateTime

Is this possible with Luxon? What can be the issue?

CodePudding user response:

It seems to me like your old code might have a few issues, so I want to at least address them before responding to your question:

The only function signature for moment which accepts a number argument is Unix Timestamp (milliseconds):

Similar to new Date(Number), you can create a moment by passing an integer value representing the number of milliseconds since the Unix Epoch (Jan 1 1970 12AM UTC).

const day = moment(1318781876406);

Note: ECMAScript calls this a "Time Value"

I'm not sure what the timeInterval parameter represents in your formatter resolver function:

function getFormatterByTimeInterval (timeInterval: number) {/* ... */}

but I'll ignore that for now. With that out of the way, let's look at the conditionals:

export const getFormatterByTimeInterval = (timeInterval: number) => {
  // This formats as time and date information
  if (timeInterval > 240) {
    return TIME_FORMATTER.DAY_HOUR_PERIOD;
  }
  // This formats as time-only information
  // Also, this will never match because any `timeInterval` value which is
  // greater than 480 is also greater than 240, so the first conditional above
  // will match and return before reaching this one
  else if (timeInterval > 480) {
    return TIME_FORMATTER.HOUR_PERIOD;
  }
  // Shorter than either of the above
  // This formats as date-only information
  else {
    return TIME_FORMATTER.DAY;
  }
}

I'm not sure how your program is supposed to work, but that doesn't seem like the intended behavior to me.


In regard to your question of conversion:

The Luxon equivalent for instantiation of a DateTime from a milliseconds number type is DateTime.fromMillis(). (You don't need to worry about using the local method because luxon uses your system's local timezone by default.)

In regard to the equivalent formatting options, they would be:

moment luxon
hh:mm hh:mm
M/DD L/dd
hh:mm A hh:mm a
hh:mm A M/DD hh:mm a L/dd
HH:mm:ss HH:mm:ss

Expressed as code, it looks like this:

import {DateTime} from 'luxon';

export const TIME_FORMATTER = {
  HOUR: (millis: number) => DateTime.fromMillis(millis).toFormat('hh:mm'),
  DAY: (millis: number) => DateTime.fromMillis(millis).toFormat('L/dd'),
  HOUR_PERIOD: (millis: number) => DateTime.fromMillis(millis).toFormat('hh:mm a'),
  DAY_HOUR_PERIOD: (millis: number) => DateTime.fromMillis(millis).toFormat('hh:mm a L/dd'),
  SEC: (millis: number) => DateTime.fromMillis(millis).toFormat('HH:mm:ss'),
};

Then, a small refactor of your resolver:

type FormatterFn = (millis: number) => string;

export const getFormatterByTimeInterval = (timeInterval: number): FormatterFn => {
  let key: keyof typeof TIME_FORMATTER;
  // I'm still not sure about these conditionals,
  // but this order matches longest duration first
  if (timeInterval > 480) key = 'HOUR_PERIOD';
  else if (timeInterval > 240) key = 'DAY_HOUR_PERIOD';
  else key = 'DAY';
  return TIME_FORMATTER[key];
};

And you can check it like this:

// The time you asked this question
const millis = DateTime.fromISO('2022-03-21T21:02:03Z').toMillis();

// Check each conditional range:
for (const timeInterval of [500, 300, 100]) {
  const formatter = getFormatterByTimeInterval(timeInterval);
  console.log(formatter(millis));
}

Code in the TypeScript Playground

Demo of compiled JavaScript from the TS playground link:

<script type="module">

import {DateTime} from 'https://unpkg.com/[email protected]/src/luxon.js';

export const TIME_FORMATTER = {
    HOUR: (millis) => DateTime.fromMillis(millis).toFormat('hh:mm'),
    DAY: (millis) => DateTime.fromMillis(millis).toFormat('L/dd'),
    HOUR_PERIOD: (millis) => DateTime.fromMillis(millis).toFormat('hh:mm a'),
    DAY_HOUR_PERIOD: (millis) => DateTime.fromMillis(millis).toFormat('hh:mm a L/dd'),
    SEC: (millis) => DateTime.fromMillis(millis).toFormat('HH:mm:ss'),
};
export const getFormatterByTimeInterval = (timeInterval) => {
    let key;
    // I'm still not sure about these conditionals,
    // but this order matches longest duration first
    if (timeInterval > 480)
        key = 'HOUR_PERIOD';
    else if (timeInterval > 240)
        key = 'DAY_HOUR_PERIOD';
    else
        key = 'DAY';
    return TIME_FORMATTER[key];
};
// The time you asked this question
const millis = DateTime.fromISO('2022-03-21T21:02:03Z').toMillis();
// Check each conditional range:
for (const timeInterval of [500, 300, 100]) {
    const formatter = getFormatterByTimeInterval(timeInterval);
    console.log(timeInterval, formatter(millis));
}

</script>

  • Related