Comment gérer les fuseaux horaires dans TanStack Start v1

Afficher les heures dans le fuseau horaire local de l'utilisateur

Problème

Lorsque les applications affichent des heures, elles représentent un moment précis qui apparaît différemment selon l'emplacement de l'utilisateur. Si un serveur affiche "20h00" dans son propre fuseau horaire ou en UTC, les utilisateurs situés dans différents fuseaux horaires verront une heure locale incorrecte, ce qui entraîne une confusion quant au moment réel des événements. Les utilisateurs s'attendent à ce que les heures reflètent automatiquement leur fuseau horaire local, mais sans traitement approprié, ils voient les heures dans le fuseau horaire utilisé par le serveur lors du rendu. Cette discordance amène les utilisateurs à manquer des rendez-vous, à mal comprendre les horaires et à perdre confiance dans la fiabilité de l'application.

Solution

Transmettez une option timeZone aux méthodes de formatage de date pour contrôler comment les dates sont interprétées et affichées. La fonction formatDate accepte Intl.DateTimeFormatOptions qui inclut la configuration du fuseau horaire. Stockez les horodatages dans un format universel comme les chaînes ISO 8601 ou les timestamps Unix, puis formatez-les côté client où le fuseau horaire du navigateur est automatiquement disponible. L'API Intl.DateTimeFormat utilise le fuseau horaire par défaut de l'utilisateur lorsqu'aucune option timeZone n'est spécifiée, garantissant ainsi que les heures s'affichent correctement pour l'emplacement de chaque utilisateur sans détection manuelle du fuseau horaire.

Étapes

1. Créer un composant de formatage de date tenant compte du fuseau horaire

Utilisez le hook useIntl pour accéder à l'objet intl et sa méthode formatDate. Ce composant accepte un horodatage ISO et le formate pour le fuseau horaire local de l'utilisateur.

import { useIntl } from "react-intl";

interface LocalTimeProps {
  timestamp: string;
  showDate?: boolean;
  showTime?: boolean;
}

export function LocalTime({
  timestamp,
  showDate = true,
  showTime = true,
}: LocalTimeProps) {
  const intl = useIntl();
  const date = new Date(timestamp);

  const formatted = intl.formatDate(date, {
    ...(showDate && {
      year: "numeric",
      month: "short",
      day: "numeric",
    }),
    ...(showTime && {
      hour: "numeric",
      minute: "2-digit",
      timeZoneName: "short",
    }),
  });

  return <time dateTime={timestamp}>{formatted}</time>;
}

Le composant FormattedDate et la méthode formatDate utilisent les API Intl.DateTimeFormat avec DateTimeFormatOptions. Le navigateur applique automatiquement le fuseau horaire de l'utilisateur lorsqu'aucun timeZone explicite n'est fourni, convertissant ainsi l'horodatage UTC en heure locale.

2. Utiliser le composant pour afficher les heures d'événements

Importez et utilisez le composant LocalTime partout où vous avez besoin d'afficher des horodatages. Le composant gère automatiquement la conversion de fuseau horaire en fonction des paramètres du navigateur de chaque utilisateur.

import { createFileRoute } from "@tanstack/react-router";
import { LocalTime } from "~/components/LocalTime";

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

function EventDetail() {
  const event = Route.useLoaderData();

  return (
    <div>
      <h1>{event.title}</h1>
      <p>
        Commence : <LocalTime timestamp={event.startTime} />
      </p>
      <p>
        Termine : <LocalTime timestamp={event.endTime} />
      </p>
    </div>
  );
}

Chaque utilisateur voit les heures d'événement converties dans son fuseau horaire local. L'horodatage ISO provenant du serveur est analysé et formaté selon les préférences de locale et de fuseau horaire de l'utilisateur.

3. Formater les heures avec affichage explicite du fuseau horaire

Lorsque les utilisateurs ont besoin de savoir quel fuseau horaire une heure représente, incluez le nom du fuseau horaire dans les options de formatage.

import { useIntl } from "react-intl";

interface ExplicitTimezoneProps {
  timestamp: string;
  timezone?: string;
}

export function ExplicitTimezone({
  timestamp,
  timezone,
}: ExplicitTimezoneProps) {
  const intl = useIntl();
  const date = new Date(timestamp);

  const formatted = intl.formatDate(date, {
    year: "numeric",
    month: "short",
    day: "numeric",
    hour: "numeric",
    minute: "2-digit",
    timeZone: timezone,
    timeZoneName: "long",
  });

  return <time dateTime={timestamp}>{formatted}</time>;
}

Spécifier une option timeZone force le formateur à afficher l'heure dans ce fuseau horaire spécifique. Lorsque timezone est indéfini, le navigateur utilise le fuseau horaire local de l'utilisateur. Cela est utile pour afficher les heures dans un fuseau horaire spécifique, indépendamment de l'emplacement de l'utilisateur.

4. Créer un assistant pour l'affichage du temps relatif

Pour les événements récents, affichez des temps relatifs comme "il y a 2 heures" tout en préservant l'heure absolue dans une infobulle.

import { useIntl } from "react-intl";

interface RelativeTimeProps {
  timestamp: string;
}

export function RelativeTime({ timestamp }: RelativeTimeProps) {
  const intl = useIntl();
  const date = new Date(timestamp);
  const now = Date.now();
  const diffInSeconds = Math.floor((now - date.getTime()) / 1000);

  const absoluteTime = intl.formatDate(date, {
    year: "numeric",
    month: "short",
    day: "numeric",
    hour: "numeric",
    minute: "2-digit",
  });

  let value: number;
  let unit: Intl.RelativeTimeFormatUnit;

  if (diffInSeconds < 60) {
    value = -diffInSeconds;
    unit = "second";
  } else if (diffInSeconds < 3600) {
    value = -Math.floor(diffInSeconds / 60);
    unit = "minute";
  } else if (diffInSeconds < 86400) {
    value = -Math.floor(diffInSeconds / 3600);
    unit = "hour";
  } else {
    value = -Math.floor(diffInSeconds / 86400);
    unit = "day";
  }

  const relativeTime = intl.formatRelativeTime(value, unit);

  return (
    <time dateTime={timestamp} title={absoluteTime}>
      {relativeTime}
    </time>
  );
}

Le temps relatif met à jour l'affichage pour montrer des durées conviviales tandis que l'attribut title préserve l'heure locale exacte pour les utilisateurs qui passent la souris. Les deux formats respectent automatiquement le fuseau horaire et la locale de l'utilisateur.