Skip to content

Durations

  • The core functions, which every industry uses.

An amount of time has two shapes in GMT:

  • A unit object, such as { minutes: 15 } or { days: 1, hours: 12 }. The date arithmetic functions take one: addDate, subtractZoned and their siblings.
  • An ISO 8601 duration string, such as "PT15M" or "P1DT12H". The duration functions take one, and so do the other functions that take a duration, such as intervalFromDurationUtc.

durationFromUnits writes a unit object as a duration string, so an amount you hold as an object works with every function that takes a string. totalUnits totals a unit object in one call. Both are under Unit objects below.

This guide covers the duration functions. To add, subtract and diff dates, see Plain Arithmetic.

import { isValidDuration, parseDuration } from "@northguild/gmt";
isValidDuration("P1DT2H30M"); // true
isValidDuration("not a duration"); // false
parseDuration("P1DT2H30M"); // "P1DT2H30M"
parseDuration("PT1.5S", { smallestUnit: "second", roundingMode: "trunc" });
// "PT1S"
parseDuration("PT1.5S", { smallestUnit: "second" }); // "PT1S"
parseDuration("PT1.5S", { fractionalSecondDigits: 3 }); // "PT1.500S"
parseDuration("PT1.9S", { smallestUnit: "second", roundingMode: "trunc" }); // "PT1S"
import { addDuration, subtractDuration } from "@northguild/gmt";
addDuration("P1D", "PT2H"); // "P1DT2H"
subtractDuration("P1D", "PT2H"); // "PT22H"
subtractDuration("PT1H", "PT2H"); // "-PT1H"
import { normalizeDuration } from "@northguild/gmt";
normalizeDuration("PT90M", { largestUnit: "hour" }); // "PT1H30M"
normalizeDuration("PT90M30S", { smallestUnit: "minute" }); // "PT91M"
normalizeDuration("PT90M"); // "PT90M" — no options = no promotion
normalizeDuration("P45D", { largestUnit: "month", relativeTo: "2024-01-01" });
// "P1M14D" — calendar units need a relativeTo anchor

getDurationUnit reads one component as stored. totalDuration converts the whole duration into one unit:

import { getDurationUnit, totalDuration } from "@northguild/gmt";
getDurationUnit("P1DT2H30M", "hours"); // 2 — the hours component as stored
totalDuration("P1DT2H30M", "hours"); // 26.5 — the whole duration totalled into hours
totalDuration("PT90M", "hours"); // 1.5
totalDuration("P1M", "days"); // null — calendar unit needs relativeTo
totalDuration("P1M", "days", { relativeTo: "2024-02-01" }); // 29

getDurationUnit("PT90M", "hours") is 0 but totalDuration("PT90M", "hours") is 1.5: the first reads a stored component, the second totals the whole duration. totalDuration returns null when a calendar unit (year, month or week) is involved without a relativeTo anchor. normalizeDuration carries the same constraint. negateDuration, absDuration, getDurationSign and getDurationUnit only read the sign or a field, so they never need an anchor.

Settings and function arguments often hold an amount as an object, such as { minutes: 15 }. Two functions take that shape.

durationFromUnits writes the object as an ISO 8601 duration string. It takes all ten units, from years down to nanoseconds:

import { durationFromUnits } from "@northguild/gmt";
durationFromUnits({ minutes: 15 }); // "PT15M"
durationFromUnits({ days: 1, hours: 12 }); // "P1DT12H"
durationFromUnits({ years: 1, months: 2 }); // "P1Y2M"
durationFromUnits({ milliseconds: 1500 }); // "PT1.5S" — a decimal fraction of a second
durationFromUnits({ minutes: 90 }); // "PT90M" — written as given, not carried into hours
durationFromUnits({}); // "" — an empty object is invalid input

The result goes straight into any function that takes a duration string:

import {
durationFromUnits,
intervalFromDurationUtc,
normalizeDuration,
} from "@northguild/gmt";
intervalFromDurationUtc("2024-01-01T00:00:00Z", durationFromUnits({ days: 1 }), "start");
// { start: "2024-01-01T00:00:00Z", end: "2024-01-02T00:00:00Z" }
normalizeDuration(durationFromUnits({ minutes: 90 }), { largestUnit: "hour" });
// "PT1H30M" — normalizeDuration carries the minutes into hours

totalUnits converts the whole object into one unit, as totalDuration does for a string:

import { totalUnits } from "@northguild/gmt";
totalUnits({ minutes: 15 }, "seconds"); // 900
totalUnits({ days: 30 }, "seconds"); // 2592000
totalUnits({ days: 1, hours: 12 }, "seconds"); // 129600
totalUnits({ hours: 36 }, "days"); // 1.5
totalUnits({ minutes: -90 }, "hours"); // -1.5

totalUnits takes exact units only: days, hours, minutes, seconds, milliseconds, microseconds and nanoseconds. It counts a day as 24 hours, as Temporal’s Duration.prototype.total does when it has no relativeTo. Temporal gives a year, a month or a week no length without a date to measure from, so totalUnits returns null for one, as a key or as the unit to total into:

import { durationFromUnits, totalDuration, totalUnits } from "@northguild/gmt";
totalUnits({ weeks: 1 }, "days"); // null — a week is not an exact unit
totalUnits({ days: 7 }, "hours"); // 168 — write the week as seven days
totalDuration(durationFromUnits({ months: 1 }), "days", { relativeTo: "2024-02-01" });
// 29 — a calendar amount: write the string, then total it against a date

TypeScript reports { weeks: 1 } as an error where the call is written. A value that only exists at run time, such as an object parsed from JSON, gets null. Check for null before you use the result. Do not default it away: totalUnits(config.ttl, "seconds") ?? 0 would turn a misspelt key into a lifetime of zero seconds.

  • A unit object of exact units, to total in one unit: totalUnits.
  • A unit object of any units, to write as a duration string: durationFromUnits.
  • A duration string, to total in one unit: totalDuration.
  • A unit object with years, months or weeks, to total in one unit: durationFromUnits, then totalDuration with relativeTo.
import { negateDuration, absDuration, getDurationSign } from "@northguild/gmt";
negateDuration("P1DT2H"); // "-P1DT2H"
absDuration("-P1DT2H"); // "P1DT2H"
getDurationSign("-P1DT2H"); // -1
getDurationSign("PT0S"); // 0
import { compareDurations } from "@northguild/gmt";
compareDurations("PT1H", "PT30M"); // 1
compareDurations("PT60M", "PT1H"); // 0 — equal by length, not spelling
compareDurations("P1M", "P30D", { relativeTo: "2024-01-01" }); // 1
compareDurations("P1M", "P30D", { relativeTo: "2024-02-01" }); // -1
compareDurations("P1M", "P30D"); // null — a calendar unit needs a relativeTo anchor
import { formatDuration } from "@northguild/gmt";
formatDuration("P1DT2H30M", "en-US");
// "1 day, 2 hours, and 30 minutes"
formatDuration("P1DT2H30M", "en-US", { style: "short" });
// "1 day, 2 hr, & 30 min"
formatDuration("P1DT0H30M", "en-US");
// "1 day and 30 minutes" — zero-valued components omitted

diffDate/diffDateTime/etc. return a single-unit number. Their *AsDuration siblings return an ISO 8601 duration string instead:

import { diffDateAsDuration, diffZonedAsDuration } from "@northguild/gmt";
diffDateAsDuration("2024-03-10", "2024-04-05", "days");
// "P26D"
diffZonedAsDuration(
"2024-03-09T12:00:00-05:00[America/New_York]",
"2024-03-11T12:00:00-04:00[America/New_York]",
"days",
);
// "P2D" — calendar days on New York's wall clock, across a DST spring-forward
diffZonedAsDuration(
"2024-03-09T12:00:00-05:00[America/New_York]",
"2024-03-11T12:00:00-04:00[America/New_York]",
"hours",
);
// "PT47H" — real elapsed time: the spring-forward day had 23 hours

diffDateAsDuration/diffDateTimeAsDuration/diffZonedAsDuration/diffUnixAsDuration/ diffUtcAsDuration are siblings of diffDate/diffDateTime/diffZoned/diffUnix/diffUtc. They return an ISO 8601 duration string (sentinel "") where the plain functions return a single-unit number (sentinel null). They take a single unit, not an array, which sets the duration’s largestUnit:

import { diffDate, diffDateAsDuration } from "@northguild/gmt";
diffDate("not a date", "2024-04-05", "days"); // null
diffDateAsDuration("not a date", "2024-04-05", "days"); // ""

Feed the result straight into any duration function: formatDuration(diffDateAsDuration(a, b, "days"), "en-US").