Calendar stdlib

std/calendar layers civil-time, timezone, country, and business-calendar helpers over Harn's timestamp builtins. Use it when a workflow needs local calendar semantics instead of raw elapsed seconds.

Core timestamp builtins such as date_parse, date_format, date_to_zone, and duration_hours stay available globally. std/calendar adds ISO week fields, quarter and boundary helpers, local-time construction with explicit DST overlap behavior, supported country metadata, and business-day arithmetic.

Public calendar helpers use reusable shapes instead of anonymous records: CalendarParts, CalendarIsoWeek, CalendarLocalDateTime, CalendarDateRangeOptions, CalendarCountry, CalendarHoliday, CalendarBusinessCalendar, CalendarBusinessWindowOptions, and CalendarBusinessWindow.

Civil calendar helpers

import { iso_week, next_weekday, quarter, start_of_day } from "std/calendar"

pipeline default() {
  let now = date_parse("2026-05-11T17:00:00-04:00")
  let next_monday = start_of_day(
    next_weekday(now, "monday", "America/New_York"),
    "America/New_York",
  )

  log(date_to_zone(next_monday, "America/New_York"))
  log(json_stringify(iso_week(next_monday, "America/New_York")))
  log(quarter(next_monday, "America/New_York"))
}

Boundary helpers include start_of_day, end_of_day, start_of_week, end_of_week, start_of_month, end_of_month, start_of_quarter, end_of_quarter, start_of_year, and end_of_year. Weeks are ISO weeks that start on Monday.

date_range(start, end, unit, timezone, options?) returns local calendar ticks in the half-open range [start, end). Supported units are day, week, month, quarter, and year.

DST semantics

local_datetime(parts, timezone, disambiguation?) builds a timestamp from local civil fields. Fall-back overlaps are deterministic:

DisambiguationOverlap behavior
"earlier"choose the first matching instant
"later"choose the second matching instant
"reject"throw when the local time is ambiguous

Spring-forward gaps always throw because there is no instant with those local fields.

import { local_datetime } from "std/calendar"

pipeline default() {
  let first = local_datetime(
    {year: 2024, month: 11, day: 3, hour: 1, minute: 30},
    "America/New_York",
    "earlier",
  )
  let second = local_datetime(
    {year: 2024, month: 11, day: 3, hour: 1, minute: 30},
    "America/New_York",
    "later",
  )

  log(date_to_zone(first, "UTC"))
  log(date_to_zone(second, "UTC"))
}

Business calendars

The v1 built-in holiday calendar is US-FEDERAL, using observed United States federal holidays and Saturday/Sunday weekends. Unsupported named calendars fail clearly. Custom calendars are plain dicts with optional timezone, weekends, holiday_calendar, and holidays fields, so scripts can layer local closures or company holidays without waiting for a new runtime release.

import {
  add_business_days,
  business_days_between,
  is_business_time,
  next_business_day,
} from "std/calendar"

pipeline default() {
  let calendar = "US-FEDERAL"
  let timezone = "America/New_York"
  let window = {timezone: timezone, start: "09:00", end: "17:00"}

  log(date_format(next_business_day("2026-07-03", calendar, timezone), "%Y-%m-%d", timezone))
  log(date_format(add_business_days("2026-07-01", 3, calendar, timezone), "%Y-%m-%d", timezone))
  log(business_days_between("2026-07-01", "2026-07-08", calendar, timezone))
  log(is_business_time(date_parse("2026-07-06T14:00:00-04:00"), calendar, window))
}

Custom calendar example:

import { is_business_day } from "std/calendar"

pipeline default() {
  let company_calendar = {
    timezone: "UTC",
    holiday_calendar: "US-FEDERAL",
    holidays: [{date: "2026-12-24", name: "Company winter closure"}],
  }

  log(is_business_day("2026-12-24", company_calendar))
}

Country and timezone metadata

Country helpers use a deterministic static dataset keyed by ISO alpha-2 codes. default_timezone_for_country only returns a timezone when the supported country has a single unambiguous entry; multi-timezone countries return nil instead of picking an arbitrary default.

import { country_info, country_timezones, default_timezone_for_country } from "std/calendar"

pipeline default() {
  let us = country_info("US")
  log(us.name)
  log(default_timezone_for_country("US") == nil)
  log(country_timezones("GB")?[0])
  log(default_timezone_for_country("GB"))
}