@burglekitt/gmt
Version:
Temporal-based date and time utilities with timezone support and polyfill integration
67 lines (66 loc) • 3.22 kB
JavaScript
import { Temporal } from "@js-temporal/polyfill";
import { isValidUnixEpochPair, resolveUnixTimeZone, } from "../../internal/resolveUnixTimeZone.js";
import { getLargestDateTimeDurationUnit } from "../../plain/calculate/getLargestDateTimeDurationUnit.js";
import { isValidDateTimeDurationUnit } from "../../plain/validate/index.js";
/**
* Return the difference between two Unix timestamps measured in the given unit.
*
* - Uses Temporal.Instant.until() to calculate the difference.
* - Supports single unit or array of units.
* - Returns null for invalid input.
*
* `smallestUnit`, `roundingIncrement`, and `roundingMode` control optional rounding of the result,
* per Temporal's DifferenceOptions — e.g. `{ smallestUnit: "hour", roundingMode: "halfExpand" }`
* rounds the difference to the nearest hour before extracting the requested unit.
* - When `units` is an array, `smallestUnit` must not be coarser than the largest unit in the
* array (e.g. `["day", "hour"]` with `smallestUnit: "week"`) — this combination is rejected by
* Temporal and returns null, same as other invalid input.
*
* @param value1 first Unix timestamp
* @param value2 second Unix timestamp
* @param units DateTimeDurationUnit | DateTimeDurationUnit[] to measure the difference
* @param options optional: epochUnit ("seconds" | "milliseconds"), timeZone (IANA), smallestUnit, roundingIncrement, roundingMode (Temporal.DifferenceOptions rounding controls)
* @returns numeric difference in the requested unit, or null on invalid input
*
* @example diffUnix(1706745600000, 1706659200000, "day") // 1
* @example diffUnix(1706745600, 1706659200, "day", { epochUnit: "seconds" }) // 1
* @example diffUnix(0, -86400000, "day") // 1 (Jan 1 1970 - Dec 31 1969 = 1 day)
*/
export function diffUnix(value1, value2, units, options) {
const epochUnit = options?.epochUnit ?? "milliseconds";
const timeZone = resolveUnixTimeZone(options?.timeZone);
if (!timeZone)
return null;
const isSingleUnit = !Array.isArray(units);
const validUnits = isSingleUnit
? isValidDateTimeDurationUnit(units)
: units.every(isValidDateTimeDurationUnit);
if (!validUnits) {
return null;
}
if (!isValidUnixEpochPair(value1, value2)) {
return null;
}
try {
const instant1 = Temporal.Instant.fromEpochMilliseconds(epochUnit === "seconds" ? value1 * 1000 : value1);
const instant2 = Temporal.Instant.fromEpochMilliseconds(epochUnit === "seconds" ? value2 * 1000 : value2);
const zdt1 = instant1.toZonedDateTimeISO(timeZone);
const zdt2 = instant2.toZonedDateTimeISO(timeZone);
const duration = zdt1.until(zdt2, {
largestUnit: isSingleUnit ? units : getLargestDateTimeDurationUnit(units),
smallestUnit: options?.smallestUnit,
roundingIncrement: options?.roundingIncrement,
roundingMode: options?.roundingMode,
});
if (isSingleUnit) {
return duration[units] ?? 0;
}
return units.reduce((result, unit) => {
result[unit] = duration[unit] ?? 0;
return result;
}, {});
}
catch {
return null;
}
}