Hydration Proof

Search documentation

Find a page or section

no-timezone-without-explicit-timezone

Require an explicit timeZone when dates are formatted or split into parts during render.

Without the Intl.DateTimeFormat timeZone option, a date is formatted in the time zone of whichever runtime renders it: UTC on most servers, the visitor's zone in the browser. no-timezone-without-explicit-timezone reports date formatting without an explicit timeZone, and local-time getters such as getHours(), in render code.

Rulehydration-proof/no-timezone-without-explicit-timezone
What it reportsRequire an explicit timeZone when dates are formatted or split into parts during render
recommended / nextWarning
strictError
Server ComponentsSkipped with the next preset (they never hydrate)
SuggestionsYes
OptionsdefaultTimeZone

What it reports

In render code:

  • toLocaleDateString() and toLocaleTimeString() without a timeZone option.
  • new Intl.DateTimeFormat() / Intl.DateTimeFormat() without a timeZone option.
  • toLocaleString() without a timeZone option when the value is clearly a date: the receiver is new Date(...) (or a variable initialized with it in the same function), or the options contain date or time fields (dateStyle, timeStyle, year, month, day, hour, minute, ...).
  • getHours(), getMinutes(), getDate(), getDay(), getMonth(), getFullYear(), getTimezoneOffset(), toDateString(), toTimeString() and toString() on new Date(...) or a variable initialized with it in the same function.
  • Intl.DateTimeFormat().resolvedOptions().timeZone, which reads the runtime's time zone.

When the options are not an object literal (toLocaleDateString('en-US', options)), the rule cannot see them and does not report.

Suggestions (never automatic fixes):

  • add timeZone: 'UTC' (or the defaultTimeZone option) to the options object, or add an options object when there is none;
  • replace getHours() and the other local getters with their UTC versions (getUTCHours()), when defaultTimeZone is 'UTC'.

Why the Intl.DateTimeFormat timeZone option matters

Dates are formatted in the time zone of the runtime. Servers usually run in UTC, browsers in the visitor's zone:

server HTML:   <p>Signed in at 5:00 AM</p>    (UTC)
client render: <p>Signed in at 10:00 AM</p>   (Asia/Karachi)

Hydration fails whenever the two zones give a different result, which for times is almost always, and for dates near midnight. Timezone hydration mismatches explains how to pick the zone both sides use.

Incorrect

function LastLogin({ at }) {
  return <p>Signed in at {new Date(at).toLocaleTimeString("en-US")}</p>;
}
 
function Posted({ at }) {
  const date = new Date(at);
  return (
    <p>
      {date.getDate()}/{date.getMonth() + 1}
    </p>
  );
}
 
function Schedule({ at }) {
  const format = new Intl.DateTimeFormat("en-US", {
    dateStyle: "medium",
    timeStyle: "short",
  });
  return <p>{format.format(at)}</p>;
}

Correct

function LastLogin({ at, timeZone }) {
  // The user's zone, stored in their profile or a cookie and passed from the server.
  return (
    <p>Signed in at {new Date(at).toLocaleTimeString("en-US", { timeZone })}</p>
  );
}
 
function Posted({ at }) {
  const date = new Date(at);
  return (
    <p>
      {date.getUTCDate()}/{date.getUTCMonth() + 1}
    </p>
  );
}
 
function Schedule({ at }) {
  const format = new Intl.DateTimeFormat("en-US", {
    timeZone: "UTC",
    dateStyle: "medium",
    timeStyle: "short",
  });
  return <p>{format.format(at)}</p>;
}

If the visitor's own zone is required and the server cannot know it, render the date after hydration (in useEffect) or put suppressHydrationWarning on the element that shows it.

Options

OptionTypeDefaultDescription
defaultTimeZonestring'UTC'Time zone inserted by the suggestion.
eslint.config.mjs
{
  rules: {
    'hydration-proof/no-timezone-without-explicit-timezone': ['warn', { defaultTimeZone: 'Europe/Berlin' }],
  },
}
  • defaultTimeZone (string, default 'UTC'): the zone the suggestion inserts. The UTC getter suggestion is only offered when this is 'UTC'.

Messages

What ESLint prints for this rule, word for word:

  • <call> formats the date in the time zone of whichever runtime renders it. Servers usually run in UTC and browsers in the visitor's zone, so the text does not match during hydration. Pass an explicit timeZone option.
  • <call> reads the date in the runtime's local time zone, which differs between the server and the browser. Use the UTC methods, or format with an explicit timeZone.
  • <call> reads the runtime's time zone, which differs between the server and the browser. Pass the time zone from the server, or read it after hydration.
  • Add timeZone: '<timeZone>'.
  • Use <method>() (UTC).

When not to use it

When the servers and all visitors share one time zone, or the runtime's default zone is set explicitly on both sides.