Containment and Overlap
Use these functions for three questions: “is this instant in the range?”, “do these two ranges share any time?” and “what part of this range falls inside that one?”
Overlap and intersection, half-open
Section titled “Overlap and intersection, half-open”intervalsOverlap(a, b) is a.start < b.end && b.start < a.end. It is true exactly
when intersectIntervals(a, b) is not null, so the two can never disagree:
import { intersectIntervals, intervalsOverlap } from "@northguild/gmt";
const morning = { start: "2024-01-01T09:00:00Z", end: "2024-01-01T13:00:00Z" };const afternoon = { start: "2024-01-01T12:00:00Z", end: "2024-01-01T18:00:00Z" };const evening = { start: "2024-01-01T18:00:00Z", end: "2024-01-01T22:00:00Z" };
intervalsOverlap(morning, afternoon); // trueintersectIntervals(morning, afternoon);// { start: "2024-01-01T12:00:00Z", end: "2024-01-01T13:00:00Z" }
intervalsOverlap(afternoon, evening); // false — they touch at 18:00 and share no instantintersectIntervals(afternoon, evening); // nullEmpty intervals (start === end) follow the same rule:
- An empty interval strictly inside another overlaps it, and the intersection is that empty interval.
- An empty interval at an edge does not overlap.
intervalContains(interval, t) is start ≤ t < end:
import { intervalContains } from "@northguild/gmt";
const morning = { start: "2024-01-01T09:00:00Z", end: "2024-01-01T13:00:00Z" };
intervalContains(morning, "2024-01-01T09:00:00Z"); // trueintervalContains(morning, "2024-01-01T13:00:00Z"); // false — the end is excludedintervalContains(morning, "2024-01-01T04:00:00-05:00"); // true — the same instant as 09:00ZClamp to a window
Section titled “Clamp to a window”clampInterval(interval, bounds) is intersectIntervals(interval, bounds) under a name
that reads better when bounds is a fixed window such as a business day:
import { clampInterval } from "@northguild/gmt";
const businessDay = { start: "2024-01-01T09:00:00Z", end: "2024-01-01T17:00:00Z" };
clampInterval({ start: "2024-01-01T08:00:00Z", end: "2024-01-01T12:00:00Z" }, businessDay);// { start: "2024-01-01T09:00:00Z", end: "2024-01-01T12:00:00Z" }The output endpoints are always the caller’s own strings. When both arguments spell the
same instant differently, such as 04:00:00-05:00 and 09:00:00Z, the first argument’s
spelling wins. No specification covers this tie. GMT chose it because a rule based on
position is deterministic without inventing a canonical spelling.
The positional functions
Section titled “The positional functions”The positional intervalContains*, intervalsOverlap* and intervalIntersection*
functions in plain/, utc/, zoned/ and unix/ follow the same half-open rule, so the
end is outside and touching intervals do not overlap:
import { intervalContainsDate, intervalContainsUtc, intervalIntersectionDate, intervalsOverlapDate,} from "@northguild/gmt";
intervalContainsUtc("2024-01-01T09:00:00Z", "2024-01-01T17:00:00Z", "2024-01-01T17:00:00Z");// false — the end is excluded
intervalsOverlapDate("2024-01-01", "2024-07-01", "2024-07-01", "2025-01-01");// false — the first interval ends before 1 July
intervalIntersectionDate("2024-01-01", "2024-07-01", "2024-04-01", "2025-01-01");// { start: "2024-04-01", end: "2024-07-01" }
intervalIntersectionDate("2024-01-01", "2024-07-01", "2024-07-01", "2025-01-01");// null — touching intervals share no dateWith a fourth argument, intervalContains* tests an inner interval instead of a point,
and intervalEngulfs* is the same test under another name. The inner interval must lie
within the outer one and overlap it, so it may end at the outer end, but an empty inner
interval at end is not contained:
intervalContainsDate("2024-01-01", "2024-12-31", "2024-03-01", "2024-09-01"); // trueintervalContainsDate("2024-01-01", "2025-01-01", "2024-07-01", "2025-01-01"); // true — same endintervalContainsDate("2024-01-01", "2025-01-01", "2025-01-01", "2025-01-01"); // false — empty, at the endintervalAbuts* is true when one interval’s end equals the other’s start, in either
order. Any gap, even one nanosecond, means they do not abut, and an empty interval abuts
nothing:
import { intervalAbutsDate, intervalAbutsUtc } from "@northguild/gmt";
intervalAbutsDate("2024-01-01", "2024-07-01", "2024-07-01", "2025-01-01"); // trueintervalAbutsDate("2024-01-01", "2024-06-30", "2024-07-01", "2024-12-31"); // false — 30 June is in neither
intervalAbutsUtc( "2024-01-01T09:00:00Z", "2024-01-01T12:00:00Z", "2024-01-01T12:00:00.000000001Z", "2024-01-01T17:00:00Z",); // false — 1 ns apartOne call for each value kind
Section titled “One call for each value kind”Each positional function exists in six variants: Date, Time, DateTime, Utc, Zoned
and Unix. The examples above use Date and Utc. These show the others, one call each.
The Unix variants take epoch numbers (milliseconds by default), and Time has no calendar,
so it compares times of day.
Containment of a point, then of an inner interval:
import { intervalContainsDateTime, intervalContainsTime, intervalContainsUnix, intervalContainsZoned, intervalEngulfsDate, intervalEngulfsDateTime, intervalEngulfsTime, intervalEngulfsUnix, intervalEngulfsUtc, intervalEngulfsZoned,} from "@northguild/gmt";
intervalContainsTime("09:00:00", "17:00:00", "12:00:00"); // trueintervalContainsDateTime("2024-01-01T00:00:00", "2024-01-02T00:00:00", "2024-01-01T12:00:00"); // trueintervalContainsUnix(0, 1700000000, 170000000); // trueintervalContainsZoned("2024-01-01T00:00:00+00:00[UTC]", "2024-12-31T23:59:59+00:00[UTC]", "2024-06-15T12:00:00+00:00[UTC]"); // true
intervalContainsTime("09:00:00", "17:00:00", "10:00:00", "16:00:00"); // true — inner interval
intervalEngulfsDate("2024-01-01", "2024-12-31", "2024-06-01", "2024-07-01"); // trueintervalEngulfsDate("2024-01-01", "2024-12-31", "2024-01-01", "2024-12-31"); // true — equal intervalsintervalEngulfsDate("2024-06-01", "2024-07-01", "2024-01-01", "2024-12-31"); // false — B is larger than AintervalEngulfsTime("09:00:00", "17:00:00", "10:00:00", "12:00:00"); // trueintervalEngulfsDateTime("2024-01-01T00:00:00", "2024-01-02T00:00:00", "2024-01-01T06:00:00", "2024-01-01T12:00:00"); // trueintervalEngulfsUnix(0, 1700000000, 1000000, 2000000); // trueintervalEngulfsUtc("2024-01-01T00:00:00Z", "2024-12-31T23:59:59Z", "2024-06-01T00:00:00Z", "2024-07-01T00:00:00Z"); // trueintervalEngulfsZoned("2024-01-01T00:00:00+00:00[UTC]", "2024-12-31T23:59:59+00:00[UTC]", "2024-06-01T00:00:00+00:00[UTC]", "2024-07-01T00:00:00+00:00[UTC]"); // trueintervalEngulfs* asks whether B lies within A and overlaps it. B may share A’s start or
end, but an empty B at A’s end is not engulfed. It is the same test as the four-argument
intervalContains*.
Overlap and intersection:
import { intervalIntersectionDateTime, intervalIntersectionTime, intervalIntersectionUnix, intervalIntersectionUtc, intervalIntersectionZoned, intervalsOverlapDateTime, intervalsOverlapTime, intervalsOverlapUnix, intervalsOverlapUtc, intervalsOverlapZoned,} from "@northguild/gmt";
intervalsOverlapTime("09:00:00", "12:00:00", "11:00:00", "14:00:00"); // trueintervalsOverlapDateTime("2024-01-01T00:00:00", "2024-01-02T00:00:00", "2024-01-01T12:00:00", "2024-01-03T00:00:00"); // trueintervalsOverlapUnix(0, 1700000000, 1000000, 2000000); // trueintervalsOverlapUtc("2024-01-01T09:00:00Z", "2024-01-01T17:00:00Z", "2024-01-01T17:00:00Z", "2024-01-01T18:00:00Z"); // false — touching at 17:00intervalsOverlapZoned("2024-01-01T00:00:00+00:00[UTC]", "2024-06-01T00:00:00+00:00[UTC]", "2024-03-01T00:00:00+00:00[UTC]", "2024-09-01T00:00:00+00:00[UTC]"); // true
intervalIntersectionTime("09:00:00", "12:00:00", "11:00:00", "14:00:00");// { start: "11:00:00", end: "12:00:00" }intervalIntersectionDateTime("2024-01-01T00:00:00", "2024-01-02T00:00:00", "2024-01-01T12:00:00", "2024-01-03T00:00:00");// { start: "2024-01-01T12:00:00", end: "2024-01-02T00:00:00" }intervalIntersectionUnix(0, 1700000000, 1000000, 2000000);// { start: 1000000, end: 2000000 } — B lies inside AintervalIntersectionUtc("2024-01-01T00:00:00Z", "2024-07-01T00:00:00Z", "2024-04-01T00:00:00Z", "2025-01-01T00:00:00Z");// { start: "2024-04-01T00:00:00Z", end: "2024-07-01T00:00:00Z" }intervalIntersectionZoned("2024-01-01T00:00:00+00:00[UTC]", "2024-06-01T00:00:00+00:00[UTC]", "2024-03-01T00:00:00+00:00[UTC]", "2024-09-01T00:00:00+00:00[UTC]");// { start: "2024-03-01T00:00:00+00:00[UTC]", end: "2024-06-01T00:00:00+00:00[UTC]" }Adjacency in the remaining variants:
import { intervalAbutsDateTime, intervalAbutsTime, intervalAbutsUnix, intervalAbutsZoned,} from "@northguild/gmt";
intervalAbutsTime("09:00:00", "12:00:00", "12:00:00", "17:00:00"); // trueintervalAbutsDateTime("2024-01-01T00:00:00", "2024-01-02T00:00:00", "2024-01-02T00:00:00", "2024-01-03T00:00:00"); // trueintervalAbutsUnix(0, 1000000, 1000000, 2000000); // trueintervalAbutsZoned("2024-01-01T00:00:00+00:00[UTC]", "2024-06-01T00:00:00+00:00[UTC]", "2024-06-01T00:00:00+00:00[UTC]", "2024-09-01T00:00:00+00:00[UTC]"); // trueintervalAbuts* is Allen’s “meets”: one interval’s end equals the other’s start, so the two
share no instant and leave no gap. A gap of even one epoch unit means they do not abut:
intervalAbutsUnix(0, 1000000, 1000001, 2000000); // false — one millisecond apartAll of these return the sentinel for the same invalid input: false for the boolean checks
and null for the intersections. A wrong type, a malformed string, a leap second, an inverted
interval, or an epoch outside the unix/ grammar (not a safe integer or a digit string)
all count.
See also
Section titled “See also”- Interval Basics: the half-open standard, validation, return conventions
- Set Operations: merge, subtract, sum covered time
intervalsOverlapreferenceintersectIntervalsreferenceclampIntervalreferenceintervalsOverlapDatereferenceintervalAbutsDatereference