Skip to content

Merging values

A merge strategy combines values from all matching layers. Use it to add staffing counts, select minimum or maximum values, or combine lists. The default override strategy uses the last matching layer’s value.

Use a tally for counts. Use merged when you need direct access to the low-level strategies.

A tally adds numeric values that cover the same time:

import { tally, weekdays, weekends } from "@kensio/quando";
const staff = tally()
.plus(weekdays(), 3, { label: "Usual crew" })
.plus(weekends(), 1)
.plus("2026-03-11", 2, { label: "Delivery cover" });
const wednesday = Temporal.ZonedDateTime.from(
"2026-03-11T11:00[Europe/London]",
);
console.log(staff.countAt(wednesday));
5

The tally supplies several common operations:

Method Meaning
plus(scope, amount, options?) Add an amount
setCount(scope, amount, options?) Replace lower values within the scope
countAt(instant) Read the amount at one instant
explain(instant) Explain how the matching lines add up
minimumCount(from, to) Find the lowest amount in a window
totalBetween(from, to, unit) Total the amount over elapsed time
countIntervals(from, to) Resolve counts, including zero gaps
validate(from, to) Find inactive and shadowed lines

countAt, minimumCount, and countIntervals treat unassigned time as zero. See explanations for the trace returned by explain. See accumulation for totals such as staff-hours.

merged stores the strategy in a cascade:

Strategy Accepted values Overlap result
override Any JSON-compatible value The later value
sum Numbers The total
max Numbers The largest value
min Numbers The smallest value
concat Arrays One array in layer order

This cascade adds two extra staff members on Wednesday:

import { dates, weekdays } from "@kensio/quando";
import { layer, merged, resolve } from "@kensio/quando/core";
const staff = merged(
"sum",
layer(weekdays(), 3),
layer(dates("2026-03-11"), 2),
);
const week = {
from: Temporal.ZonedDateTime.from("2026-03-09T00:00[Europe/London]"),
to: Temporal.ZonedDateTime.from("2026-03-16T00:00[Europe/London]"),
};
for (const { start, end, value } of resolve(staff, week)) {
console.log(`${start?.toPlainDate()} → ${end?.toPlainDate()}: ${value}`);
}
2026-03-09 → 2026-03-11: 3
2026-03-11 → 2026-03-12: 5
2026-03-12 → 2026-03-14: 3

Layer order controls the order used by concat:

const onCall = merged(
"concat",
layer(weekdays(), ["alice"]),
layer(dates("2026-03-11"), ["bob"]),
);

The value on Wednesday is ["alice", "bob"].

A replacement layer removes lower layers from its selected period. The replacement’s result still merges with values from higher layers.

A nested replacement cascade uses its own strategy. This lets an outer sum cascade contain a replacement that uses max, for example.

Each strategy requires a compatible value type. TypeScript checks that sum, max, and min receive numbers and that concat receives arrays.

Runtime validation applies the same rules to raw layers and parsed documents. Invalid values fail when the cascade is constructed or parsed:

import { parseString, parseCascade } from "@kensio/quando/parsing";
parseCascade(
{
type: "cascade",
merge: "sum",
layers: [{ scope: { type: "always" }, value: "alice" }],
},
parseString,
);
TypeError: cascade.layers[0].value: sum needs numbers.

Strategy names are part of the stored JSON document. parseCascade rejects unknown names.