Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,135 @@
# 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