question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Duration.normalize() only normalizes on existing keys.

See original GitHub issue

EDIT

This was a misunderstanding on my part. The functionality is such that it only normalizes with respect to the provided keys. I thought that these two would give the same result.

const a = luxon.Duration.fromObject({ milliseconds: 1001 }).normalize();
const b = luxon.Duration.fromObject({ seconds: 0, milliseconds: 1001 }).normalize();

console.log('Test - (a) seconds', a.seconds);             // gives 0
console.log('Test - (a) milliseconds', a.milliseconds);   // gives 1001
console.log('Test - (b) seconds', b.seconds);             // gives 1
console.log('Test - (b) milliseconds', b.milliseconds);   // gives 1

Perhaps the documentation should be more explicit about this?


ORIGINAL

Title: Chaining Duration.plus() with normalize() results in normalize() not doing anything

Specifically, if you set a value to more than it’s normalized max, it will correctly transform it into the other units. However, when using plus(), if the sum of any field goes above it’s normalized value, normalize() doesn’t touch it.

Here are some test cases highlighting this issue.

const luxon = require('luxon');

{
  const a = luxon.Duration.fromObject({ years: 12019 });
  const b = luxon.Duration.fromObject({ months: 13 });
  const c = luxon.Duration.fromObject({ days: 366 });
  const d = luxon.Duration.fromObject({ hours: 25 });
  const e = luxon.Duration.fromObject({ minutes: 61 });
  const f = luxon.Duration.fromObject({ seconds: 61 });
  const g = luxon.Duration.fromObject({ milliseconds: 1001 });
  const h = a.plus(b).plus(c).plus(d).plus(e).plus(f).plus(g).normalize()

  console.log('Test A - years', h.years);               // gives 12021
  console.log('Test A - months', h.months);             // gives 1
  console.log('Test A - days', h.days);                 // gives 7
  console.log('Test A - hours', h.hours);               // gives 2
  console.log('Test A - minutes', h.minutes);           // gives 2
  console.log('Test A - seconds', h.seconds);           // gives 2
  console.log('Test A - milliseconds', h.milliseconds); // gives 1
}
{
  const a = luxon.Duration.fromObject({ months: 6 });
  const b = luxon.Duration.fromObject({ months: 7 });
  const c = a.plus(b).normalize();
  console.log('Test B - years', c.years);               // gives 0
  console.log('Test B - months', c.months);             // gives 13
}
{
  const a = luxon.Duration.fromObject({ days: 200 });
  const b = luxon.Duration.fromObject({ days: 166 });
  const c = a.plus(b).normalize();
  console.log('Test C - years', c.years);               // gives 0
  console.log('Test C - days', c.days);                 // gives 366
}
{
  const a = luxon.Duration.fromObject({ hours: 20 });
  const b = luxon.Duration.fromObject({ hours: 5 });
  const c = a.plus(b).normalize();
  console.log('Test D - days', c.days);                 // gives 0
  console.log('Test D - hours', c.hours);               // gives 25
}
{
  const a = luxon.Duration.fromObject({ minutes: 43 });
  const b = luxon.Duration.fromObject({ minutes: 18 });
  const c = a.plus(b).normalize();
  console.log('Test E - hours', c.hours);               // gives 0
  console.log('Test E - minutes', c.minutes);           // gives 61
}
{
  const a = luxon.Duration.fromObject({ seconds: 43 });
  const b = luxon.Duration.fromObject({ seconds: 18 });
  const c = a.plus(b).normalize();
  console.log('Test F - minutes', c.minutes);           // gives 0
  console.log('Test F - seconds', c.seconds);           // gives 61
}
{
  const a = luxon.Duration.fromObject({ milliseconds: 500 });
  const b = luxon.Duration.fromObject({ milliseconds: 501 });
  const c = a.plus(b).normalize();
  console.log('Test G - seconds', c.seconds);           // gives 0
  console.log('Test G - milliseconds', c.milliseconds); // gives 1001
}
{ // Also seems to be the case for minus
  const a = luxon.Duration.fromObject({ years: 12019 });
  const b = luxon.Duration.fromObject({ months: 1 });
  const c = luxon.Duration.fromObject({ days: 1 });
  const d = luxon.Duration.fromObject({ hours: 1 });
  const e = luxon.Duration.fromObject({ minutes: 1 });
  const f = luxon.Duration.fromObject({ seconds: 1 });
  const g = luxon.Duration.fromObject({ milliseconds: 1 });
  const h = a.minus(b).minus(c).minus(d).minus(e).minus(f).minus(g).normalize()

  console.log('Test H - years', h.years);               // gives 12018
  console.log('Test H - months', h.months);             // gives 11
  console.log('Test H - days', h.days);                 // gives -1
  console.log('Test H - hours', h.hours);               // gives -1
  console.log('Test H - minutes', h.minutes);           // gives -1
  console.log('Test H - seconds', h.seconds);           // gives -1
  console.log('Test H - milliseconds', h.milliseconds); // gives -1
}

I’ve looked at the source code for the plus/minus/normalize functions, but I’m unfamiliar with this code base, so I don’t know what could be causing it. I would only be speculating if I tried.

If I were to speculate, I would suspect https://github.com/moment/luxon/blob/master/src/duration.js#L233 . i.e. that the fromObject does some pre-normalization on creation that the new Duration object created during clone does not.

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Reactions:9
  • Comments:6 (1 by maintainers)

github_iconTop GitHub Comments

1reaction
pomlecommented, Dec 3, 2021

For you finding this when being confused about DateTime.diff, provide a list of units as second argument as a hint to .diff.

  const duration = now
    .diff(date, ["years", "months", "days", "hours", "minutes", "seconds"])
    .normalize();
0reactions
Multihuntrcommented, Oct 8, 2022

Intuition for dates/times is contextual and tricky. That’s why this library exists.

So, I think that having an optional parameter to explicitly declare which keys to normalise with would be good for two reasons.

  1. You can bypass the requirement to add keys equal to zero on the input date object if you find that to be unintuitive.
  2. It implicitly makes this potential issue obvious in the documentation without devoting special space to it.

Being an optional parameter, it wouldn’t be a breaking change, either.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Demystifying Cache Normalization - Apollo GraphQL Blog
Normalization is a technique used to organize data in a way that reduces ... we can enforce unique data getting added to the...
Read more >
Key normalization - PostgreSQL wiki
Key normalization is an enhancement to the representation of index tuples. It should only be applied to those index tuples that "separate ...
Read more >
Is there a built in way to normalize a duration within Luxon?
I have tried Luxon's normalize function, I tried to call it like so: const duration = Duration.fromObject({ hours: 90 }); duration.normalize().
Read more >
Everything you need to know about Min-Max normalization
This is my second post about the normalization techniques that are often used prior to machine learning (ML) model fitting.
Read more >
Relational Database Normalization Process
This process of specifying and defining tables, keys, columns, and relationships in order to create an efficient database is called normalization.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found