Skip to content

Time zones

Quando evaluates local dates and times in a named time zone. A rule uses the query context’s zone unless a schedule or an explicit rule wrapper supplies one.

Every query starts from a Temporal.ZonedDateTime. Its zone becomes the default for rules that have no zone of their own.

import { timeOfDayRange, weekdays } from "@kensio/quando";
import { intervals } from "@kensio/quando/core";
const officeHours = weekdays().and(timeOfDayRange("09:00", "17:00"));
const londonDay = {
from: Temporal.ZonedDateTime.from("2026-03-09T00:00[Europe/London]"),
to: Temporal.ZonedDateTime.from("2026-03-10T00:00[Europe/London]"),
};
const tokyoDay = {
from: Temporal.ZonedDateTime.from("2026-03-09T00:00[Asia/Tokyo]"),
to: Temporal.ZonedDateTime.from("2026-03-10T00:00[Asia/Tokyo]"),
};
console.log([...intervals(officeHours, londonDay)][0]?.start?.toString());
console.log([...intervals(officeHours, tokyoDay)][0]?.start?.toString());
2026-03-09T09:00:00+00:00[Europe/London]
2026-03-09T09:00:00+09:00[Asia/Tokyo]

Both intervals begin at 09:00 local time. They represent different instants.

Set the context’s default zone through from. Context has no separate zone field.

inZone(zone, rule) evaluates a complete rule subtree in the named zone.

import { inZone, timeOfDayRange, weekdays } from "@kensio/quando";
const londonOffice = inZone(
"Europe/London",
weekdays().and(timeOfDayRange("09:00", "17:00")),
);

Both the weekday and the time range now use London local time. A query from Tokyo still evaluates this rule in London.

A nested inZone can choose another zone for one part of the rule. The nearest explicit zone applies to that subtree.

Set zone when constructing a schedule to fix its opening hours to that zone:

import { schedule, weekdays } from "@kensio/quando";
const londonOffice = schedule({ zone: "Europe/London" }).open(
weekdays(),
"09:00-17:00",
);

Every rule passed to this schedule uses London unless that rule contains its own explicit zone. Omit the schedule zone when the definition should follow the query instant’s zone.

Intervals are returned in the zone of context.from. A London rule queried from Tokyo produces values displayed in Tokyo time.

This affects display only. Calling .withTimeZone("Europe/London") on a result keeps the same instant and changes its displayed local time.

When a context has a finite to, results are clipped at that instant even if the rule’s local interval continues beyond it.

timeOfDayRange describes wall-clock endpoints. A shift from 22:00 to 06:00 keeps those local times when clocks change. Its elapsed duration can be seven, eight, or nine hours.

import { timeOfDayRange } from "@kensio/quando";
import { duration, intervals } from "@kensio/quando/core";
const nightShift = timeOfDayRange("22:00", "06:00");
const springChange = {
from: Temporal.ZonedDateTime.from("2026-03-28T12:00[Europe/London]"),
to: Temporal.ZonedDateTime.from("2026-03-29T12:00[Europe/London]"),
};
const autumnChange = {
from: Temporal.ZonedDateTime.from("2026-10-24T12:00[Europe/London]"),
to: Temporal.ZonedDateTime.from("2026-10-25T12:00[Europe/London]"),
};
for (const context of [springChange, autumnChange]) {
const [shift] = [...intervals(nightShift, context)];
console.log(shift === undefined ? undefined : duration(shift)?.toString());
}
PT7H
PT9H

Queries such as coveredDuration and addCoveredTime use exact elapsed time. addCoveredTime therefore rejects calendar durations containing years, months, weeks, or days.

Some local times do not exist when clocks move forward. Others occur twice when clocks move back.

By default, Quando uses Temporal’s compatible disambiguation. Set disambiguation on the context when the application needs another policy:

const strict = {
from: Temporal.ZonedDateTime.from("2026-03-28T00:00[Europe/London]"),
to: Temporal.ZonedDateTime.from("2026-03-31T00:00[Europe/London]"),
disambiguation: "reject" as const,
};

The available policies are compatible, earlier, later, and reject. The reject policy throws when evaluation encounters an ambiguous or nonexistent local time.

A time-of-day range with zero elapsed duration produces no interval. For example, the London range from 01:00 to 02:00 is absent on the 2026 spring clock-change date.

Rule builders, schedule, and parsers validate zone names when called. An unknown zone causes an error when the definition is created or parsed.