How to format relative time in Next.js (Pages Router) v16

Format timestamps as '2 days ago' phrases

Problem

Displaying timestamps as relative time phrases like "2 days ago" or "in 3 hours" makes content feel immediate and contextual. However, these phrases follow complex grammatical patterns that vary dramatically across languages. English places "ago" after past quantities and "in" before future ones, but other languages may use different word orders, inflect time units based on quantity, or employ entirely different grammatical structures. Manually constructing these phrases with string concatenation produces incorrect output in every language except the one it was written for, breaking the user experience for international audiences.

Solution

Use react-intl's relative time formatting to convert numeric time differences into grammatically correct phrases for the user's locale. Calculate the time difference between a timestamp and the current moment, determine the appropriate unit (seconds, minutes, hours, days), and pass both values to react-intl's formatting APIs. The underlying Intl.RelativeTimeFormat API handles locale-specific grammar, word order, and inflection rules automatically.

Steps

1. Create a helper to calculate relative time values

The time difference must be converted into a numeric value and an appropriate unit. Build a function that compares a timestamp to the current time and selects the best unit.

export function getRelativeTimeValue(date: Date | number) {
  const now = Date.now();
  const timestamp = typeof date === "number" ? date : date.getTime();
  const diffInSeconds = (timestamp - now) / 1000;

  const minute = 60;
  const hour = minute * 60;
  const day = hour * 24;
  const week = day * 7;
  const month = day * 30;
  const year = day * 365;

  const absDiff = Math.abs(diffInSeconds);

  if (absDiff < minute) {
    return { value: Math.round(diffInSeconds), unit: "second" as const };
  } else if (absDiff < hour) {
    return {
      value: Math.round(diffInSeconds / minute),
      unit: "minute" as const,
    };
  } else if (absDiff < day) {
    return { value: Math.round(diffInSeconds / hour), unit: "hour" as const };
  } else if (absDiff < week) {
    return { value: Math.round(diffInSeconds / day), unit: "day" as const };
  } else if (absDiff < month) {
    return { value: Math.round(diffInSeconds / week), unit: "week" as const };
  } else if (absDiff < year) {
    return { value: Math.round(diffInSeconds / month), unit: "month" as const };
  } else {
    return { value: Math.round(diffInSeconds / year), unit: "year" as const };
  }
}

This function returns a signed numeric value (negative for past, positive for future) and the most appropriate unit based on the magnitude of the difference.

2. Format relative time with the useIntl hook

The formatRelativeTime function returns a formatted relative time string and expects a numeric value, a unit, and options that conform to Intl.RelativeTimeFormatOptions. Use the helper from step 1 to get the value and unit, then format it.

import { useIntl } from "react-intl";
import { getRelativeTimeValue } from "@/lib/relative-time";

export default function CommentTimestamp({ date }: { date: Date }) {
  const intl = useIntl();
  const { value, unit } = getRelativeTimeValue(date);

  return (
    <time dateTime={date.toISOString()}>
      {intl.formatRelativeTime(value, unit)}
    </time>
  );
}

The formatRelativeTime method produces locale-appropriate phrases like "2 days ago" in English or "il y a 2 jours" in French.

3. Use FormattedRelativeTime for declarative formatting

FormattedRelativeTime implements advanced features like updating over time and uses the formatRelativeTime API with props for value, unit, and relative formatting options. For components that need automatic updates, use the component form.

import { FormattedRelativeTime } from "react-intl";
import { getRelativeTimeValue } from "@/lib/relative-time";

export default function LiveTimestamp({ date }: { date: Date }) {
  const { value, unit } = getRelativeTimeValue(date);

  return (
    <time dateTime={date.toISOString()}>
      <FormattedRelativeTime
        value={value}
        unit={unit}
        numeric="auto"
        updateIntervalInSeconds={10}
      />
    </time>
  );
}

The updateIntervalInSeconds prop controls how frequently the component re-renders, keeping the displayed phrase current as time passes.

4. Customize formatting style

The component accepts RelativeTimeFormatOptions including numeric and style properties. Adjust these options to control output format.

import { FormattedRelativeTime } from "react-intl";
import { getRelativeTimeValue } from "@/lib/relative-time";

export default function CompactTimestamp({ date }: { date: Date }) {
  const { value, unit } = getRelativeTimeValue(date);

  return (
    <FormattedRelativeTime
      value={value}
      unit={unit}
      numeric="auto"
      style="narrow"
    />
  );
}

Setting numeric="auto" produces phrases like "yesterday" instead of "1 day ago" when appropriate, and style="narrow" creates shorter forms like "2 mo. ago" instead of "2 months ago".