Skip to content

intervalCountZoned

intervalCountZoned(start: string, end: string, unit: string): number
import { intervalCountZoned } from "@northguild/gmt/zoned/interval";

Count how many unit boundaries a zoned interval crosses.

  • Counts local calendar boundaries touched by the half-open interval [start, end) — distinct from diffZoned, which measures exact elapsed duration.
  • The end boundary is excluded: midnight to midnight two days later counts 2 days.
  • A zero-length interval counts 1 when it sits mid-unit and 0 when it sits exactly on a unit boundary.
  • DST-aware: a local day that springs forward counts 23 hour boundaries and one that falls back counts 25. A local day whose midnight is skipped entirely starts at 01:00.
  • A fixed 24-hour span touches 25 local hour boundaries in zones offset by :30/:45.
  • When start and end carry different time zones, boundaries are counted in start’s zone.
  • Weeks start on Monday (ISO 8601).
  • Accepts singular or plural units ("day" and "days" behave identically).
  • Accepts GMT calendar-annotated zoned strings (as produced by convertZonedToCalendar) as well as bare ISO ones — E7 (issue #152). When BOTH endpoints carry the same calendar tag the measurement is made in that calendar; when the tags mismatch, or either endpoint is bare ISO, it falls back to Gregorian/ISO rather than returning the sentinel (E7’s D5-zoned). The fallback is mandatory, not a convenience: ZonedDateTime.prototype.until throws across mismatched calendars for EVERY largestUnit — verified, including "hour" and "nanosecond".
  • Returns null on invalid input (unparseable start/end, start > end, unsupported unit, leap-second strings).
Parameter Type Description
start string ISO 8601 zoned datetime string for the interval start
end string ISO 8601 zoned datetime string for the interval end
unit string unit string — any DateTimeUnit

number of unit boundaries touched, or null on invalid input

intervalCountZoned("2024-01-01T23:59:00+00:00[UTC]", "2024-01-02T00:01:00+00:00[UTC]", "day") // 2
intervalCountZoned("2024-03-10T00:00:00-05:00[America/New_York]", "2024-03-11T00:00:00-04:00[America/New_York]", "hour") // 23 (spring forward)
intervalCountZoned("2024-11-03T00:00:00-04:00[America/New_York]", "2024-11-04T00:00:00-05:00[America/New_York]", "hour") // 25 (fall back)
intervalCountZoned("2024-01-01T00:00:00-05:00[America/New_York]", "2024-01-03T00:00:00+09:00[Asia/Tokyo]", "day") // 2 (counted in America/New_York)
intervalCountZoned("2024-01-01T05:00:00+00:00[UTC]", "2024-01-01T05:00:00+00:00[UTC]", "day") // 1 (zero-length, mid-day)
intervalCountZoned("2024-01-01T00:00:00+00:00[UTC]", "2024-01-01T00:00:00+00:00[UTC]", "day") // 0 (zero-length, on the boundary)
intervalCountZoned("5784-01-01T00:00:00-04:00[u-ca=hebrew][America/New_York]", "5785-01-01T00:00:00-04:00[u-ca=hebrew][America/New_York]", "month") // 13 (Hebrew leap year; the ISO equivalent is 14)
intervalCountZoned("invalid", "2024-01-02T00:00:00+00:00[UTC]", "day") // null

zoned/interval/intervalCountZoned.ts