How to handle timezones in Next.js (Pages Router) v16

Show times in user's local timezone

Problem

When applications display times without accounting for the user's location, confusion arises. A meeting scheduled for "3:00 PM" means different moments depending on whether the viewer is in New York, London, or Tokyo. If the server renders a time in UTC or its own timezone, users in other regions see incorrect local times, leading to missed appointments and scheduling errors. The same timestamp must be interpreted differently for each user based on their timezone.

Solution

Store timestamps in a universal format such as ISO 8601 or Unix timestamps, then format them for display in the user's local timezone on the client. Detect the user's timezone using the browser's Internationalization API and pass it to react-intl's formatting functions. This ensures that every user sees times adjusted to their own timezone, eliminating ambiguity about when events occur.

Steps

1. Detect the user's timezone on the client

The browser's Intl.DateTimeFormat().resolvedOptions().timeZone returns the user's IANA timezone identifier, such as America/New_York or Europe/London. Create a custom hook to access this value.

import { useState, useEffect } from "react";

export function useUserTimeZone() {
  const [timeZone, setTimeZone] = useState<string>("UTC");

  useEffect(() => {
    const detected = Intl.DateTimeFormat().resolvedOptions().timeZone;
    setTimeZone(detected);
  }, []);

  return timeZone;
}

This hook runs only on the client, avoiding hydration mismatches, and defaults to UTC until the timezone is detected.

2. Format dates with the user's timezone using FormattedDate

Pass the timeZone option to react-intl's formatting methods to control which timezone is used for display. Use the <FormattedDate> component with the detected timezone.

import { FormattedDate } from "react-intl";
import { useUserTimeZone } from "../hooks/useUserTimeZone";

interface EventDateProps {
  timestamp: string;
}

export function EventDate({ timestamp }: EventDateProps) {
  const userTimeZone = useUserTimeZone();

  return (
    <FormattedDate
      value={new Date(timestamp)}
      timeZone={userTimeZone}
      year="numeric"
      month="long"
      day="numeric"
      hour="numeric"
      minute="2-digit"
      timeZoneName="short"
    />
  );
}

The timeZone prop ensures the date is formatted in the user's local timezone rather than the server's or UTC.

3. Format times imperatively with useIntl

For scenarios where you need a formatted string rather than a component, use the useIntl hook to access the imperative formatting API.

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

interface MeetingTimeProps {
  startTime: string;
}

export function MeetingTime({ startTime }: MeetingTimeProps) {
  const intl = useIntl();
  const userTimeZone = useUserTimeZone();

  const formattedTime = intl.formatDate(new Date(startTime), {
    timeZone: userTimeZone,
    hour: "numeric",
    minute: "2-digit",
    timeZoneName: "short",
  });

  return <span title={startTime}>{formattedTime}</span>;
}

The formatDate method accepts a second argument with DateTimeFormatOptions, including timeZone, allowing full control over how the timestamp is displayed.

4. Pass server timestamps as ISO strings

In getServerSideProps, fetch data at request time and pass it to the page component as props. Serialize dates as ISO 8601 strings to preserve timezone information.

import { GetServerSideProps } from "next";

interface Event {
  id: string;
  title: string;
  startTime: string;
}

interface EventPageProps {
  event: Event;
}

export const getServerSideProps: GetServerSideProps<
  EventPageProps
> = async () => {
  const event = {
    id: "1",
    title: "Team Meeting",
    startTime: new Date("2025-02-15T15:00:00Z").toISOString(),
  };

  return {
    props: { event },
  };
};

export default function EventPage({ event }: EventPageProps) {
  return (
    <div>
      <h1>{event.title}</h1>
      <EventDate timestamp={event.startTime} />
    </div>
  );
}

ISO strings preserve the exact moment in time, allowing client-side formatting to convert them accurately to any timezone.