Perché Date non ha una proprietà di fuso orario
Un Date di JavaScript è sempre un conteggio di millisecondi UTC. Nessun fuso è memorizzato dentro l'oggetto. Quando chiami .toString() o .toLocaleString() senza opzioni, JavaScript usa il fuso locale del runtime dal sistema operativo. Lo stesso codice produce output diversi su un server a New York e su un portatile a Tokyo, anche se il timestamp sottostante è identico.
Formattare con Intl.DateTimeFormat
Intl.DateTimeFormat è l'API integrata per la formattazione consapevole di locale e fuso. Supporta gli identificatori di fuso IANA, gestisce automaticamente le transizioni dell'ora legale ed è disponibile nei browser moderni e in Node.js. La chiave è passare l'opzione timeZone esplicitamente invece di affidarsi al default del runtime.
- new Intl.DateTimeFormat('en-US', { timeZone: 'America/New_York', dateStyle: 'full', timeStyle: 'long' }).format(date)
- date.toLocaleString('en-GB', { timeZone: 'Europe/London', hour12: false })
- date.toLocaleString('ja-JP', { timeZone: 'Asia/Tokyo' })
- new Intl.DateTimeFormat('en-US', { timeZone: 'UTC', hour12: false }).format(date)
Estrarre singole parti con formatToParts
Usa formatToParts() per ottenere i singoli componenti della data come oggetti {type, value} e costruire stringhe di data personalizzate. È meglio che dividere una stringa di data localizzata perché punteggiatura, ordine e scritture variano per locale.
- const parts = new Intl.DateTimeFormat('en-US', { timeZone: 'America/Chicago', year: 'numeric', month: '2-digit', day: '2-digit' }).formatToParts(date)
- parts.find(p => p.type === 'year').value → '2023'
- parts.find(p => p.type === 'month').value → '11'
- Object.fromEntries(parts.map(p => [p.type, p.value])) → { year, month, day, hour, minute, second }
Convertire l'ora dell'orologio in UTC (la direzione difficile)
Passare da un'ora dell'orologio in un dato fuso a UTC è più difficile con la classica API Date. Formattare un istante in America/New_York è facile; costruire l'istante rappresentato da 2026-03-08 02:30 in America/New_York non lo è, perché quell'ora locale può essere saltata o ripetuta durante le transizioni dell'ora legale.
- Temporal ha raggiunto lo Stage 4 nel 2026, ma il supporto nativo dei browser non è ancora universale
- Per l'uso in produzione su tutti i browser oggi, il polyfill di Temporal o date-fns-tz toDate() restano scelte pratiche
- Evita l'aritmetica manuale di offset UTC — le transizioni dell'ora legale avvengono a orari locali diversi in anni diversi
- Il zonedToEpochMs() di questo sito usa una correzione di offset in una iterazione — vedi src/timeUtils.ts
Scegliere l'identificatore di fuso corretto
Usa nomi di fuso IANA come America/New_York, Europe/London, Asia/Tokyo o UTC. Evita offset fissi come UTC-5 per l'ora rivolta all'utente perché gli offset cambiano con l'ora legale. America/New_York può essere UTC-5 a gennaio e UTC-4 a luglio; il nome IANA consente alla piattaforma di applicare la regola storica corretta per la data selezionata.
- Bene: America/Los_Angeles — include regole di ora legale storiche e future
- Bene: Europe/Berlin — gestisce automaticamente l'ora legale dell'Europa centrale
- Bene: Asia/Shanghai — visualizzazione stabile UTC+8 senza ora legale
- Usa UTC per log, archiviazione API, timestamp di database e confronto tra regioni
- Usa il fuso IANA dell'utente per la visualizzazione finale nella UI del prodotto
FAQ sulla formattazione dei fusi
Two daylight-saving transitions a year break the assumption that "wall-clock time + timezone = a unique moment". Both are encoded in the IANA timezone database (still often called the Olson database) — the authoritative source of timezone rules used by every modern OS, browser, and language runtime.
- DST gap: 02:00 to 02:59 local time does not exist on the spring-forward day
- DST overlap: 01:00 to 01:59 local time occurs twice on the fall-back day
- Ambiguous local time: in the overlap window you must pick "earlier" or "later" explicitly
- Python uses a fold flag; JS Temporal uses a disambiguation option; java.time defaults to the earlier offset
- Olson database / IANA tzdata / zoneinfo are three names for the same dataset, updated several times a year
- Each IANA name (America/Los_Angeles, Europe/Berlin) maps to a list of (transition, offset, abbreviation) tuples
- Stale tzdata on a device produces silently-wrong local times after any DST or zone change
- Server best practice: do all date arithmetic in UTC, convert to local only at display
FAQ
- Can JavaScript format a date in another timezone without a library?
- Yes. Use Intl.DateTimeFormat or toLocaleString with a timeZone option, such as America/New_York or Asia/Tokyo.
- Does Intl.DateTimeFormat handle daylight saving time?
- Yes. When you use an IANA timezone identifier, the runtime applies the correct offset for that timezone and date.
- Should I store timezone offsets or timezone names?
- Store UTC for event instants. If you need the user's local context, store an IANA timezone name such as America/New_York, not only a numeric offset.
- How do I get the user's timezone in JavaScript?
- Use Intl.DateTimeFormat().resolvedOptions().timeZone, which returns an IANA name like America/New_York. Store that name, not a numeric offset, when you need to reconstruct the user's local time later.
- What is the DST gap (spring-forward)?
- The DST gap is the wall-clock hour that is skipped when daylight saving time begins. In most US zones, when the clock would tick from 02:00 to 02:59 it instead jumps to 03:00. Local times in that window do not exist on the transition day. Unix timestamps themselves never skip; only the wall-clock interpretation does.
- What is the DST overlap (fall-back)?
- The DST overlap is the wall-clock hour that occurs twice when daylight saving time ends. In most US zones, the clock ticks from 01:59 back to 01:00, so every minute from 01:00 to 01:59 happens twice. A wall-clock time in that window maps to two Unix timestamps; converters and schedulers must specify "earlier" or "later".