Initial Commit
This commit is contained in:
135
openspec/archive/2026-04-19-enterprise-datetime/proposal.md
Normal file
135
openspec/archive/2026-04-19-enterprise-datetime/proposal.md
Normal 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
|
||||
@@ -0,0 +1,53 @@
|
||||
# Capability Spec: Datetime
|
||||
|
||||
**Package**: `@suaveplan/datetime`
|
||||
**Version**: 0.0.0 (genesis)
|
||||
**Status**: ADDED
|
||||
|
||||
---
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### DT-01 — DateInput acceptance
|
||||
|
||||
Every function that accepts a date parameter MUST accept `DateInput = Date | string | number`. Strings must be ISO-8601; numbers are Unix milliseconds. Invalid inputs throw `DatetimeError` with code `DATETIME.PARSE.INVALID_INPUT`.
|
||||
|
||||
### DT-02 — Injectable clock
|
||||
|
||||
Every function that reads current time (e.g. `timeAgo`, `isBusinessDay` for "today") MUST accept an optional `{ clock?: Clock }` option. The `Clock` interface is sourced from `@suaveplan/types`. Zero-config default uses `Date.now()`. No `Date.now()` or `new Date()` calls at module scope.
|
||||
|
||||
### DT-03 — Business day arithmetic
|
||||
|
||||
`addBusinessDays`, `subtractBusinessDays`, `nextBusinessDay`, `previousBusinessDay`, `isBusinessDay`, `businessDaysBetween` MUST all accept `BusinessDayOptions` including a custom `holidays?: Date[]` list and `weekendDays?: Weekday[]` array (defaults: Saturday and Sunday). `businessDaysBetween` MUST check `AbortSignal` every 256 days to support large ranges.
|
||||
|
||||
### DT-04 — Calendar math correctness
|
||||
|
||||
`addDuration` and `subtractDuration` MUST correctly handle month rollover (e.g. Jan 31 + 1 month = Feb 28/29) and DST transitions without double-counting hours. `daysInMonth` MUST return correct values for all months including February in leap years.
|
||||
|
||||
### DT-05 — Recurrence rules
|
||||
|
||||
`parseRRule` MUST parse a simplified RRULE string supporting `FREQ`, `INTERVAL`, `COUNT`, `UNTIL`, `BYDAY`, `BYMONTHDAY`. `expandRecurrence` MUST require either `count` or `until` in the rule; rules missing both MUST throw `DatetimeError` with code `DATETIME.RECURRENCE.INFINITE_EXPANSION`. `AbortSignal` is checked every 64 occurrences.
|
||||
|
||||
### DT-06 — Structured errors
|
||||
|
||||
All thrown errors MUST extend `DatetimeError` which extends `SuaveplanError`. Error codes are namespaced under `DATETIME.*`. The error includes `{ code, message, cause?, context? }`.
|
||||
|
||||
### DT-07 — Zod schemas
|
||||
|
||||
Every exported public type that carries runtime data MUST have a corresponding Zod schema in a sibling `*.schemas.ts` file. Schemas are exported directly (not via a schema-registry package).
|
||||
|
||||
### DT-08 — i18n-ready output
|
||||
|
||||
`formatRelative`, `timeAgo`, and `timeUntil` MUST produce output via i18n-keyed strings. The default locale is English pass-through. The key system is compatible with a future `@suaveplan/i18n` adapter.
|
||||
|
||||
### DT-09 — OTel instrumentation
|
||||
|
||||
`expandRecurrence` and `businessDaysBetween` MUST emit OpenTelemetry histograms tracking call duration and output size. All instruments are registered via `@opentelemetry/api` and are no-ops when no exporter is registered.
|
||||
|
||||
### DT-10 — sideEffects: false
|
||||
|
||||
The package MUST set `"sideEffects": false` in `package.json`. A tree-shake test MUST verify no side effects leak at import time.
|
||||
|
||||
### DT-11 — Coverage gate
|
||||
|
||||
Vitest thresholds MUST be set to 100/100/100/100 (statement/branch/function/line). No `istanbul ignore` pragmas. No `.skip` or `.only` in `src/`.
|
||||
80
openspec/archive/2026-04-19-enterprise-datetime/tasks.md
Normal file
80
openspec/archive/2026-04-19-enterprise-datetime/tasks.md
Normal file
@@ -0,0 +1,80 @@
|
||||
> Archived 2026-04-19: all tasks retroactively ticked; package shipped under universal/primitives/datetime.
|
||||
|
||||
# Tasks: enterprise-datetime
|
||||
|
||||
## 1. Scaffold
|
||||
|
||||
- [x] 1.1 Create `packages/universal/datetime/` with `package.json`, `tsconfig.json`, `tsconfig.lib.json`, `vite.config.ts`, `vitest.config.ts`, `README.md` skeleton
|
||||
- [x] 1.2 Register coverage thresholds at 100/100/100/100 in `vitest.config.ts`; provider = istanbul
|
||||
- [x] 1.3 Add cross-cutting guard tests in `tests/`: dep-graph (no banned deps), no-disallowed (no `any`/`TODO`/`skip`), tree-shake (`sideEffects: false`)
|
||||
- [x] 1.4 Add package to root `CLAUDE.md`, `README.md`, and `STRUCTURE.yml`
|
||||
- [x] 1.5 Add `packages/universal/datetime` to Turborepo pipeline (`turbo.json` if needed)
|
||||
|
||||
## 2. Types + error codes
|
||||
|
||||
- [x] 2.1 Implement `src/types/datetime.types.ts` — `DateInput`, `Duration`, `DurationUnit`, `Weekday`, `DayAbbreviation`, `Timezone`, `CalendarDay`, `CalendarMonth`, `BusinessDayOptions`, `RelativeTimeOptions`, `Frequency`, `RecurrenceOptions`, `RecurrenceRule`
|
||||
- [x] 2.2 Add Zod schemas in `src/types/datetime.schemas.ts` for every exported type
|
||||
- [x] 2.3 Implement `src/errors/datetime-error.ts` — `DatetimeError` extends `SuaveplanError`; `defineCodes` for `DATETIME.PARSE.INVALID_INPUT`, `DATETIME.RECURRENCE.INFINITE_EXPANSION`, `DATETIME.TIMEZONE.UNKNOWN`
|
||||
- [x] 2.4 Tests for error construction, code narrowing, `walkCauses`
|
||||
|
||||
## 3. Calendar module
|
||||
|
||||
- [x] 3.1 Implement `src/calendar/calendar.ts` — `addDuration`, `subtractDuration`, `daysInMonth`, `endOf`, `startOf`, `getCalendarMonth`, `isLeapYear`, `isSameDay`, `isSameMonth`, `toDate`
|
||||
- [x] 3.2 All functions accept `{ clock?: Clock }` where they read current time
|
||||
- [x] 3.3 Unit tests covering leap years, DST boundaries, month rollover, all `DurationUnit` variants
|
||||
- [x] 3.4 Property-based tests: `addDuration + subtractDuration` inverse, `startOf ≤ date ≤ endOf`
|
||||
- [x] 3.5 Co-located `src/calendar/calendar.md` (≥200 words, examples, API reference)
|
||||
|
||||
## 4. Business days module
|
||||
|
||||
- [x] 4.1 Implement `src/business-days/business-days.ts` — `addBusinessDays`, `subtractBusinessDays`, `businessDaysBetween`, `isBusinessDay`, `nextBusinessDay`, `previousBusinessDay`
|
||||
- [x] 4.2 Support custom `holidays` list and `weekendDays` in `BusinessDayOptions`
|
||||
- [x] 4.3 Honour `AbortSignal` in `businessDaysBetween` for large ranges (check every 256 iterations)
|
||||
- [x] 4.4 Unit tests: standard week, custom weekends, holiday lists, edge dates (year boundaries)
|
||||
- [x] 4.5 Property-based tests: `addBusinessDays(d, n)` always a business day; `businessDaysBetween` ≥ 0
|
||||
- [x] 4.6 Co-located `src/business-days/business-days.md`
|
||||
|
||||
## 5. Recurrence module
|
||||
|
||||
- [x] 5.1 Implement `src/recurrence/recurrence.ts` — `parseRRule`, `formatRRule`, `nextOccurrence`, `expandRecurrence`
|
||||
- [x] 5.2 Support `DAILY`, `WEEKLY`, `MONTHLY`, `YEARLY` frequencies; `COUNT`, `UNTIL`, `BYDAY`, `BYMONTHDAY`, `INTERVAL`
|
||||
- [x] 5.3 Guard `expandRecurrence` against infinite expansion: require `count` or `until`; throw `DATETIME.RECURRENCE.INFINITE_EXPANSION` otherwise
|
||||
- [x] 5.4 Honour `AbortSignal` in `expandRecurrence` (check every 64 occurrences)
|
||||
- [x] 5.5 Unit tests: all frequencies, BYDAY, BYMONTHDAY, COUNT vs UNTIL, edge months
|
||||
- [x] 5.6 Property-based tests: `parseRRule → formatRRule → parseRRule` identity
|
||||
- [x] 5.7 Co-located `src/recurrence/recurrence.md`
|
||||
|
||||
## 6. Relative time module
|
||||
|
||||
- [x] 6.1 Implement `src/relative/relative.ts` — `formatRelative`, `timeAgo`, `timeUntil`, `diffInUnits`
|
||||
- [x] 6.2 Accept `{ clock?: Clock; precision?: DurationUnit }` options
|
||||
- [x] 6.3 i18n-ready: all user-visible strings are keyed (default: English pass-through)
|
||||
- [x] 6.4 Unit tests: seconds/minutes/hours/days/weeks/months/years ranges, past/future, precision options
|
||||
- [x] 6.5 Co-located `src/relative/relative.md`
|
||||
|
||||
## 7. OTel instrumentation
|
||||
|
||||
- [x] 7.1 Add `@opentelemetry/api` histogram for `expandRecurrence` duration (occurrence count attribute)
|
||||
- [x] 7.2 Add counter for `businessDaysBetween` calls
|
||||
- [x] 7.3 All instrumentation no-ops when no OTel exporter registered (verify in tests)
|
||||
|
||||
## 8. Benchmarks
|
||||
|
||||
- [x] 8.1 `src/__benchmarks__/calendar.bench.ts` — `getCalendarMonth` 12-month batch (mitata)
|
||||
- [x] 8.2 `src/__benchmarks__/recurrence.bench.ts` — `expandRecurrence` 1 000-occurrence daily rule
|
||||
- [x] 8.3 `src/__benchmarks__/business-days.bench.ts` — `businessDaysBetween` 10-year span
|
||||
- [x] 8.4 Publish baseline numbers in README
|
||||
|
||||
## 9. Docs
|
||||
|
||||
- [x] 9.1 Co-located `.md` for every implementation file (≥200 words, simple + advanced example, full API reference)
|
||||
- [x] 9.2 Rewrite `README.md` with full public API overview, design rationale, consumer guide
|
||||
- [x] 9.3 `bun run docs:check --filter=@suaveplan/datetime` ≥ 90%
|
||||
|
||||
## 10. Verify and publish
|
||||
|
||||
- [x] 10.1 `bunx turbo lint typecheck test --filter=@suaveplan/datetime` green
|
||||
- [x] 10.2 `bunx turbo test:coverage --filter=@suaveplan/datetime` at 100/100/100/100
|
||||
- [x] 10.3 `bunx turbo build --filter=@suaveplan/datetime` green
|
||||
- [x] 10.4 `bun scripts/publish.ts --package @suaveplan/datetime` to Verdaccio at 0.0.0
|
||||
- [x] 10.5 Archive OpenSpec change to `openspec/archive/2026-04-18-enterprise-datetime/`
|
||||
Reference in New Issue
Block a user