Answering the Core Question
Electron’s main process uses the bundled ICU data by default. Renderers, however, can fall back to the host OS’s ICU unless a locale is explicitly set. To guarantee that Intl.DateTimeFormat resolves the same timezone in both processes, you must either force the renderer to use the bundled ICU or explicitly propagate the locale to each renderer. Relying on the host ICU without propagation can lead to mismatches, especially on Linux distributions with separate ICU packages.
Confirmed Facts
- Calling
app.setLocale() before app.whenReady() sets Chromium’s locale flag but does not change the ICU data source for the renderer.
- Using
app.commandLine.appendSwitch('lang', locale) before app.whenReady() or setting webPreferences.locale in BrowserWindow forces Chromium to load the bundled ICU for that locale in the renderer.
- Both main and renderer can report their resolved timezone via
Intl.DateTimeFormat().resolvedOptions().timeZone. Matching strings confirm identical ICU usage.
- If ICU sources diverge, formatting a known UTC date with
Intl.DateTimeFormat(timeZoneName:'long') will produce different strings in the two processes.
- Bundling a custom date‑library (e.g.,
luxon, date-fns-tz) that carries its own IANA tzdb can circumvent ICU differences entirely.
Steps to Verify and Synchronize
- Set Locale Early: In the main process, before
app.whenReady(), call app.commandLine.appendSwitch('lang', 'en-US') or create each BrowserWindow with webPreferences: { locale: 'en-US' }.
- Log Timezone in Main: After
app.whenReady(), log Intl.DateTimeFormat().resolvedOptions().timeZone.
- Expose to Renderer: In a preload script, expose a function via
contextBridge that returns the same resolved timezone string.
- Compare via IPC: From the renderer, send the value to the main process and compare. Matching values confirm parity.
- Optional Library Path: If you prefer not to manage ICU flags, format dates in the main process with
Date.now() and send the UTC timestamp to the renderer, where a library with its own tzdb performs the formatting.
Practical Verification Example
// main.js
app.commandLine.appendSwitch('lang', 'en-US');
app.whenReady().then(() => {
console.log('Main tz:', Intl.DateTimeFormat().resolvedOptions().timeZone);
const win = new BrowserWindow({
webPreferences: { preload: path.join(__dirname, 'preload.js') }
});
win.loadFile('index.html');
});
// preload.js
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('api', {
getTimeZone: () => Intl.DateTimeFormat().resolvedOptions().timeZone,
sendToMain: (tz) => ipcRenderer.send('tz-from-renderer', tz)
});
// renderer (index.html)
When to Use Bundled ICU vs Host ICU
- Use bundled ICU when you need deterministic formatting across all platforms, regardless of the host’s ICU version.
- Use host ICU with explicit locale propagation if you want to benefit from the OS’s latest timezone data and are comfortable with potential differences.
Diagnostic Detail Needed?
If you are targeting a Linux distribution that ships a separate ICU package or if your application is bundled with asar at a specific Electron version, let us know. That information can affect whether forcing the bundled ICU is the safest path.