Timezone Emulation in Headless Browsers

Timezone emulation makes a headless browser report a chosen IANA time zone. See one instant rendered in three zones, the silent failure mode and the code.

By Simon Chiu · Updated

Timezone emulation is a browser automation setting that makes the page's JavaScript believe it runs in a chosen time zone, so Date, Intl.DateTimeFormat and every clock on the page show local time for that zone.

It matters for any capture that contains a time: order histories, calendars, booking pages, dashboards, "posted 3 hours ago" labels. Without it, the browser uses the time zone of the server it runs on. This entry is part of the screenshot and rendering glossary.

A plain explanation

A web page does not know where you are. It asks the browser, and the browser answers from the operating system's clock settings. Emulation overrides that answer for one page or context. Your visitors in Tokyo and New York see different clock times for the same instant; emulation lets one machine produce both views.

One instant, three zones

We rendered a test page that formats one fixed instant, 06:30 UTC on 8 March 2026, with the browser's own settings, then switched the emulated zone between captures:

Three captures of the same page. With UTC it shows Mar 8, 2026, 6:30 AM and offset 0 minutes. With America/New_York it shows 1:30 AM and offset 300 minutes. With Asia/Tokyo it shows 3:30 PM and offset minus 540 minutes.
Captured with Puppeteer page.emulateTimezone() on 2026-10-15. The page formats Date.UTC(2026, 2, 8, 6, 30) with toLocaleString('en-US').
Setting Intl reports Local time shown getTimezoneOffset()
Nothing (host clock in Toronto) America/Toronto Mar 8, 2026, 1:30 AM 300
UTC UTC Mar 8, 2026, 6:30 AM 0
America/New_York America/New_York Mar 8, 2026, 1:30 AM 300
Asia/Kolkata Asia/Calcutta Mar 8, 2026, 12:00 PM -330
Asia/Tokyo Asia/Tokyo Mar 8, 2026, 3:30 PM -540
Mars/Base (invalid), set after Tokyo Asia/Tokyo Mar 8, 2026, 3:30 PM -540

Three details are worth knowing:

  • Invalid IDs fail silently once a zone is set. On a fresh page, Puppeteer 24 threw Invalid timezone ID: Mars/Base. After a valid zone had been set on the same page, the same call returned without an error and the page kept Tokyo time. Validate IDs yourself.
  • Canonical names can differ. Both Puppeteer and Playwright accepted Asia/Kolkata and then reported the older alias Asia/Calcutta. A test that compares the string fails.
  • The offset sign is inverted. getTimezoneOffset() is UTC minus local, so zones east of UTC are negative.

The instant sits two hours before US clocks changed that day. That is deliberate: test pages near a daylight-saving switch are where hard-coded offsets go wrong.

How it works technically

Chrome exposes the override through the DevTools Protocol command Emulation.setTimezoneOverride. It replaces the zone that the JavaScript engine's time zone database uses for that page, so Date, Intl and libraries built on them follow. It does not move the system clock, change your IP address, or alter times that the server formatted into the HTML. For server-side zones, the site usually reads an account setting, a cookie or IP-based location; see geolocation emulation for the location side.

How to set it in Puppeteer, Playwright and RenderScreenshot

Puppeteer sets it per page, and it applies to documents loaded after the call:

import puppeteer from 'puppeteer';

const zone = 'Asia/Tokyo';
// Throws RangeError for unknown IDs. Do not use Intl.supportedValuesOf('timeZone'):
// in Node 22 it omits UTC and lists Asia/Calcutta instead of Asia/Kolkata.
new Intl.DateTimeFormat('en-US', { timeZone: zone });

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  await page.emulateTimezone(zone);
  await page.goto('https://example.com');
  console.log(await page.evaluate(() => Intl.DateTimeFormat().resolvedOptions().timeZone));
  await page.screenshot({ path: 'tokyo.png' });
} finally {
  await browser.close();
}

Playwright sets it on the context with timezoneId, usually next to locale:

import { chromium } from 'playwright';

const browser = await chromium.launch();
try {
  const context = await browser.newContext({ timezoneId: 'Asia/Tokyo', locale: 'ja-JP' });
  const page = await context.newPage();
  await page.goto('https://example.com');
  await page.screenshot({ path: 'tokyo.png' });
} finally {
  await browser.close();
}

RenderScreenshot takes an IANA name in browser.timezone, or timezone as a query parameter. See the browser options.

curl --get https://api.renderscreenshot.com/v1/screenshot \
  -H "Authorization: Bearer $RS_API_KEY" \
  --data-urlencode 'url=https://example.com' \
  --data-urlencode 'timezone=Asia/Tokyo' \
  -d locale=ja-JP \
  --output tokyo.png
import { Client, TakeOptions } from 'renderscreenshot';

const client = new Client(process.env.RS_API_KEY);
const image = await client.take(
  TakeOptions.url('https://example.com').timezone('Asia/Tokyo').locale('ja-JP'),
);

The table and both browser scripts were tested on 2026-10-15 with Node 22.18, Puppeteer 24.43.1 (headless shell mode, Chrome 148) and Playwright 1.64.1 (Chromium 156). The RenderScreenshot snippets follow the API docs and were not run for this page.

Common mistakes

  • Trusting the default. A capture that looks right on your laptop shows UTC times on a server. Set the zone on every capture that shows a clock.
  • Emulating after navigation. In Puppeteer, the page you already loaded keeps the times it rendered. Set the zone first, then goto.
  • Using offsets instead of names. UTC+9 is not an IANA ID, and a fixed offset ignores daylight saving. Use Asia/Tokyo.
  • Setting the zone but not the locale. The time is right but the format is American. The localized screenshots guide sets both.

For locale and time zone together in a Puppeteer project, read emulating time zone and locale in Puppeteer. To capture one page in several zones, get a free API key.

Frequently asked questions

What time zone does a headless browser use by default?
The time zone of the machine or container it runs on. In our test on a laptop set to Toronto, headless Chrome reported America/Toronto. Many servers and Docker images run in UTC, so the same script shows different times locally and in production.
Does timezone emulation change server-rendered times?
No. It only changes what JavaScript in the page sees. A time that the server formats into the HTML uses the server's own settings, which you cannot change from the browser.
Is time zone the same as locale?
No. Locale controls language and formats such as day-month order and the decimal comma. Time zone controls the clock offset. A French-language page can still show New York time; set both when you need both.

Related in this topic

Try RenderScreenshot free

150 free credits every month, no credit card required.