@arki/date for agents
For @arki/[email protected] · source docs/agent-guide.md
The long form of llms.txt. When they disagree, src/index.ts wins.
The model
- Five value types:
PlainDate(a day),PlainDateTime(a wall clock),ZonedDateTime(an exact moment in a zone),Instant(an exact moment) andDuration. - Pure functions, one subpath per module.
date(...)from/fluentwraps aZonedDateTimefor chains. - A
Dateis an instant that pretends to be a calendar date. Convert at the edge withfromDate,toDate,fromPickerDateandtoPickerDate. - Time comes from a
Clockyou pass. The zone comes from an argument. - Invalid input throws.
tryParse*returnsundefined.
Task map
Count business days with holidays
- Build a calendar once:
businessCalendar({ holidays: [...] }). - Call
addBusinessDays(plainDate(...), n, calendar). - Read the order day in the shop's zone first (
zoned, thentoPlainDate).
The tested version is examples/delivery-estimate.ts in the repository.
import { addBusinessDays, businessCalendar, plainDate } from '@arki/date';
const calendar = businessCalendar({ holidays: ['2026-12-01', '2026-12-25'] });
const due = addBusinessDays(plainDate('2026-11-27'), 3, calendar);
if (String(due) !== '2026-12-03') throw new Error(String(due));Add a month without surprises
- Use
add(value, { months: 1 }). It clamps: Jan 31 plus one month is Feb 28. - Pass
{ overflow: 'reject' }when a clamp would be a bug.
import { add, plainDate } from '@arki/date';
if (String(add(plainDate('2026-01-31'), { months: 1 })) !== '2026-02-28') throw new Error('clamp');Show a moment in the user's zone
- Make an
Instantfrom the stored value. - Call
inZone(value, user.timeZone)orzoned(value, user.timeZone). - Format with
format(value, 'yyyy-MM-dd HH:mm')orformatIntl(value, { dateStyle: 'long', locale }).
Test code that depends on time
- Take a
clockoption or parameter. PassSystemClockin production. - In the test, pass a
MockClockfrom@arki/clockand calladvance. - Do not look for a global
setTestNow. There is none.
Parse user text
- Use
tryParseDate(text, 'yyyy-MM-dd'). It returnsundefinedfor bad input. - For ISO text in many shapes, use
parseISO. Zone-less text is read in local time.
Send values over the wire
- oRPC and SuperJSON: register the codecs from
@arki/date/orpcon both ends. - TanStack Start: register
temporalSerializationAdaptersfrom@arki/date/tanstackonce. - Validation: use the schemas in
@arki/date/zod.
Rules that bite
new Date('2026-05-20')is May 19 in the Americas. KeepDateat the edges.zoned('2026-05-20T09:30')throws. Pass a zone.diff(from, to)isto - from. Whole units truncate toward zero.diffcounts calendar units.diffElapsedcounts exact time.- Weeks start on Monday and week 1 holds January 4, unless you pass
{ week: SUNDAY_WEEK }. - Intervals are half-open.
eachthrows pastmaxItems. - Never let the server's zone leak into output. Pass the user's zone and locale.
- The optional subpaths are not in the root: import
@arki/date/zod,/react,/orpc,/tanstack.
Errors
| Code | Meaning | What to do |
|---|---|---|
| none | The package defines no error codes. | Catch the built-in error types below. |
RangeError | A value is out of range, or a zone is unknown, or reject was chosen. | Fix the input, or use a tryParse* function. |
TypeError | Two values of different kinds were mixed. | Convert one with toPlainDate, toInstant or inZone. |
Verify
bun run test
bun run test:docs
bun ../../tooling/library/src/llms.ts --check