first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,84 @@
# relative
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
The `relative` module provides human-readable relative-time formatting for `@suaveplan/datetime`. It converts the distance between two moments into concise English phrases like `"3 days ago"`, `"in 2 hours"`, or `"just now"`. The `clock` injection point makes all functions deterministic in tests, while the unified `formatRelative` entry point subsumes the common `timeAgo` / `timeUntil` aliases.
The module also exposes `diffInUnits`, a lower-level utility that returns the signed numeric difference between two `DateInput` values in any `DurationUnit`. This is useful when callers need the raw integer delta rather than a formatted string.
## Features
- `diffInUnits` — signed numeric difference in any `DurationUnit`
- `formatRelative` — threshold-based English string (past and future)
- `timeAgo` — convenience alias for past-relative formatting
- `timeUntil` — convenience alias for future-relative formatting
- Pluggable `Clock` for deterministic rendering in tests
- `"just now"` threshold for differences under 45 seconds
## Usage: Basic Example
```ts
import { timeAgo, timeUntil } from "@suaveplan/datetime/relative";
// Using wall clock
console.log(timeAgo(new Date(Date.now() - 3 * 60_000))); // "3 minutes ago"
console.log(timeUntil(new Date(Date.now() + 2 * 3600_000))); // "in 2 hours"
```
## Usage: Advanced Example
```ts
import { diffInUnits, formatRelative } from "@suaveplan/datetime/relative";
// Deterministic with a fake clock
const clock = () => new Date("2024-06-01T12:00:00Z").getTime();
const pastDate = "2024-05-28T12:00:00Z";
console.log(formatRelative(pastDate, { clock })); // "4 days ago"
console.log(diffInUnits(pastDate, "2024-06-01T12:00:00Z", "days")); // 4
// Sub-unit precision
const oneHourAgo = new Date(clock() - 3723_000); // 1h 2m 3s ago
console.log(formatRelative(oneHourAgo, { clock })); // "1 hour ago"
console.log(diffInUnits(oneHourAgo, new Date(clock()), "seconds")); // 3723
```
## API Reference
### `diffInUnits(a: DateInput, b: DateInput, unit: DurationUnit): number`
Returns `floor((b − a) / unitMs)` truncated toward zero. Positive when `b > a`.
- `a` — start date (any `DateInput`)
- `b` — end date (any `DateInput`)
- `unit` — one of the `DurationUnit` values
### `formatRelative(date: DateInput, opts?: RelativeTimeOptions): string`
Format the difference between `date` and `opts.clock()` (default: `Date.now()`).
- Distances under 45 seconds → `"just now"`
- Distances under 45 minutes → `"N seconds ago"` / `"in N seconds"`
- Distances under 22 hours → `"N minutes ago"` / `"in N minutes"`
- Distances under 26 days → `"N hours ago"` / `"in N hours"`
- Distances under 11 months → `"N days ago"` / `"in N days"`
- Otherwise → `"N months ago"` / `"in N months"` or `"N years ago"` / `"in N years"`
### `timeAgo(date: DateInput, opts?: RelativeTimeOptions): string`
Alias for `formatRelative`. Intended for past dates, but works symmetrically.
### `timeUntil(date: DateInput, opts?: RelativeTimeOptions): string`
Alias for `formatRelative`. Intended for future dates, but works symmetrically.
## Implementation Notes
Average month (30.4375 days) and average year (365.25 days) constants are used for the coarse `months` / `years` thresholds. These are approximations intentionally — precise calendar arithmetic for display strings would add complexity without improving user experience. Use `diffInUnits("months")` together with calendar arithmetic when you need exact month counts.
The threshold table mirrors the conventions used by popular libraries like `date-fns` and `moment.js` so that migrating code produces the same user-visible strings.
The `precision` field in `RelativeTimeOptions` is reserved for future use to cap the label at a specific unit (e.g. only show up to "days", never "hours"). Currently it is accepted but not applied in the implementation.