modulo 08 / lezione 43
Debugging di dati, backend e integrazioni
Timezone, encoding e locale
Riconoscere bug che dipendono da fuso orario, salvataggio in UTC vs locale, encoding misto, formattazione numerica e date.
In parole semplici
Le date e i testi sembrano semplici. Sono la sorgente di bug più sottili e ricorrenti del software. Tre categorie:
- Timezone: la stessa data appare a giorni diversi a utenti diversi.
- Encoding: stringhe con accenti diventano spazzatura.
- Locale: numeri formattati con virgola/punto, ordinamenti differenti, separatori inattesi.
Parole nuove
- UTC (Coordinated Universal Time): fuso “zero” di riferimento.
- timezone: fuso orario (Europe/Rome, America/New_York).
- ISO 8601: formato standard per date (
2025-05-16T14:30:00Z). - UTF-8: encoding standard, supporta tutti i caratteri Unicode.
- locale: combinazione lingua + paese (it-IT, en-US).
Cosa impari
- Salvare e mostrare date senza confusione di fuso.
- Diagnosticare e correggere encoding misti.
- Formattare numeri secondo locale.
Timezone: il problema universale
Tre fonti di confusione:
- Il server è in un fuso, il client in un altro.
- Il database salva senza fuso (
TIMESTAMPvsTIMESTAMPTZin Postgres). - Le librerie convertono in modo diverso.
Regola d’oro
Salva sempre in UTC. Mostra sempre in locale.
In codice:
// salvataggio
const now = new Date(); // momento attuale, internamente UTC
const isoString = now.toISOString(); // "2025-05-16T14:30:00.000Z"
db.salva({ creato_il: isoString });
// lettura
const dateUTC = new Date(record.creato_il); // legge UTC
const formattata = dateUTC.toLocaleString("it-IT", { timeZone: "Europe/Rome" });
// "16/05/2025, 16:30:00"
Mai salvare "15/05/2025 14:30" senza fuso. È ambiguo: di chi?
Bug tipico: il capodanno
2024-12-31T23:00Z salvato senza fuso. Mostrato a utente in Europe/Rome (UTC+1): 2025-01-01 00:00. La query “ordini del 2024” lo esclude. L’utente non capisce.
Fix: usa sempre Intl.DateTimeFormat con timeZone esplicito.
Date e database
Postgres:
TIMESTAMP(senza fuso): “muto”, interpretato come locale del DB. Sconsigliato.TIMESTAMPTZ: salva sempre internamente UTC, converte in locale all’output.
MySQL:
DATETIMEvsTIMESTAMP: differenza sottile.TIMESTAMPha range limitato e conversione UTC.
Regola: usa il tipo “con fuso” sempre che possibile.
Encoding: tre regole
- UTF-8 ovunque: file, database, header HTTP. Tag HTML:
<meta charset="utf-8">
Header HTTP:
Content-Type: text/html; charset=utf-8
DB Postgres: CREATE DATABASE x ENCODING 'UTF8'.
-
Mai mescolare encoding: una stringa Latin-1 letta come UTF-8 produce
caff�. Convertire una volta sola, al confine. -
BOM (Byte Order Mark): alcuni editor aggiungono
all’inizio dei file. CSV con BOM aperti in tool diversi mostrano un carattere strano in alto a sinistra. Verifica con un hex viewer.
Diagnosticare encoding misto
Sintomi:
- “Caffè” diventa “Caff” + caratteri strani.
- Emoji non rese.
- Caratteri sostituiti con
?o�(replacement character).
Diagnosi:
file -i nome-file.csv
# nome-file.csv: text/plain; charset=iso-8859-1
Conversione:
iconv -f ISO-8859-1 -t UTF-8 nome-file.csv > nome-file-utf8.csv
In JS (browser/Node):
const decoded = new TextDecoder("iso-8859-1").decode(byteArray);
Locale: numeri e date
1.000,50 in italiano = 1,000.50 in inglese. Confondere è bug.
const n = 1234.56;
n.toLocaleString("it-IT"); // "1.234,56"
n.toLocaleString("en-US"); // "1,234.56"
n.toLocaleString("de-DE"); // "1.234,56"
Mai usare parseFloat("1.234,56"): ritorna 1.234. Per parsing user input localizzato:
function parseLocaleNumber(s, locale = "it-IT") {
const parts = new Intl.NumberFormat(locale).formatToParts(1234.5);
const decimal = parts.find(p => p.type === "decimal").value;
const group = parts.find(p => p.type === "group").value;
return parseFloat(
s.replaceAll(group, "").replace(decimal, ".")
);
}
Date e locale
new Date().toLocaleDateString("it-IT"); // "16/05/2025"
new Date().toLocaleDateString("en-US"); // "5/16/2025"
new Date().toLocaleDateString("ja-JP"); // "2025/5/16"
Per formati specifici:
new Intl.DateTimeFormat("it-IT", {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
}).format(new Date());
// "venerdì 16 maggio 2025"
Ordinamento
["mela", "Banana", "ciliegia"].sort() ordina per codepoint Unicode: ["Banana", "ciliegia", "mela"] (maiuscole prima).
Per locale-aware:
const collator = new Intl.Collator("it-IT", { sensitivity: "base" });
["mela", "Banana", "ciliegia"].sort(collator.compare);
// ["Banana", "ciliegia", "mela"] con tolleranza
Esercizio guidato
- Crea un piccolo programma che salva un evento a “31 dicembre 2024, 23:00” come stringa locale.
- Mostra l’evento a un utente in
Europe/Rome, uno inAmerica/New_York, uno inAsia/Tokyo. Cosa vedono? - Cambia salvataggio a UTC ISO 8601. Ripeti. Coerenza?
- Esporta un CSV con accenti in UTF-8 vs Latin-1. Apri in Excel e in un text editor. Differenze?
Errori frequenti del principiante
- Salvare date senza fuso. “Bug del fuso” tornano sempre.
new Date("15/05/2025"): parsing dipendente dalla locale del runtime.- Mescolare UTF-8 e Latin-1 senza saperlo.
parseFloatsu numero localizzato: rovesciato.