
Sostituire Moment.js, date-fns, Luxon e Day.js con Temporal
Una guida pratica alla migrazione: come fare formattazione, parsing, aritmetica, differenze, confronti e conversioni di fuso orario in Temporal, mappati direttamente sulle chiamate di Moment/date-fns/Luxon/dayjs che già conosci.
L’articolo precedente su Temporal ha spiegato perché esiste e quali sono i suoi tipi principali. Questo è invece il riferimento a cui ricorrere davvero durante una migrazione: la chiamata Temporal esatta per ogni cosa comune che oggi fai con Moment.js, date-fns, Luxon o Day.js.
ℹ️ Setup
Dove Temporal non è ancora disponibile nativamente, installa il polyfill ufficiale: npm install @js-temporal/polyfill. Importalo una volta con import {Temporal} from "@js-temporal/polyfill";
— tutti gli esempi qui sotto usano lo stesso oggetto globale Temporal in entrambi i casi.
Formattazione
// Moment
moment().format("YYYY-MM-DD"); // "2026-09-15"
// date-fns
format(new Date(), "yyyy-MM-dd"); // "2026-09-15"
// Luxon
DateTime.now().toFormat("yyyy-MM-dd"); // "2026-09-15"
// Temporal — l'output ISO è integrato, nessuna stringa di formato necessaria in questo caso
Temporal.Now.plainDateISO().toString(); // "2026-09-15"
Per qualsiasi output sensibile alla lingua (nomi dei giorni, formato “15 settembre 2026”), Temporal si appoggia a Intl.DateTimeFormat invece di inventarsi un proprio linguaggio di token di formato:
const date = Temporal.Now.plainDateISO();
new Intl.DateTimeFormat("it-IT", { dateStyle: "long" }).format(date);
// "15 settembre 2026"
date.toLocaleString("it-IT", { weekday: "long", month: "short", day: "numeric" });
// "martedì 15 set"
toLocaleString su qualsiasi oggetto Temporal inoltra direttamente a Intl.DateTimeFormat — non c’è una sintassi separata a token di formato (yyyy, MMM, ecc.) da imparare o tenere sincronizzata con la documentazione di una libreria.
Parsing
// Moment — accetta quasi tutto, che è esattamente il problema dell'ambiguità
moment("2026-09-15T14:30:00");
// date-fns
parseISO("2026-09-15T14:30:00");
// Luxon
DateTime.fromISO("2026-09-15T14:30:00");
// Temporal — solo ISO 8601 / RFC 9557 rigoroso, lancia un errore su input ambigui invece di indovinare
Temporal.PlainDateTime.from("2026-09-15T14:30:00");
⚠ Temporal non fa parsing di formati permissivi
Temporal.PlainDate.from("15/09/2026") lancia un RangeError. È deliberato — Temporal si rifiuta
di indovinare se è giorno/mese o mese/giorno. Se stai ingerendo stringhe non-ISO (un export CSV,
un’API legacy), convertile prima tu stesso in una stringa ISO, allo stesso modo in cui valideresti
qualsiasi altro input non fidato.
Ottenere “adesso”
// Moment
moment();
// date-fns
new Date();
// Luxon
DateTime.now();
// Temporal — scegli il tipo che corrisponde a ciò che ti serve davvero
Temporal.Now.plainDateISO(); // solo la data di oggi
Temporal.Now.plainDateTimeISO(); // data di oggi + orario locale, nessun fuso
Temporal.Now.zonedDateTimeISO(); // istante + orario locale nel fuso — di solito quello che serve
Temporal.Now.instant(); // istante UTC grezzo, nessun calendario/fuso
Questo è il cambio mentale più grande arrivando da una libreria basata su Date: invece di un unico oggetto per ogni caso d’uso, scegli il tipo Temporal che corrisponde alla domanda che stai davvero facendo. Usare zonedDateTimeISO() come default copre la maggior parte dei casi “che ora è adesso, qui”.
Somma e sottrazione
// Moment
moment().add(3, "days").subtract(2, "hours");
// date-fns
subHours(addDays(new Date(), 3), 2);
// Luxon
DateTime.now().plus({ days: 3 }).minus({ hours: 2 });
// dayjs
dayjs().add(3, "day").subtract(2, "hour");
// Temporal
Temporal.Now.zonedDateTimeISO().add({ days: 3 }).subtract({ hours: 2 });
Il chaining funziona come sei abituato con Luxon/dayjs, e ogni valore intermedio è una nuova istanza immutabile — niente qui può mutare accidentalmente un oggetto condiviso come fa moment().add(...) mutando sul posto.
Differenza tra due date
// Moment
moment(end).diff(moment(start), "hours");
// date-fns
differenceInHours(end, start);
// Luxon
end.diff(start, "hours").hours;
// Temporal — until()/since() restituiscono una Duration, non un numero grezzo
start.until(end, { largestUnit: "hours" }); // Temporal.Duration
start.until(end, { largestUnit: "hours" }).hours; // solo il numero, se è tutto ciò che serve
until/since restituiscono una Temporal.Duration completa (ore, minuti, secondi popolati secondo necessità), non un singolo float di cui devi ricordare l’unità — chiedi la scomposizione in unità che vuoi tramite largestUnit/smallestUnit invece di chiamare una funzione diversa per ogni unità come differenceInHours / differenceInDays / differenceInMinutes.
Confronto
// Moment
moment(a).isBefore(b);
moment(a).isAfter(b);
moment(a).isSame(b);
// date-fns
isBefore(a, b);
isAfter(a, b);
isEqual(a, b);
// Luxon
a < b; // Luxon DateTime supporta il confronto basato su valueOf
a.equals(b);
// Temporal
Temporal.PlainDate.compare(a, b); // -1, 0 o 1 — adatto per il sort
a.equals(b);
Temporal.PlainDate.compare (e l’equivalente compare statico su ogni tipo Temporal) restituisce la stessa forma -1/0/1 che si aspetta Array.prototype.sort, quindi ordinare una lista di valori Temporal è dates.sort(Temporal.PlainDate.compare), senza dover scrivere a mano una funzione di confronto.
Conversione di fuso orario
// Moment (richiede moment-timezone, un pacchetto separato)
moment.tz(date, "America/New_York").format();
// date-fns (richiede date-fns-tz, un pacchetto separato)
formatInTimeZone(date, "America/New_York", "yyyy-MM-dd HH:mm:ssXXX");
// Luxon — integrato
dt.setZone("America/New_York");
// Temporal — integrato, nessun pacchetto extra indipendentemente da quale libreria stai sostituendo
zonedDateTime.withTimeZone("America/New_York");
Questo è il caso col divario più grande: Moment e date-fns hanno bisogno di un pacchetto separato per i fusi orari (moment-timezone, date-fns-tz) solo per fare la conversione correttamente, perché nessuno dei due include di default i dati dei fusi IANA. Temporal lo ha integrato — withTimeZone è un metodo su ZonedDateTime, niente da installare in più.
Cheatsheet
| Operazione | Moment | date-fns | Luxon | Temporal |
|---|---|---|---|---|
| Adesso | moment() |
new Date() |
DateTime.now() |
Temporal.Now.zonedDateTimeISO() |
| Parsing stringa ISO | moment(str) |
parseISO(str) |
DateTime.fromISO(str) |
Temporal.PlainDateTime.from(str) |
| Formattazione | .format("YYYY-MM-DD") |
format(d, "yyyy-MM-dd") |
.toFormat("yyyy-MM-dd") |
.toLocaleString(locale, opts) |
| Somma durata | .add(3, "days") |
addDays(d, 3) |
.plus({ days: 3 }) |
.add({ days: 3 }) |
| Sottrai durata | .subtract(2, "hours") |
subHours(d, 2) |
.minus({ hours: 2 }) |
.subtract({ hours: 2 }) |
| Differenza | .diff(other, "hours") |
differenceInHours(a, b) |
.diff(other, "hours") |
.until(other, { largestUnit: "hours" }) |
| Confronto | .isBefore(other) |
isBefore(a, b) |
a < b |
Temporal.PlainDate.compare(a, b) |
| Cambio fuso orario | .tz("America/New_York") (richiede moment-timezone) |
formatInTimeZone(...) (richiede date-fns-tz) |
.setZone("America/New_York") |
.withTimeZone("America/New_York") |
| Mutabilità | Muta sul posto | Immutabile | Immutabile | Immutabile |
Cosa non si mappa 1:1
Alcune cose da sapere prima di iniziare a sostituire le chiamate:
- Nessuna stringa a token di formato. Non esiste un equivalente Temporal di
"YYYY-MM-DD HH:mm"— la formattazione passa per le opzioni diIntl.DateTimeFormat({ year: "numeric", month: "2-digit", ... }) invece che per una stringa. Per un pugno di output in stile ISO,toString()/toJSON()danno già ciò che darebbe una stringa di formato. - Nessun parsing permissivo. Qualsiasi codice che fa affidamento sul parsing “indovina un po’” di Moment per stringhe ambigue ha bisogno di un passaggio di conversione esplicito prima di arrivare a Temporal — vedi l’avviso sopra.
- Il tempo relativo (“3 giorni fa”) non è integrato in Temporal stesso. Usa
Intl.RelativeTimeFormatcon la differenza numerica dauntil/since, allo stesso modo in cui già combineresti una funzione di diff con un livello di formattazione in date-fns.
FAQ
❓ Posso migrare in modo incrementale, file per file?
Sì — gli oggetti Temporal si convertono in modo pulito da/verso Date nativo
(Temporal.Instant.fromEpochMilliseconds(date.getTime()) e instant.epochMilliseconds), quindi
un modulo basato su Temporal e uno basato su Moment/date-fns possono coesistere mentre migri pezzo
per pezzo invece che tutto in una volta.
❓ Questo elimina il bisogno di date-fns-tz o moment-timezone nello specifico?
Sì — la conversione di fuso orario è uno dei punti di forza integrati di Temporal, coperto sopra. Di solito è il singolo guadagno più grande in termini di dipendenze dalla migrazione, dato che i dati dei fusi orari IANA sono ciò che rende quei pacchetti aggiuntivi non banali in primo luogo.
❓ C'è un motivo legato al peso del bundle per migrare, non solo all'API?
Dove Temporal è supportato nativamente dai tuoi runtime target, sì — arriva incluso nel motore, quindi elimini del tutto il peso della libreria. Fino ad allora, il polyfill ha un suo costo in termini di dimensioni, quindi il vantaggio riguarda prima la correttezza dell’API e poi, una volta arrivato il supporto nativo, la dimensione del bundle.
Conclusione
Il passaggio da Moment/date-fns/Luxon/dayjs a Temporal è per lo più meccanico, una volta che conosci la mappatura: scegli il tipo Temporal che corrisponde a ciò che stai rappresentando, sostituisci le chiamate con stringhe di formato con Intl.DateTimeFormat/toLocaleString, ed elimina il pacchetto separato per i fusi orari dato che withTimeZone è integrato. La cheatsheet sopra copre le chiamate che incontrerai al primo passaggio su qualsiasi codebase reale.
Serve una mano con una migrazione di questo tipo?
Lavoro come sviluppatore e consulente JavaScript/TypeScript attraverso PAPION, con sede a Udine — per clienti a Udine e in Friuli, oltre che da remoto per team in tutta Italia e all’estero. Se stai pianificando una migrazione di libreria, o hai bisogno di lavoro su React, Node.js o TypeScript più in generale, scrivimi.
Riferimenti

Co-Fondatore & CTO presso PAPION. Senior full-stack engineer specializzato in React, TypeScript, Node.js e sicurezza applicativa.