Skip to content

Interval Operations

GMT’s interval functions cover containment, overlap, set operations, splitting, counting, and length. The 10 mistakes here are the ones that have caused the most confusion.

HIGH

Confusing intervalsOverlap with intervalAbuts

intervalsOverlap returns true when two intervals share any instants. intervalAbuts returns true when they touch at exactly one endpoint without overlapping. Touching is not overlapping.

Wrong

intervalsOverlapDate("2024-03-15/2024-03-20", "2024-03-20/2024-03-25"); // false — they abut, not overlap

Right

import { intervalsOverlapDate, intervalAbutsDate } from "@northguild/gmt/interval";

intervalsOverlapDate("2024-03-15/2024-03-20", "2024-03-20/2024-03-25"); // false
intervalAbutsDate("2024-03-15/2024-03-20", "2024-03-20/2024-03-25"); // true
HIGH

Assuming intervalEngulfs is new logic

intervalEngulfs returns true when the first interval completely contains the second. It is not the same as overlap — A can overlap B without engulfing it.

Wrong

// assuming overlap implies engulfing

Right

import { intervalEngulfsDate, intervalsOverlapDate } from "@northguild/gmt/interval";

intervalsOverlapDate("2024-03-10/2024-03-20", "2024-03-15/2024-03-18"); // true
intervalEngulfsDate("2024-03-10/2024-03-20", "2024-03-15/2024-03-18"); // true — B is inside A
MEDIUM

Mutating intersection/union results

intervalIntersection and intervalUnion return new interval objects. The originals are unchanged. Mutating the result does not affect the inputs, but mutating the inputs before calling does.

Wrong

const a = { start: "2024-03-15", end: "2024-03-20" };
const b = { start: "2024-03-18", end: "2024-03-25" };
const r = intervalIntersectionDate(a, b);
a.end = "2024-03-22"; // does not change r

Right

const r = intervalIntersectionDate(a, b);
// r is independent — safe to use after the call
MEDIUM

Off-by-one on splitIntervalByUnit final slice

splitIntervalByUnitDate splits an interval into N equal pieces. The final slice may be shorter than the others if the interval is not evenly divisible. The number of slices is ceiling(duration / unit), not floor.

Wrong

splitIntervalByUnitDate("2024-03-15/2024-03-20", "day"); // 6 slices
// assuming all slices are exactly 1 day

Right

// the last slice is the remainder — 2024-03-19 to 2024-03-20 is 1 day, which matches
// but for uneven splits, expect a shorter final slice
HIGH

Using intervalCount where you meant a duration

intervalCountDate returns how many full unit-sized intervals fit inside a range. It is not the duration of the interval. If you need '5 days', use intervalLengthDate or diffDateAsDuration.

Wrong

intervalCountDate("2024-03-15/2024-03-20", "day"); // 5 — number of full days
// used as the duration — but duration and count are different concepts

Right

import { intervalLengthDate, diffDateAsDuration } from "@northguild/gmt";

intervalLengthDate("2024-03-15/2024-03-20", "day"); // 5 — duration in days
diffDateAsDuration("2024-03-15", "2024-03-20"); // duration object
MEDIUM

Expecting a zero-length interval to count 1

An interval where start === end has zero duration. intervalCountDate returns 0 for it, not 1. A zero-length interval is empty, not a single instant.

Wrong

intervalCountDate("2024-03-15/2024-03-15", "day"); // 0 — not 1

Right

// a zero-duration interval is empty
// use a single point representation if you need to express "just this instant"
HIGH

Assuming intervalFromDurationTime applies a calendar-unit duration

intervalFromDurationDate creates an interval from a start date and a duration. Calendar-unit durations (years, months) require a reference point to resolve to days. Without one, the interval length is ambiguous.

Wrong

intervalFromDurationDate("2024-03-15", "P1M"); // 30 or 31 days depending on implicit reference

Right

import { intervalFromDurationDate } from "@northguild/gmt/interval";

intervalFromDurationDate("2024-03-15", "P1M", { relativeTo: "2024-03-15" });
// "2024-03-15/2024-04-15" — resolved using the reference
MEDIUM

Forgetting a negative duration can invert the span

Subtracting a larger duration than the interval width produces a negative result. intervalFromDurationDate with a negative duration creates an interval where end < start.

Wrong

intervalFromDurationDate("2024-03-15", "P-10D"); // end before start — invalid interval

Right

// ensure the duration is positive, or normalize the bounds after construction
HIGH

Expecting date-fns's getOverlappingDaysInIntervals number from intervalOverlappingDays*

date-fns's getOverlappingDaysInIntervals returns the count of overlapping days. GMT's intervalOverlappingDaysDate returns the overlapping interval object(s). They have different return types and different semantics for edge cases.

Wrong

// expecting a number like date-fns

Right

import { intervalOverlappingDaysDate } from "@northguild/gmt/interval";

// returns the overlapping sub-interval, not a day count
HIGH

Confusing intervalLength with intervalCount

intervalLengthDate returns the duration of an interval in a given unit. intervalCountDate returns how many full unit-sized intervals fit inside. They are not the same when the interval is not evenly divisible.

Wrong

intervalLengthDate("2024-03-15/2024-03-20", "day"); // 5 — the length
intervalCountDate("2024-03-15/2024-03-20", "day"); // 5 — count of full days
// these happen to match here, but not for uneven intervals

Right

// use intervalLengthDate when you need the span
// use intervalCountDate when you need how many complete units fit
MEDIUM

Expecting intervalXorAll/mergeIntervals to take flat arguments like their pairwise siblings

intervalXorAll and mergeIntervals accept an array of intervals, not individual arguments. Passing two intervals as separate arguments like intervalXorDate produces a type mismatch.

Wrong

intervalXorDate(a, b); // pairwise — correct for 2 intervals
intervalXorAll(a, b); // wrong — expects an array

Right

import { intervalXorAll, mergeIntervalsDate } from "@northguild/gmt/interval";

intervalXorAll([a, b, c]); // array of 3+ intervals — correct