npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

relative-time-live

v0.1.0

Published

Self-updating human relative time ("2 minutes ago") that live-ticks, backed by Intl.RelativeTimeFormat. i18n-ready, framework-agnostic with optional React/Vue bindings.

Readme

relative-time-live

Self-updating, human-friendly relative time ("2 minutes ago", "in 3 hours") that live-ticks, backed by the native Intl.RelativeTimeFormat. i18n-ready, framework-agnostic, with optional React and Vue bindings.

  • Live updating with a single shared scheduler — many subscriptions, one timer.
  • Deterministic & testable — inject now, never hidden Date.now() in pure paths.
  • Dual ESM + CJS builds with full .d.ts types.
  • Tree-shakeable (sideEffects: false).
npm install relative-time-live

Quick start

import { relativeTime } from "relative-time-live";

relativeTime(Date.now() - 2 * 60_000); // "2 minutes ago"
relativeTime(Date.now() + 3 * 3_600_000); // "in 3 hours"
relativeTime("2026-05-30T12:00:00Z", { now: Date.parse("2026-05-31T12:00:00Z") });
// "yesterday" (numeric: "auto" default)

Live updating in the browser:

import { liveRelativeTime } from "relative-time-live";

const stop = liveRelativeTime(someDate, (text) => {
  el.textContent = text; // recomputed on an automatic cadence
});
// later:
stop();

Accepted date inputs

Everywhere a date is accepted you may pass:

  • a Date instance,
  • a number of milliseconds since the Unix epoch,
  • a string parseable by Date.parse (ISO 8601 recommended).

Invalid inputs throw a TypeError.

API

relativeTime(date, options?) => string

Formats date relative to now using Intl.RelativeTimeFormat, auto-selecting the best unit (seconds → minutes → hours → days → weeks → months → years).

relativeTime(date, {
  locale: "en",          // BCP-47 locale or array; default: runtime default
  style: "long",         // "long" | "short" | "narrow"; default "long"
  numeric: "auto",       // "auto" | "always"; default "auto"
  now: 1748000000000,    // number | Date | (() => number); default: live clock
  round: "round",        // "round" | "floor" | "ceil"; default "round"
  minUnit: "second",     // smallest unit to use; default "second"
  maxUnit: "year",       // largest unit to use; default "year"
  thresholds: {          // see below; "just now" handling
    justNow: 5,          // seconds: |diff| < 5s -> justNowText (set 0 to disable)
    justNowText: "just now",
  },
});

Options

| Option | Type | Default | Notes | |--------------|-------------------------------------------------|--------------------|-------| | locale | string \| string[] | runtime default | Passed to Intl.RelativeTimeFormat. | | style | "long" \| "short" \| "narrow" | "long" | "short" → "2 min. ago", "narrow" → "2m ago" (locale dependent). | | numeric | "auto" \| "always" | "auto" | "auto" yields "yesterday"/"tomorrow"/"today"; "always" yields "1 day ago". | | now | number \| Date \| (() => number) | live clock fn | Inject for tests. A function is called each computation. | | round | "round" \| "floor" \| "ceil" | "round" | How fractional unit values are reduced to integers. | | minUnit | Unit | "second" | Values smaller than this clamp up to it. | | maxUnit | Unit | "year" | Values larger than this clamp down to it. | | thresholds | { justNow?: number; justNowText?: string } | { justNow: 5 } | If |diff| (seconds) < justNow, returns justNowText (localized via locale pack). |

Unit is "second" | "minute" | "hour" | "day" | "week" | "month" | "year".

The justNowText resolves in this order: explicit thresholds.justNowText → the registered locale pack's justNow string → "just now".

calendar(date, options?) => string

Calendar-style phrasing relative to now:

  • same day → "Today at 3:00 PM"
  • previous day → "Yesterday at 3:00 PM"
  • next day → "Tomorrow at 3:00 PM"
  • within the next/previous 6 days → weekday, e.g. "Monday at 3:00 PM"
  • otherwise → absolute date fallback, e.g. "05/24/2026".
calendar(date, {
  locale: "en",
  now: someNow,                 // same as relativeTime
  timeStyle: "short",           // Intl.DateTimeFormat timeStyle for the "at <time>" part
  dateStyle: "short",           // Intl.DateTimeFormat dateStyle for the fallback
  withTime: true,               // append " at <time>" to Today/Yesterday/Tomorrow/weekday
  formats: {                    // override any bucket's template; {time}/{weekday} placeholders
    sameDay: "Today at {time}",
    nextDay: "Tomorrow at {time}",
    lastDay: "Yesterday at {time}",
    nextWeek: "{weekday} at {time}",
    lastWeek: "{weekday} at {time}",
    // sameElse uses the dateStyle fallback
  },
});

Templates and words (Today/Yesterday/Tomorrow) come from the locale pack and may be overridden per-call via formats.

range(start, end, options?) => string

Humanizes the duration between two dates as a compound string.

range(start, end); // "3 days"
range(start, end, { units: 2 }); // "2 hours 5 minutes"
range(start, end, {
  locale: "en",
  units: 1,               // max number of unit components to include; default 1
  minUnit: "second",
  maxUnit: "year",
  style: "long",          // "long" | "short" | "narrow" (Intl.NumberFormat unit style)
  separator: " ",         // joins components
});

Order of start/end does not matter; the magnitude of the duration is used.

nextUpdateDelay(date, options?) => number

Returns the number of milliseconds until the string produced by relativeTime would next change, given the current now. Used internally by the live scheduler; exported for advanced use.

nextUpdateDelay(date, { now }); // e.g. 1000 when <1min old, 60000 when <1hr, etc.

The cadence:

  • diff < 1 minute → updates every second (delay ≤ 1000 ms),
  • diff < 1 hour → updates each minute,
  • diff < 1 day → updates each hour,
  • beyond → updates each day.

The returned delay is the time until the next boundary, never larger than the cadence and never less than ~250 ms.

liveRelativeTime(date, callback, options?) => () => void

Subscribes to live updates. Immediately invokes callback(text) once, then again each time the displayed string would change, on an automatic, self-correcting cadence. Returns an unsubscribe function. All subscriptions share one timer via the global ticker.

const stop = liveRelativeTime(date, (text) => (el.textContent = text), { style: "short" });
stop(); // unsubscribe

createTicker() => Ticker

Creates an independent scheduler for advanced control (e.g. custom timing source or isolation in tests).

interface Ticker {
  subscribe(
    date: DateInput,
    callback: (text: string) => void,
    options?: RelativeTimeOptions,
  ): () => void;
  /** Force recompute of all subscriptions now. */
  flush(): void;
  /** Stop the underlying timer and clear all subscriptions. */
  stop(): void;
  /** Number of active subscriptions. */
  readonly size: number;
}

import { createTicker } from "relative-time-live";
const ticker = createTicker();

DOM helper: liveTimes(root?, options?) => () => void

Scans root (default document.body) for elements carrying a datetime attribute (as on <time datetime="...">) or a data-time attribute, and keeps their textContent set to the live relative time. Returns a stop function that removes all subscriptions. New elements are not auto-discovered; call again or re-run after DOM changes.

<time datetime="2026-05-31T11:58:00Z"></time>
<span data-time="1748000000000"></span>
import { liveTimes } from "relative-time-live"; // also at "relative-time-live/dom"
const stop = liveTimes(document.body, { style: "short" });

Per-element overrides via attributes: data-style, data-numeric, data-locale.

React

Import from relative-time-live/react. React >=17 is an optional peer dependency.

useRelativeTime(date, options?) => string

import { useRelativeTime } from "relative-time-live/react";

function Ago({ date }: { date: Date }) {
  const text = useRelativeTime(date, { style: "short" });
  return <span>{text}</span>;
}

Re-renders the component on each tick. Uses the shared global ticker.

<RelativeTime value={...} ... />

Renders a semantic <time> element with a machine-readable dateTime attribute and live-updating text content.

import { RelativeTime } from "relative-time-live/react";

<RelativeTime value={date} style="short" numeric="auto" locale="en" />;
// <time datetime="2026-05-31T11:58:00.000Z">2 minutes ago</time>

Props: value (required DateInput) plus all RelativeTimeOptions, plus any standard <time> attributes (className, title, …). The dateTime attribute is set automatically and may be overridden.

Vue

Import from relative-time-live/vue. Vue >=3 is an optional peer dependency.

useRelativeTime(date, options?) => Ref<string>

import { useRelativeTime } from "relative-time-live/vue";

const text = useRelativeTime(someDate, { style: "short" });

date and options may be plain values, refs, or getters; the composable reacts to changes and re-subscribes. Cleans up on unmount.

RelativeTime component

<script setup>
import { RelativeTime } from "relative-time-live/vue";
</script>

<template>
  <RelativeTime :value="date" style="short" numeric="auto" />
</template>

Renders a <time :datetime> element with live text.

i18n

Relative phrases ("2 minutes ago") come entirely from Intl.RelativeTimeFormat, so any locale the runtime supports works via the locale option. Only the small set of non-Intl words ("just now", calendar templates, weekday/Today/Yesterday wording is taken from Intl where possible) are sourced from locale packs.

An English pack ships built-in. Register more:

import { registerLocale } from "relative-time-live";

registerLocale("es", {
  justNow: "justo ahora",
  calendar: {
    sameDay: "hoy a las {time}",
    nextDay: "mañana a las {time}",
    lastDay: "ayer a las {time}",
    nextWeek: "{weekday} a las {time}",
    lastWeek: "{weekday} pasado a las {time}",
  },
});

Lookup is by exact tag then primary subtag (es-MXesen).

TypeScript

The package is written in strict TypeScript and ships types for every entry.

import type {
  DateInput,
  Unit,
  RelativeTimeOptions,
  CalendarOptions,
  RangeOptions,
  Ticker,
  LocalePack,
} from "relative-time-live";
type DateInput = Date | number | string;
type Unit = "second" | "minute" | "hour" | "day" | "week" | "month" | "year";

Edge cases

  • Invalid dates throw TypeError.
  • diff === 0 (or within justNow threshold) → "just now".
  • Future vs past is handled by sign; relativeTime uses Intl's "in …"/"… ago".
  • numeric: "auto" can yield non-numeric output ("yesterday"); this only happens for ±1 of a unit per Intl rules.
  • SSR / no DOM: the core and React/Vue hooks never touch document. Only liveTimes requires a DOM.
  • Timers: live updates use one shared setTimeout chain; subscriptions are recomputed at the soonest needed boundary. Unsubscribing the last subscriber stops the timer.

License

MIT © udayps