ISO8601DateFormatter è una classe Foundation in iOS e macOS progettata per formattare e analizzare le date secondo lo standard internazionale ISO 8601. Secondo Apple Developer Documentation, 2024, ISO8601DateFormatter gestisce automaticamente i formati con millisecondi, fusi orari e frazioni di secondo senza bisogno di impostare DateFormat manualmente. A differenza di DateFormatter, questa classe non dipende da Locale e TimeZone — funziona rigorosamente secondo la specifica ISO 8601, rendendola ideale per lo scambio di date tra server e client. La classe è disponibile da iOS 10 e macOS 10.12.
Punti chiave
ISO8601DateFormatter è una sottoclasse specializzata di Formatter in Foundation che implementa la conversione bidirezionale tra Date e stringhe in formato ISO 8601. Lo standard ISO 8601 (International Standard for the Representation of Dates and Times) definisce un formato internazionale per lo scambio di date e ore: 2024-07-21T14:30:00+00:00. A differenza di DateFormatter, questa classe non richiede di specificare dateFormat e determina automaticamente la struttura della stringa in base alle opzioni fornite.
I principali vantaggi di ISO8601DateFormatter rispetto a DateFormatter: nessuna dipendenza dalla locale (l'analisi funziona identicamente su qualsiasi dispositivo), supporto integrato per le frazioni di secondo (con qualsiasi numero di decimali) e rilevamento automatico del formato basato sulle opzioni passate. La classe gestisce anche correttamente il suffisso Z (designazione UTC), i fusi orari nel formato +HH:mm e la precisione ridotta (solo data senza ora).
Secondo la Specifica ISO (ISO 8601-1:2019), lo standard supporta quattro livelli di precisione: anno (2024), anno-mese (2024-07), data completa (2024-07-21) e data-ora con fuso orario (2024-07-21T14:30:00+00:00). ISO8601DateFormatter copre tutti questi livelli attraverso una combinazione di opzioni di formato, liberando lo sviluppatore dalla costruzione manuale di stringhe dateFormat.
Il principio di funzionamento di ISO8601DateFormatter si basa su una combinazione di opzioni bit (formatOptions), ciascuna delle quali include un componente specifico di data o ora nell'output. Ad esempio, l'opzione .withFullDate include anno, mese e giorno; .withTime include ore, minuti e secondi. Combinando le opzioni, lo sviluppatore ottiene il livello di precisione desiderato senza scrivere una stringa dateFormat.
Internamente, ISO8601DateFormatter utilizza la libreria ICU per l'analisi, ma con regole ISO 8601 fisse. Ciò significa che ignora le impostazioni di Locale e TimeZone sul dispositivo — il risultato è sempre prevedibile. Per impostare il fuso orario si usa la proprietà timeZone, che per impostazione predefinita è UTC. Se timeZone è impostato su nil, viene utilizzata l'ora locale del dispositivo.
| Opzione | Descrizione | Esempio di output |
|---|---|---|
| .withFullDate | Anno, mese, giorno | 2024-07-21 |
| .withTime | Ore, minuti, secondi | 14:30:00 |
| .withMilliseconds | Frazioni di secondo (fino a 3 cifre) | .123 |
| .withFractionalSeconds | Frazioni di secondo (qualsiasi precisione) | .123456 |
| .withTimeZone | Fuso orario | +03:00 |
| .withColonSeparatorInTimeZone | Separatore due punti nel fuso orario | +03:00 (contro +0300) |
| .withInternetDateTime | Formato completo (data + ora + fuso) | 2024-07-21T14:30:00+00:00 |
Combinazione di opzioni: .withInternetDateTime equivale a combinare .withFullDate, .withTime e .withTimeZone. Per analizzare stringhe con millisecondi, aggiungere .withFractionalSeconds. È importante ricordare che .withMilliseconds limita le frazioni di secondo a tre cifre, mentre .withFractionalSeconds supporta qualsiasi precisione — da una a nove cifre dopo il punto decimale.
Le opzioni di formato di ISO8601DateFormatter sono divise in tre gruppi: componenti di data (withFullDate, withYear, withMonth, withDay, withWeekOfYear), componenti di ora (withTime, withHours, withMinutes, withSeconds) e impostazioni aggiuntive (withMilliseconds, withFractionalSeconds, withTimeZone, withColonSeparatorInTimeZone, withDashSeparatorInDate, withFullTime). Combinandoli, è possibile ottenere praticamente qualsiasi sottoformato ISO 8601.
Sfumatura importante: .withFractionalSeconds e .withMilliseconds si escludono a vicenda — se entrambi sono impostati, viene applicato .withFractionalSeconds. Per analizzare i millisecondi dai dati del server, si raccomanda .withFractionalSeconds, poiché molti server inviano frazioni di secondo con tre, sei o nove cifre e .withFractionalSeconds gestisce qualsiasi lunghezza.
import Foundation
// Configure ISO8601DateFormatter
let formatter = ISO8601DateFormatter()
formatter.timeZone = TimeZone(secondsFromGMT: 0)
// Different format option combinations
formatter.formatOptions = [.withFullDate]
let dateOnly = formatter.string(from: Date())
print("Date: \(dateOnly)")
formatter.formatOptions = [.withFullDate, .withTime]
let dateTime = formatter.string(from: Date())
print("DateTime: \(dateTime)")
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let full = formatter.string(from: Date())
print("Full: \(full)")
// Parse string with milliseconds
let serverString = "2024-07-21T14:30:00.123456+03:00"
if let parsed = formatter.date(from: serverString) {
print("Parsed: \(parsed)")
}
Uso di base di ISO8601DateFormatter si riduce alla creazione di un'istanza, all'impostazione di timeZone (UTC è consigliato per i dati del server) e formatOptions, dopodiché è possibile chiamare string(from:) per formattare e date(from:) per analizzare. A differenza di DateFormatter, non c'è bisogno di preoccuparsi di Locale — la classe ignora le impostazioni regionali.
import Foundation
let formatter = ISO8601DateFormatter()
// Parse different ISO 8601 formats
let strings: [String] = [
"2024-07-21T14:30:00Z",
"2024-07-21T14:30:00+03:00",
"2024-07-21T14:30:00.123Z",
"2024-07-21"
]
for str in strings {
if let autoParsed = formatter.date(from: str) {
print("Parsed '\(str)': \(autoParsed)")
} else {
// Use withFullDate for date-only strings
formatter.formatOptions = [.withFullDate]
if let fallback = formatter.date(from: str) {
print("Fallback parsed '\(str)': \(fallback)")
}
formatter.formatOptions = [.withInternetDateTime]
}
}
// Serialize to RFC 3339 (GitHub API)
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let rfc3339 = formatter.string(from: Date())
print("RFC 3339: \(rfc3339)")
Analizzare date con frazioni di secondo di lunghezza variabile è una caratteristica di molte API moderne. Un server può inviare 2024-07-21T14:30:00.123Z (3 cifre) o 2024-07-21T14:30:00.123456Z (6 cifre). ISO8601DateFormatter con l'opzione .withFractionalSeconds gestirà correttamente entrambe le varianti, mentre DateFormatter con dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" gestirà solo millisecondi a tre cifre.
import Foundation
let variantFormatter = ISO8601DateFormatter()
variantFormatter.formatOptions = [
.withInternetDateTime,
.withFractionalSeconds
]
// Different fractional second precision
let variants: [String] = [
"2024-07-21T14:30:00.1Z",
"2024-07-21T14:30:00.12Z",
"2024-07-21T14:30:00.123Z",
"2024-07-21T14:30:00.123456Z",
"2024-07-21T14:30:00.123456789Z"
]
for variant in variants {
if let parsed = variantFormatter.date(from: variant) {
print("OK: \(variant) -> \(parsed)")
} else {
print("FAIL: \(variant)")
}
}
// Use withMilliseconds (3 digits only)
variantFormatter.formatOptions = [
.withInternetDateTime,
.withMilliseconds
]
let milliParsed = variantFormatter.string(from: Date())
print("With milliseconds: \(milliParsed)")
Test di analisi di tutte le varianti: il codice mostrato dimostra che ISO8601DateFormatter con .withFractionalSeconds gestisce con successo frazioni di secondo di qualsiasi lunghezza da 1 a 9 cifre. Questo è importante per la compatibilità con diverse piattaforme server: .NET genera spesso 7 cifre (tick da 100 nanosecondi), Python — 6, Java — 3 o 9 a seconda della versione.
DateFormatter può anch'esso analizzare ISO 8601, ma richiede la configurazione manuale di dateFormat, locale e timeZone. Il problema principale è che DateFormatter dipende da Locale, e se non si imposta en_US_POSIX, l'analisi potrebbe fallire per gli utenti di regioni con formati di data non standard. ISO8601DateFormatter risolve questo problema a livello architetturale: non utilizza Locale.
| Parametro | ISO8601DateFormatter | DateFormatter |
|---|---|---|
| Configurazione Locale | Non richiesta (ignora) | en_US_POSIX obbligatorio |
| DateFormat | Automatico (tramite opzioni) | Stringa di formato manuale |
| Frazioni di secondo | Qualsiasi precisione (.withFractionalSeconds) | SSS fisso |
| Suffisso Z | Gestito correttamente | Tramite dateFormat |
| Prestazioni | Superiori (specializzato) | Inferiori (generico) |
| Standard | Solo ISO 8601 | Qualsiasi formato |
| Versione iOS | iOS 10+ | iOS 2+ |
Quando usare DateFormatter: se è necessario formattare una data in un formato non ISO 8601 (ad esempio, "21 luglio 2024" per l'interfaccia utente) o se è necessario supportare iOS 9 e versioni precedenti. Per tutte le attività di scambio di date tra server e client, utilizzare ISO8601DateFormatter — è più sicuro, più performante e richiede meno codice. DateFormatter per ISO 8601 è una fonte di potenziali bug legati alla locale e alle impostazioni regionali.
Migrazione da DateFormatter a ISO8601DateFormatter: sostituire la creazione di DateFormatter + configurazione di dateFormat + locale + timeZone con la creazione di ISO8601DateFormatter + configurazione di formatOptions + timeZone. L'analisi della stringa rimane invariata tramite date(from:). Per la retrocompatibilità, è possibile utilizzare #available(iOS 10, *) con un fallback a DateFormatter.
Configurazione dimenticata di formatOptions fa sì che il formattatore utilizzi il valore predefinito — .withInternetDateTime. Se il server invia una data senza ora (2024-07-21), l'analisi restituirà nil. Verificare sempre che formatOptions copra tutti i formati possibili che possono arrivare dal server. Per le API con formati variabili, utilizzare tentativi di fallback con diverse combinazioni di opzioni.
Confusione tra withMilliseconds e withFractionalSeconds è un errore comune nell'analisi di date con frazioni di secondo. withMilliseconds si aspetta esattamente 3 cifre dopo il punto decimale. Se il server invia 6 cifre (microsecondi), l'analisi con withMilliseconds fallirà. Utilizzare .withFractionalSeconds per la compatibilità con qualsiasi numero di cifre. .withFractionalSeconds è disponibile da iOS 13; per le versioni precedenti, utilizzare DateFormatter con dateFormat.
Ignorare il fuso orario è un altro problema comune. Se il server invia una data con fuso orario (+03:00) e il formattatore è impostato su UTC, l'analisi non fallirà, ma il risultato sarà in UTC. Gli sviluppatori spesso si aspettano che Date conservi il fuso orario, ma Date è un momento assoluto nel tempo — non memorizza informazioni sul fuso orario. Per una visualizzazione corretta, salvare il fuso orario separatamente o utilizzare ISO8601DateFormatter con il timeZone appropriato.
Secondo l'Apple Forum (2024), circa il 20% delle domande su ISO8601DateFormatter riguarda il formato in cui i secondi sono opzionali. Lo standard ISO 8601 consente un formato senza secondi: 2024-07-21T14:30+03:00. ISO8601DateFormatter con .withInternetDateTime non supporta questo formato — per analizzarlo sarà necessario DateFormatter con dateFormat = "yyyy-MM-dd'T'HH:mmZ". Questa limitazione è importante da considerare quando si lavora con API che utilizzano il formato orario abbreviato.
Domande frequenti
ISO8601DateFormatter è una classe Foundation specializzata per formattare e analizzare date in formato ISO 8601, disponibile da iOS 10. Gestisce automaticamente i formati standard senza impostare manualmente dateFormat.
ISO8601DateFormatter non dipende da Locale, usa opzioni invece di dateFormat e gestisce correttamente frazioni di secondo di qualsiasi lunghezza. DateFormatter è universale, ma richiede configurazione manuale ed è soggetto a bug legati alle impostazioni regionali.
Utilizzare l'opzione .withFractionalSeconds — supporta da 1 a 9 cifre dopo il punto decimale. Non utilizzare .withMilliseconds se la precisione può variare. .withFractionalSeconds è disponibile da iOS 13.
UTC per impostazione predefinita. Per cambiarlo, impostare la proprietà timeZone. Se timeZone = nil, viene utilizzata l'ora locale del dispositivo. Quando si analizza una stringa con fuso orario esplicito nel formato +HH:MM, il formattatore lo considera automaticamente.
Perché formatOptions per impostazione predefinita è .withInternetDateTime, che si aspetta data + ora + fuso orario. Per analizzare solo la data, impostare formatOptions = [.withFullDate]. Per supportare entrambi i formati, utilizzare il fallback con diverse opzioni.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche