Skip to content

feat(intl)!: make relativetime() take a moment and pick the unit by the distance - #287

Merged
dangreen merged 1 commit into
mainfrom
feat/intl-relativetime-moment
Oct 7, 2026
Merged

dangreen merged 1 commit into
mainfrom
feat/intl-relativetime-moment

Conversation

@dangreen

@dangreen dangreen commented Oct 7, 2026

Copy link
Copy Markdown
Member

Why

relativetime() took a count of one fixed unit, a thin wrapper over Intl.RelativeTimeFormat.prototype.format(value, unit). Everything that makes relative time useful stayed with the app: the distance to now, the unit and the rounding. An activity list that shows "5 minutes ago" or "yesterday" kept seven relativetime() formatters, one per unit, a table of unit lengths and a function to pick one, and its messages still did not follow the clock. A count of units is rare in real data, which carries timestamps: even the docs example formatStartsIn(1) leaves the app to count the days.

What

  • relativetime() takes a moment, like datetime(): a date, a timestamp or a date string.
  • It formats the distance to relativeTo in the largest unit the distance fills at least once, truncated, as since() and until() of Temporal round by default. A month counts as 30 days and a year as 365: the distance picks the unit, not the calendar.
  • largestUnit and smallestUnit, named as in Temporal, bound the unit. By default the range runs from year to second, and both set to one unit format in it. quarter is not in the range.
  • relativeTo, named as in Temporal, is the moment to count from, the current time by default. A signal or an accessor is read inside the message, so the message follows a ticking clock, for example a computed over interval().
  • Zero of a time unit, from hours down, reads as "now", and zero of a calendar unit as "today" or "this week". With numeric: 'auto', Intl.RelativeTimeFormat writes zero minutes as "this minute" and zero hours as "this hour", which a list does not show.
  • numeric, style and localeMatcher go to Intl.RelativeTimeFormat as before.
  • The spec of relativetime is rewritten for the new input: 13 cases cover the unit choice, both bounds, one unit, zero, dates and date strings, the default clock, a signal clock, the raw mode and fallbacks.

With numeric: 'auto' in en:

Moment Default smallestUnit: 'minute' smallestUnit: 'day'
20 seconds ago 20 seconds ago now today
5 minutes ago 5 minutes ago 5 minutes ago today
30 hours ago yesterday yesterday yesterday
10 days ago last week last week last week
in 3 days in 3 days in 3 days in 3 days

Breaking change

relativetime() takes a moment instead of a count of units, and the unit option is gone. Keeping unit with the new input would turn formatStartsIn(1) into "56 years ago" without a type error, so a call that passes unit no longer compiles. Format in one unit with largestUnit and smallestUnit set to it, and format a count of units with Intl.RelativeTimeFormat or a plural() message. The release PR #227 prepares 2.0.0 already.

Size

All publics of intl grow by 203 B gzip, 2031 → 2234 B, and by 186 B brotli, 1888 → 2074 B, so their limits go up to 2.25 and 2.1 kB. The minimal and basic sets do not include relativetime() and do not change, and an app that does not import it pays nothing. On an esbuild bench of the whole of intl, the app code that picks the unit over the current relativetime() adds 284 B gzip, and this change adds 206 B. Writing the unit lengths as products of literals changes nothing under the rolldown of size-limit, and named constants cost 7 B more.

Notes

  • Left to the app: calendar arithmetic, where "yesterday" means before midnight rather than 24 hours ago and depends on a time zone that differs on the server, so with smallestUnit: 'day' an event 23 hours ago reads "today". The same goes for thresholds in the style of moment, where 45 seconds already count as a minute, and for a switch to an absolute date after a threshold, as threshold of GitHub's relative-time does.
  • The docs describe the released package and are not touched. The relativetime section of intl/api.mdx and its formatStartsIn(1) example need an update before the 2.0.0 release.
  • oxlint, tsc and the unit tests of intl pass, 133 of them, and so does the pre-push pnpm test.

… the distance

`relativetime()` took a count of one fixed unit, so the app measured the distance to now, picked the unit and rounded on its own: a list showing "5 minutes ago" or "yesterday" kept a formatter per unit and a table of their lengths. It now takes a moment, like `datetime()`, and formats the distance to `relativeTo`, the current time by default, in the largest unit between `largestUnit` and `smallestUnit` that the distance fills, truncated. `relativeTo` may be a signal or an accessor, so a message follows a ticking clock. A month counts as 30 days and a year as 365. Zero of a time unit, from hours down, reads as "now", and zero of a calendar unit as "today" or "this week", as `Intl.RelativeTimeFormat` writes them with `numeric: 'auto'`.

BREAKING CHANGE: `relativetime()` takes a moment, a date, a timestamp or a date string,
instead of a count of units, and its `unit` option is gone, so a call that passes it no
longer compiles. Format in one unit with `largestUnit` and `smallestUnit` set to it, and
format a count of units with `Intl.RelativeTimeFormat` or a `plural()` message.
@codecov

codecov Bot commented Oct 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.22%. Comparing base (4010477) to head (0cae614).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #287      +/-   ##
==========================================
+ Coverage   83.20%   83.22%   +0.02%     
==========================================
  Files         228      228              
  Lines        4823     4835      +12     
  Branches     1065     1071       +6     
==========================================
+ Hits         4013     4024      +11     
- Misses        574      575       +1     
  Partials      236      236              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@dangreen
dangreen merged commit cdf1238 into main Oct 7, 2026
11 checks passed
@dangreen
dangreen deleted the feat/intl-relativetime-moment branch October 7, 2026 14:11
@github-actions github-actions Bot mentioned this pull request Oct 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant