So formatierst du relative Zeitangaben in TanStack Start v1

Zeitstempel als Phrasen wie „vor 2 Tagen“ formatieren

Problem

Zeitstempel als relative Zeitangaben wie „vor 2 Tagen“ oder „in 3 Stunden“ darzustellen, macht zeitliche Informationen für Nutzer:innen intuitiver verständlich. Allerdings folgen diese Phrasen komplexen grammatikalischen Regeln, die sich je nach Sprache unterscheiden. Im Englischen steht „ago“ nach Mengenangaben in der Vergangenheit und „in“ vor zukünftigen Zeitangaben, während andere Sprachen andere Wortstellungen, gebeugte Zeiteinheiten oder ganz andere grammatische Strukturen verwenden. Wer diese Phrasen manuell mit String-Verkettung zusammensetzt, erhält in jeder Sprache außer der hartcodierten falsche Ausgaben.

Die zugrundeliegende Formatierungs-API erwartet einen numerischen Wert und eine Zeiteinheit, aber Zeitstempel liegen meist als Date-Objekte oder Millisekundenwerte vor. Um einen Zeitstempel in die passende Einheit und den richtigen Wert umzuwandeln, muss die Differenz zum aktuellen Zeitpunkt berechnet und die natürlichste Einheit zur Darstellung dieser Differenz gewählt werden.

Lösung

Berechne die Zeitdifferenz zwischen einem Zeitstempel und dem aktuellen Zeitpunkt, wähle die passendste Einheit für diese Differenz und formatiere das Ergebnis mit einer lokalisierungsspezifischen relativen Zeitformatierung. So werden rohe Zeitstempel in grammatikalisch korrekte Phrasen umgewandelt, die sich an die Sprache der Nutzer:innen anpassen.

Nutze das relative Zeitformat von react-intl zusammen mit einer Hilfsfunktion, die die beste Einheit bestimmt. Die Hilfsfunktion vergleicht die Zeitdifferenz mit Schwellenwerten für jede Einheit und gibt sowohl den numerischen Wert als auch den Namen der Einheit zurück. Der Formatter wendet dann die korrekten grammatikalischen Regeln für die aktive Sprache an.

Schritte

1. Hilfsfunktion zur Einheitsauswahl erstellen

Erstelle eine Funktion, die die Zeitdifferenz berechnet und die natürlichste Einheit zur Darstellung auswählt.

type RelativeTimeUnit =
  | "second"
  | "minute"
  | "hour"
  | "day"
  | "week"
  | "month"
  | "year";

interface RelativeTimeValue {
  value: number;
  unit: RelativeTimeUnit;
}

export function selectRelativeTimeUnit(
  timestamp: Date | number,
  baseTime: Date | number = Date.now(),
): RelativeTimeValue {
  const date = typeof timestamp === "number" ? timestamp : timestamp.getTime();
  const base = typeof baseTime === "number" ? baseTime : baseTime.getTime();
  const diffMs = date - base;
  const absDiff = Math.abs(diffMs);

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

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

Diese Funktion wandelt einen Zeitstempel in einen relativen Wert und eine Einheit um, indem sie die Zeitdifferenz mit festen Schwellenwerten vergleicht. Sie gibt negative Werte für vergangene Zeiten und positive Werte für zukünftige Zeiten zurück, die der Formatter korrekt interpretiert.

2. Erstellen Sie eine Komponente für relative Zeitangaben

Erstellen Sie eine Komponente, die den Einheitenauswahl-Helper mit der Formatierung von react-intl kombiniert.

import { FormattedRelativeTime } from "react-intl";
import { selectRelativeTimeUnit } from "./selectRelativeTimeUnit";

interface RelativeTimeProps {
  date: Date | number;
  numeric?: "always" | "auto";
  style?: "long" | "short" | "narrow";
}

export function RelativeTime({
  date,
  numeric = "auto",
  style = "long",
}: RelativeTimeProps) {
  const { value, unit } = selectRelativeTimeUnit(date);

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

Diese Komponente akzeptiert einen Zeitstempel und Formatierungsoptionen, berechnet den relativen Wert und die Einheit und delegiert dann an die Komponente von react-intl für locale-bewusstes Rendering. Die Option numeric="auto" erzeugt Phrasen wie "gestern" anstelle von "vor 1 Tag", wenn dies angemessen ist.

3. Verwenden Sie die Komponente in Ihren Routes

Importieren und rendern Sie die Komponente überall dort, wo Sie relative Zeitstempel anzeigen müssen.

import { createFileRoute } from "@tanstack/react-router";
import { RelativeTime } from "../components/RelativeTime";

export const Route = createFileRoute("/posts/$postId")({
  component: PostPage,
});

function PostPage() {
  const post = {
    title: "Understanding Relative Time",
    publishedAt: new Date("2024-11-15T10:30:00Z"),
    updatedAt: new Date(Date.now() - 2 * 60 * 60 * 1000),
  };

  return (
    <article>
      <h1>{post.title}</h1>
      <p>
        Published <RelativeTime date={post.publishedAt} />
      </p>
      <p>
        Updated <RelativeTime date={post.updatedAt} style="short" />
      </p>
    </article>
  );
}

Die Komponente funktioniert sowohl in server-gerenderten als auch in client-gerenderten Kontexten. Auf dem Server erzeugt sie die initiale relative Zeitphrase, und auf dem Client zeigt sie dieselbe Phrase unter Verwendung der Locale des Benutzers aus dem IntlProvider an.

4. Fügen Sie eine imperative Formatierungsoption hinzu

Erstellen Sie für Fälle, in denen Sie den formatierten String direkt benötigen, einen Hook-basierten Helper.

import { useIntl } from "react-intl";
import { selectRelativeTimeUnit } from "./selectRelativeTimeUnit";

export function useRelativeTime() {
  const intl = useIntl();

  return (
    date: Date | number,
    options?: {
      numeric?: "always" | "auto";
      style?: "long" | "short" | "narrow";
    },
  ) => {
    const { value, unit } = selectRelativeTimeUnit(date);
    return intl.formatRelativeTime(value, unit, options);
  };
}

Dieser Hook gibt eine Funktion zurück, die Zeitstempel imperativ formatiert, nützlich für das Setzen von Textattributen oder das Berechnen von Werten außerhalb von JSX.

5. Verwenden Sie den Hook für Nicht-Komponenten-Kontexte

Rufen Sie den Hook in Komponenten auf, in denen Sie formatierte Strings für Attribute oder Logik benötigen.

import { createFileRoute } from "@tanstack/react-router";
import { useRelativeTime } from "../hooks/useRelativeTime";

export const Route = createFileRoute("/events/$eventId")({
  component: EventPage,
});

function EventPage() {
  const formatRelativeTime = useRelativeTime();
  const event = {
    name: "Product Launch",
    startTime: new Date(Date.now() + 3 * 24 * 60 * 60 * 1000),
  };

  const timeUntil = formatRelativeTime(event.startTime);

  return (
    <div>
      <h1>{event.name}</h1>
      <time dateTime={event.startTime.toISOString()} title={timeUntil}>
        {timeUntil}
      </time>
    </div>
  );
}

Der Hook bietet dieselbe Formatierungslogik in funktionaler Form und ermöglicht es Ihnen, relative Zeitstrings in Attributen, berechneten Werten oder überall dort zu verwenden, wo eine Komponente nicht gerendert werden kann.