All files / src normalize.ts

100% Statements 32/32
100% Branches 2/2
100% Functions 7/7
100% Lines 26/26

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 881x   1x 1x 1x 1x   1x       2x       44x   44x 154x 154x 154x     44x       1x   44x     1x   110x     1x 23x 22x       22x                 22x         22x                                 1x       23x             23x    
import { UNITS_META_MAP_LITERAL, ZERO, UnitKey } from './lib/units';
import { Duration, DurationInput, DateInput } from './types';
import { between } from './between';
import { apply } from './apply';
import { toMilliseconds, toMonths } from './toUnit';
import { parse } from './parse';
 
const createUnitsNormalizer = <T extends UnitKey>(
	keys: T[],
	getDivisor: (unit: T) => number,
) => {
	return (
		duration: Readonly<Duration>,
		remaining: number,
	) => {
		const output = { ...duration };
 
		keys.forEach(unit => {
			const divisor = getDivisor(unit);
			output[unit] = ~~(remaining / divisor);
			remaining -= output[unit] * divisor;
		});
 
		return output;
	};
};
 
const yearMonthNormalizer = createUnitsNormalizer(
	['years', 'months'],
	unit => UNITS_META_MAP_LITERAL[unit].months,
);
 
const dayAndTimeNormalizer = createUnitsNormalizer(
	['days', 'hours', 'minutes', 'seconds', 'milliseconds'],
	unit => UNITS_META_MAP_LITERAL[unit].milliseconds,
);
 
const baseNormalizer = (duration: DurationInput): Duration => {
	const { years, months, weeks, days, ...rest } = parse(duration);
	let output = { ...ZERO };
 
	// Normalize years and months between themselves.
	// They cannot be normalized with other units due to their variable length.
	output = yearMonthNormalizer(
		output,
		toMonths({ years, months }),
	);
 
	// Normalize unambiguous units. It could be argued that `days` is ambiguous as
	// a day is not always 24 hours long, but the ISO 8601 spec says a day is 24 hours.
	// When not changing timezones, a day is consistently 24 hours, whereas months
	// and years are consistently irregular.
	output = dayAndTimeNormalizer(
		output,
		toMilliseconds({ ...rest, days: days + (weeks * 7) }),
	);
 
	return output;
};
 
/**
 * Convert a duration object or number of milliseconds into a complete
 * duration object that expresses the value in the most appropriate units.
 *
 * If a `referenceDate` argument is provided, the returned duration is normalized
 * relative to that date. This means each day, month and year has an unambiguous
 * duration, and the `normalize` function can safely convert between these units.
 *
 * @example
 * normalize({ milliseconds: 4000 }) // { ..., seconds: 4, milliseconds: 0 }
 * normalize('P28DT24H') // { ..., days: 29 }
 * normalize('P28DT24H', '2018-02-01') // { ..., months: 1, days: 1 }
 * normalize('P28DT24H', '2016-02-01') // { ..., months: 1, days: 0 } (leap year)
 */
export const normalize = (
	duration: DurationInput,
	referenceDate?: DateInput,
): Duration => {
	const durationToNormalize = referenceDate != null
		// When using a reference date, `between` may give a result like this:
		// { years: 1, months: -11 }
		// Because of this, we pass that through `baseNormalizer` too.
		? between(referenceDate, apply(referenceDate, duration))
		: duration;
 
	return baseNormalizer(durationToNormalize);
};