Duration
An amount of time has two shapes: an ISO 8601 duration string ("PT15M") and a unit object
({ minutes: 15 }). The duration/ functions take the string, except totalUnits and
durationFromUnits, which take the object. The date arithmetic functions (addDate,
subtractZoned and their siblings) take the object. Passing the wrong shape is the most common
source of duration bugs.
Mistakes
Section titled “Mistakes”Confusing duration strings with unit objects
'P1Y2M' and { years: 1, months: 2 } are one amount in two shapes, and a function takes only one of them. The other shape returns the invalid-input sentinel. When you hold a unit object and the function takes a duration string, write the string with durationFromUnits.
Wrong
formatDuration({ years: 1, months: 2 }, "en-US"); // "" — a unit object, not a duration stringaddDate("2024-01-15", "P1Y2M"); // "" — a duration string, not a unit objectRight
import { addDate, durationFromUnits, formatDuration } from "@northguild/gmt";
durationFromUnits({ years: 1, months: 2 }); // "P1Y2M"formatDuration(durationFromUnits({ years: 1, months: 2 }), "en-US"); // "1 year and 2 months"addDate("2024-01-15", { years: 1, months: 2 }); // "2025-03-15"Not validating before parsing
parseDuration returns an empty string for invalid input. Feeding unvalidated user input straight into duration arithmetic produces silent failures.
Wrong
const d = parseDuration(userInput); // userInput = "PX" → "" — no error thrownRight
import { isValidDuration, parseDuration } from "@northguild/gmt";
if (isValidDuration(userInput)) {const d = parseDuration(userInput);}Expecting a best-effort answer for calendar units without relativeTo
Converting a calendar-unit duration (years, months) to days requires a reference date. Without relativeTo, the conversion is ambiguous — different months have different lengths.
Wrong
totalDuration("P1M", "days"); // null — 28, 29, 30 or 31 days: without relativeTo, which month?Right
import { totalDuration } from "@northguild/gmt";
totalDuration("P1M", "days", { relativeTo: "2024-02-01" }); // 29 — February 2024 has 29 daysGiving totalUnits a week
totalUnits takes exact units only: days, hours, minutes, seconds, milliseconds, microseconds and nanoseconds. A week, a month or a year returns null, as a key or as the unit to total into, because TC39 Temporal gives those units no length without a date to measure from. Write a week as seven days. For a calendar amount, use totalDuration with relativeTo.
Wrong
totalUnits({ weeks: 1 }, "days"); // null — a week is not an exact unittotalUnits({ days: 7 }, "weeks"); // null — the same rule for the unit to total intoRight
import { totalDuration, totalUnits } from "@northguild/gmt";
totalUnits({ days: 7 }, "hours"); // 168 — a day counts as 24 hourstotalDuration("P1W", "days", { relativeTo: "2024-02-01" }); // 7 — a week measured from a dateReading a component with getDurationUnit when you wanted a total
getDurationUnit returns one component of a normalized duration (e.g. just the hours). If you need the total duration in a specific unit, use totalDuration instead.
Wrong
const d = normalizeDuration("P1DT2H3M");const hours = getDurationUnit(d, "hours"); // 2 — just the hours component, not the totalRight
import { normalizeDuration, totalDuration } from "@northguild/gmt";
const d = normalizeDuration("P1DT2H3M");const totalHours = totalDuration(d, "hours"); // 26.05 — full duration converted, 3 minutes includedExpecting normalizeDuration to resolve calendar units against today
normalizeDuration has no implicit relativeTo. Whenever a calendar unit (years, months, weeks) is involved, in the duration or as largestUnit, it needs an explicit relativeTo; without one it returns the empty-string sentinel, never a conversion against today's date.
Wrong
normalizeDuration("P1M", { largestUnit: "day" }); // "" — no relativeTo, so no answerRight
import { normalizeDuration } from "@northguild/gmt";
normalizeDuration("P1M", { largestUnit: "day", relativeTo: "2024-02-01" }); // "P29D"Expecting formatDuration to match native Intl.DurationFormat output exactly
GMT's formatDuration renders each non-zero component with Intl.NumberFormat's unit style and joins them with Intl.ListFormat. Intl.DurationFormat output varies by locale, unit set, and style options in its own way. They are not drop-in replacements for each other.
Wrong
// assuming formatDuration("PT1H30M") matches Intl.DurationFormatRight
import { formatDuration } from "@northguild/gmt";
formatDuration("PT1H30M", "en-US"); // "1 hour and 30 minutes" — joined as a listPassing an array of units to totalDuration
totalDuration takes a single unit name, not an array. Passing a list of unit names returns null.
Wrong
totalDuration("P1DT2H3M", ["hours", "minutes"]); // null — expects a single unit stringRight
import { totalDuration } from "@northguild/gmt";
totalDuration("P1DT2H3M", "hours"); // 26.05totalDuration("P1DT2H3M", "minutes"); // 1563 — one call per unit