Skip to content

Commit e3c4004

Browse files
authored
Fix Duration#normalize, shiftTo and shiftToAll (#1498)
* Fix normalizeValues not converting fractional units into lower order units * Clarify Duration#normalize with fractional parts * Add tests for new Duration#normalize behavior * Add tests for shiftTo and shiftToAll only producing fractions in the lowest unit * Finish Duration#normalize fourth example
1 parent 8eff275 commit e3c4004

File tree

2 files changed

+80
-2
lines changed

2 files changed

+80
-2
lines changed

src/duration.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ function normalizeValues(matrix, vals) {
142142
// if this is not the case, factor is used to make it so
143143
const factor = durationToMillis(matrix, vals) < 0 ? -1 : 1;
144144

145-
reverseUnits.reduce((previous, current) => {
145+
orderedUnits.reduceRight((previous, current) => {
146146
if (!isUndefined(vals[current])) {
147147
if (previous) {
148148
const previousVal = vals[previous] * factor;
@@ -172,6 +172,21 @@ function normalizeValues(matrix, vals) {
172172
return previous;
173173
}
174174
}, null);
175+
176+
// try to convert any decimals into smaller units if possible
177+
// for example for { years: 2.5, days: 0, seconds: 0 } we want to get { years: 2, days: 182, hours: 12 }
178+
orderedUnits.reduce((previous, current) => {
179+
if (!isUndefined(vals[current])) {
180+
if (previous) {
181+
const fraction = vals[previous] % 1;
182+
vals[previous] -= fraction;
183+
vals[current] += fraction * matrix[previous][current];
184+
}
185+
return current;
186+
} else {
187+
return previous;
188+
}
189+
}, null);
175190
}
176191

177192
// Remove all properties with a value of 0 from an object
@@ -710,14 +725,16 @@ export default class Duration {
710725
/**
711726
* Reduce this Duration to its canonical representation in its current units.
712727
* Assuming the overall value of the Duration is positive, this means:
713-
* - excessive values for lower-order units are converted to higher order units (if possible, see first and second example)
728+
* - excessive values for lower-order units are converted to higher-order units (if possible, see first and second example)
714729
* - negative lower-order units are converted to higher order units (there must be such a higher order unit, otherwise
715730
* the overall value would be negative, see second example)
731+
* - fractional values for higher-order units are converted to lower-order units (if possible, see fourth example)
716732
*
717733
* If the overall value is negative, the result of this method is equivalent to `this.negate().normalize().negate()`.
718734
* @example Duration.fromObject({ years: 2, days: 5000 }).normalize().toObject() //=> { years: 15, days: 255 }
719735
* @example Duration.fromObject({ days: 5000 }).normalize().toObject() //=> { days: 5000 }
720736
* @example Duration.fromObject({ hours: 12, minutes: -45 }).normalize().toObject() //=> { hours: 11, minutes: 15 }
737+
* @example Duration.fromObject({ years: 2.5, days: 0, hours: 0 }).normalize().toObject() //=> { years: 2, days: 182, hours: 12 }
721738
* @return {Duration}
722739
*/
723740
normalize() {

test/duration/units.test.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,17 @@ test("Duration#shiftTo handles mixed units", () => {
105105
});
106106
});
107107

108+
test("Duration#shiftTo does not produce unnecessary fractions in higher order units", () => {
109+
const duration = Duration.fromObject(
110+
{ years: 2.5, weeks: -1 },
111+
{ conversionAccuracy: "longterm" }
112+
);
113+
const shifted = duration.shiftTo("years", "weeks", "minutes").toObject();
114+
expect(shifted.years).toBe(2);
115+
expect(shifted.weeks).toBe(25);
116+
expect(shifted.minutes).toBeCloseTo(894.6, 5);
117+
});
118+
108119
//------
109120
// #shiftToAll()
110121
//-------
@@ -122,6 +133,22 @@ test("Duration#shiftToAll shifts to all available units", () => {
122133
});
123134
});
124135

136+
test("Duration#shiftToAll does not produce unnecessary fractions in higher order units", () => {
137+
const duration = Duration.fromObject(
138+
{ years: 2.5, weeks: -1, seconds: 0 },
139+
{ conversionAccuracy: "longterm" }
140+
);
141+
const toAll = duration.shiftToAll().toObject();
142+
expect(toAll.years).toBe(2);
143+
expect(toAll.months).toBe(5);
144+
expect(toAll.weeks).toBe(3);
145+
expect(toAll.days).toBe(2);
146+
expect(toAll.hours).toBe(10);
147+
expect(toAll.minutes).toBe(29);
148+
expect(toAll.seconds).toBe(6);
149+
expect(toAll.milliseconds).toBeCloseTo(0, 5);
150+
});
151+
125152
test("Duration#shiftToAll maintains invalidity", () => {
126153
const dur = Duration.invalid("because").shiftToAll();
127154
expect(dur.isValid).toBe(false);
@@ -243,6 +270,40 @@ test("Duration#normalize can convert all unit pairs", () => {
243270
}
244271
});
245272

273+
test("Duration#normalize moves fractions to lower-order units", () => {
274+
expect(Duration.fromObject({ years: 2.5, days: 0, hours: 0 }).normalize().toObject()).toEqual({
275+
years: 2,
276+
days: 182,
277+
hours: 12,
278+
});
279+
expect(Duration.fromObject({ years: -2.5, days: 0, hours: 0 }).normalize().toObject()).toEqual({
280+
years: -2,
281+
days: -182,
282+
hours: -12,
283+
});
284+
expect(Duration.fromObject({ years: 2.5, days: 12, hours: 0 }).normalize().toObject()).toEqual({
285+
years: 2,
286+
days: 194,
287+
hours: 12,
288+
});
289+
expect(Duration.fromObject({ years: 2.5, days: 12.25, hours: 0 }).normalize().toObject()).toEqual(
290+
{ years: 2, days: 194, hours: 18 }
291+
);
292+
});
293+
294+
test("Duration#normalize does not produce fractions in higher order units when rolling up negative lower order unit values", () => {
295+
const normalized = Duration.fromObject(
296+
{ years: 100, months: 0, weeks: -1, days: 0 },
297+
{ conversionAccuracy: "longterm" }
298+
)
299+
.normalize()
300+
.toObject();
301+
expect(normalized.years).toBe(99);
302+
expect(normalized.months).toBe(11);
303+
expect(normalized.weeks).toBe(3);
304+
expect(normalized.days).toBeCloseTo(2.436875, 7);
305+
});
306+
246307
//------
247308
// #rescale()
248309
//-------

0 commit comments

Comments
 (0)