3

I am migrating from Momentjs to Luxon. I allow users to pick a number of hours that a task will take. When we save this value, I'd like it to normalize. So if a task takes 90 hours, I would prefer if we stored 3 days and 18 hours. I store the ISO Duration String so I'd like to convert PT90H to P3DT18H... of course with any given value.

I have tried Luxon's normalize function, I tried to call it like so:

const duration = Duration.fromObject({ hours: 90 });
duration.normalize().toISO();

However, normalize does not seem to do anything to the result. I still get PT90H. Is there a built in way to do this with Luxon?

2 Answers 2

5

Durtation.normalize() won't add properties to the Duration object so you need to supply the units you want it to normalize to. (This is odd to me, but provides implicit control over outcome).

normalize() – Reduce this Duration to its canonical representation in its current units.

const duration = Duration
  .fromObject({ days: 0, hours: 90 })
  .normalize()
  .toISO();

console.log(duration);
<script src="https://cdnjs.cloudflare.com/ajax/libs/luxon/2.2.0/luxon.min.js"></script>
<script>const { Duration } = luxon;</script>

Alternatively you can use Duration.shiftTo() to explicitly force the duration into specific units.

const duration = Duration
  .fromObject({hours: 90 })
  .shiftTo('days', 'hours')
  .toISO();

console.log(duration);
<script src="https://cdnjs.cloudflare.com/ajax/libs/luxon/2.2.0/luxon.min.js"></script>
<script>const { Duration } = luxon;</script>


If you know you will always want to normalize into all units you could declare a utility to return a Duration object initialized with all units.

function createDuration(duration) {
  return Duration.fromObject(
    Object.assign(
      { years: 0, months: 0, weeks: 0, days: 0, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 },
      duration
    )
  );
}

const duration = createDuration({ hours: 90, minutes: 3004 }).normalize().toISO();

console.log(duration);
<script src="https://cdnjs.cloudflare.com/ajax/libs/luxon/2.2.0/luxon.min.js"></script>
<script>const { Duration } = luxon;</script>

4
  • Thanks for your answer, it is unfortunate that Luxon will not optimize this for me. I don't really want to store PT0H20M to the server, but I really don't want to see PT1200S.
    – nephiw
    Commented Dec 14, 2021 at 15:14
  • Honestly I would just store seconds or milliseconds. Arithmetic conversion is trivial on the front end.
    – pilchard
    Commented Dec 14, 2021 at 15:19
  • 1
    ...but I don't see where you're getting the PT0H20M from, { hours: 0, minutes: 0, seconds: 1200} toISO yields PT20M in the above snippets
    – pilchard
    Commented Dec 14, 2021 at 15:25
  • Good point, I didn't run those values through Luxon and you are right Duration.fromObject({ hours: 0, minutes: 20 }).toISO() does become PT20M not PT0H20M. This will make my calculations simpler, I won't need to filter out 0 valued units.
    – nephiw
    Commented Dec 14, 2021 at 15:34
3

Per @pilchard's answer, I ended up using this function to normalize my duration.

function normalize(duration) {
  const durationObject = duration.shiftTo(
    'years',
    'months',
    'days',
    'hours',
    'minutes',
    'seconds'
  ).toObject();

  // filter out units with 0 values.
  const resultObject = Object.keys(durationObject).reduce((result, key) => {
    const value = durationObject[key];
    return value ? { ...result, [key]: value } : result;
  }, {});

  return Duration.fromObject(resultObject);
}

console.log(normalize(Duration.fromObject({ hours: 90 })));
<script src="https://cdnjs.cloudflare.com/ajax/libs/luxon/2.2.0/luxon.min.js"></script>
<script>const { Duration } = luxon;</script>

[EDIT] The step that filters out 0'ed units is unneeded for my stated goals, the toISO method does this for us. My needs related to making spec's pass, and it seems like it will be better change the specs than waste the CPU cycles normalizing during calculations. Using the above method will make the resulting ISO duration strings normalized units - PT1H30M instead of PT90M.

In the end I chose not to use the 0-unit filtering and just allow that to happen when I convert it to an ISO string.

Not the answer you're looking for? Browse other questions tagged or ask your own question.