Luxon DateTime zones, durations and intervals: working versions (tested 2026-10-15)
Parse, convert and format with Luxon across zones, do calendar-aware math, business-hours checks and Interval overlap, all DST-safe.
passed5 passed · 0 failed2026-10-15 13:31 UTC- Status
- passed
- Last passed
- 2026-10-15 13:31 UTC
- Tests
- 5 / 5
- Language
- TypeScript
- Runtime
- node v22.23.3
pinned versions4
luxon@3.7.2tsx@4.23.15@types/node@24.19.2@types/luxon@3.7.6
run it
npm install --no-audit --no-fund --ignore-scripts=false npm test
test environment
node v22.23.3 linux 6.17.0-1022-azure x64 runner: github-actions 38742087302
The code
Exactly the files that passed. Your agent can pull them with get_recipe("luxon-timezones-intervals") (install), or see them on GitHub.
package.json
{
"name": "luxon-timezones-intervals",
"private": true,
"type": "module",
"scripts": {
"test": "node --import tsx --test test/*.test.ts"
},
"dependencies": {
"luxon": "3.7.2"
},
"devDependencies": {
"tsx": "4.23.15",
"@types/node": "24.19.2",
"@types/luxon": "3.7.6"
}
}
src/time.ts
import { DateTime, Interval, Duration, Settings } from "luxon";
Settings.throwOnInvalid = true; // fail loudly instead of producing Invalid DateTime
export const toZone = (iso: string, zone: string) => DateTime.fromISO(iso, { setZone: true }).setZone(zone);
export function isBusinessHours(isoUtc: string, zone: string) {
const t = DateTime.fromISO(isoUtc, { zone: "utc" }).setZone(zone);
return t.weekday <= 5 && t.hour >= 9 && t.hour < 17;
}
export const meetingsOverlap = (a: [string, string], b: [string, string]) =>
Interval.fromISO(`${a[0]}/${a[1]}`).overlaps(Interval.fromISO(`${b[0]}/${b[1]}`));
export const humanDuration = (ms: number) => Duration.fromMillis(ms).shiftTo("hours", "minutes").toHuman({ unitDisplay: "short" });
export const addMonthsEndClamped = (iso: string, months: number) => DateTime.fromISO(iso, { zone: "utc" }).plus({ months }).toISODate();
test/time.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { DateTime } from "luxon";
import { addMonthsEndClamped, humanDuration, isBusinessHours, meetingsOverlap, toZone } from "../src/time.ts";
test("zone conversion keeps the instant", () => {
const t = toZone("2026-07-01T12:00:00Z", "Europe/Paris");
assert.equal(t.toFormat("HH:mm"), "14:00");
assert.equal(t.offset, 120);
assert.equal(t.toUTC().toISO(), "2026-07-01T12:00:00.000Z");
});
test("business hours in the caller's zone", () => {
assert.equal(isBusinessHours("2026-10-15T17:00:00Z", "America/Anchorage"), true); // 09:00 AKDT Thu
assert.equal(isBusinessHours("2026-10-17T20:00:00Z", "America/Anchorage"), false); // Saturday
});
test("interval overlap", () => {
assert.equal(meetingsOverlap(["2026-01-01T10:00Z", "2026-01-01T11:00Z"], ["2026-01-01T10:30Z", "2026-01-01T12:00Z"]), true);
assert.equal(meetingsOverlap(["2026-01-01T10:00Z", "2026-01-01T11:00Z"], ["2026-01-01T11:00Z", "2026-01-01T12:00Z"]), false);
});
test("month math clamps to month end; durations read well", () => {
assert.equal(addMonthsEndClamped("2026-01-31", 1), "2026-02-28");
assert.match(humanDuration(5_400_000), /1 hr, 30 min/);
});
test("invalid input throws", () => {
assert.throws(() => DateTime.fromISO("2026-13-45"));
});
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
~/.cursor/mcp.json
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }.vscode/mcp.json
{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }~/.codeium/windsurf/mcp_config.json
{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }MCP settings
{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.