Home > Back-end >  Is there a built in way to normalize a duration within Luxon?
Is there a built in way to normalize a duration within Luxon?

Time:12-15

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?

CodePudding user response:

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>

CodePudding user response:

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.

  • Related