Files
suaveplan/openspec/archive/2026-04-19-enterprise-datetime/proposal.md
2026-05-27 23:29:53 +01:00

5.1 KiB
Raw Blame History

OpenSpec Change: enterprise-datetime

  • Change ID: enterprise-datetime
  • Package: @suaveplan/datetime
  • Category: universal
  • Target path: packages/universal/datetime
  • Status: IN PROGRESS
  • Date: 2026-04-18
  • Reference spec: /mnt/s/web/suaveplan/enterprise/datetime.md

Summary

Greenfield implementation of @suaveplan/datetime for the genesis branch. The existing main-branch datetime package provides the baseline public-API surface to preserve; this change delivers timezone-aware date/time utilities with business-day arithmetic, calendar math, recurrence rules (RRule subset), relative time formatting, injectable clock, structured errors, property-based tests, OTel instrumentation, and benchmarks.

Current-state audit (genesis)

The package does not exist on genesis yet — this is a fresh build.

Available workspace deps on genesis

Package Available
@suaveplan/error ✅
@suaveplan/types ✅
@suaveplan/events ✅
@suaveplan/timing ❌ not yet built — no dependency taken
@suaveplan/streams ❌ not yet built — not required
@suaveplan/opentelemetry ❌ not yet built — use @opentelemetry/api directly
@suaveplan/feature-flags ❌ not yet built — skip
@suaveplan/schema-registry ❌ not yet built — export Zod schemas directly

External deps (via catalog):

  • zod → catalog:forms
  • @opentelemetry/api → catalog:observability
  • tslib → catalog:build

Dev deps (via catalog):

  • vitest, @vitest/coverage-istanbul → catalog:testing
  • fast-check → catalog:testing
  • mitata → catalog:testing

Preserved public API surface (from main branch)

All of the following must be exported from the genesis build:

Functions — Business Days

  • addBusinessDays(date, n, opts?) → Date
  • businessDaysBetween(start, end, opts?) → number
  • isBusinessDay(date, opts?) → boolean
  • nextBusinessDay(date, opts?) → Date
  • previousBusinessDay(date, opts?) → Date
  • subtractBusinessDays(date, n, opts?) → Date

Functions — Calendar

  • addDuration(date, duration) → Date
  • daysInMonth(year, month) → number
  • endOf(date, unit) → Date
  • getCalendarMonth(year, month) → CalendarMonth
  • isLeapYear(year) → boolean
  • isSameDay(a, b) → boolean
  • isSameMonth(a, b) → boolean
  • startOf(date, unit) → Date
  • subtractDuration(date, duration) → Date
  • toDate(input) → Date

Functions — Recurrence

  • expandRecurrence(rule, opts) → Date[]
  • formatRRule(rule) → string
  • nextOccurrence(rule, from) → Date | null
  • parseRRule(input) → RecurrenceRule

Functions — Relative

  • diffInUnits(a, b, unit) → number
  • formatRelative(date, opts?) → string
  • timeAgo(date, opts?) → string
  • timeUntil(date, opts?) → string

Types

  • BusinessDayOptions — { holidays?: Date[]; weekendDays?: Weekday[]; clock?: Clock }
  • CalendarDay — { date: Date; dayOfWeek: number; isWeekend: boolean; isToday: boolean }
  • CalendarMonth — { year: number; month: number; days: CalendarDay[] }
  • DateInput — Date | string | number
  • Duration — { years?: number; months?: number; weeks?: number; days?: number; hours?: number; minutes?: number; seconds?: number; milliseconds?: number }
  • DurationUnit — union of duration field names
  • RelativeTimeOptions — { locale?: string; clock?: Clock; precision?: DurationUnit }
  • Timezone — IANA timezone string branded type
  • Weekday — 0–6 (Sunday–Saturday)
  • DayAbbreviation — 'Sun' | 'Mon' | … | 'Sat'
  • Frequency — 'daily' | 'weekly' | 'monthly' | 'yearly'
  • RecurrenceOptions — options for expandRecurrence
  • RecurrenceRule — { freq: Frequency; interval?: number; count?: number; until?: Date; byDay?: Weekday[]; byMonthDay?: number[] }

New additions for genesis

Structured errors

  • DatetimeError extends SuaveplanError with codes under DATETIME.* namespace
  • Codes: DATETIME.PARSE.INVALID_INPUT, DATETIME.RECURRENCE.INFINITE_EXPANSION, DATETIME.TIMEZONE.UNKNOWN

Zod schemas

  • DateInputSchema, DurationSchema, RecurrenceRuleSchema, TimezoneSchema — all public types with Zod schemas in *.schemas.ts siblings

Injectable clock

  • All functions that read current time accept optional { clock?: Clock } — zero-config default uses Date.now()

OTel instrumentation

  • @opentelemetry/api counters and histograms on expandRecurrence, formatRelative, and business-day batch functions

Property-based tests (fast-check)

  • Round-trip: parseRRule → formatRRule → parseRRule identity
  • Inverse pairs: addDuration + subtractDuration, addBusinessDays + subtractBusinessDays
  • Boundary invariants: startOf(date, 'day') ≤ date ≤ endOf(date, 'day')

Benchmarks (mitata)

  • expandRecurrence 1 000-occurrence daily rule
  • getCalendarMonth 12-month batch
  • businessDaysBetween 10-year span

Impact

  • Adds one new package: packages/universal/datetime
  • Adds one new spec capability: datetime
  • No existing package consumers on genesis yet