RelativeDateTimeFormatter: essenza, date relative e Swift

Autore: IT Sectr Pubblicato: 2026-07-13 Tempo di lettura: 10 min

RelativeDateTimeFormatter è una classe Foundation in iOS e macOS che converte date assolute in frasi relative leggibili dall'uomo: "5 minuti fa", "ieri", "tra 3 giorni". Secondo Apple Developer Documentation, 2024, RelativeDateTimeFormatter seleziona automaticamente l'unità appropriata (secondi, minuti, ore, giorni) e localizza l'output nella lingua delle impostazioni internazionali correnti del dispositivo. A differenza del calcolo manuale della differenza tra le date tramite Calendar, questa classe tiene conto delle caratteristiche linguistiche di ogni lingua: per alcune lingue i numerali si declinano, per altre viene usata una forma speciale per la parola "ieri". La classe è disponibile a partire da iOS 13 e macOS 10.15.

Punti chiave

  • RelativeDateTimeFormatter — una classe per visualizzare date relative in iOS e macOS (iOS 13+)
  • Output localizzato — sceglie automaticamente le frasi nella lingua delle impostazioni internazionali correnti
  • Tre tipi di contesto — passato (fa), futuro (tra), presente (ora) con frasi diverse
  • Selezione automatica dell'unità — secondi, minuti, ore, giorni, settimane, mesi, anni
  • Personalizzazione dello stile — numeric (tra 3 giorni) o abbreviated (tra 3 gg.)

Cos'è RelativeDateTimeFormatter?

RelativeDateTimeFormatter è una sottoclasse di Formatter in Foundation che prende una Date (o una differenza in secondi) e restituisce una stringa localizzata con tempo relativo. Ad esempio, per una data 5 minuti prima di quella corrente, restituisce "5 minuti fa". La classe supporta tre contesti temporali: passato, futuro e presente.

La logica interna di RelativeDateTimeFormatter utilizza Calendar e Locale per calcolare la differenza tra le date e selezionare la forma grammaticale corretta. Per l'italiano, sceglie tra "1 minuto fa" e "5 minuti fa". Questa funzionalità si basa sui dati ICU (International Components for Unicode) e non richiede configurazione aggiuntiva da parte dello sviluppatore.

Secondo Apple WWDC 2019, RelativeDateTimeFormatter è diventato parte del framework per semplificare la localizzazione — prima della sua introduzione, gli sviluppatori dovevano calcolare manualmente la differenza delle date e sostituire le stringhe localizzate tramite String.localizedStringWithFormat. Ciò causava errori di declinazione (specialmente per le lingue slave e arabe) e una selezione errata delle unità di misura.

Come fa RelativeDateTimeFormatter a mostrare "5 minuti fa"?

L'algoritmo di RelativeDateTimeFormatter consiste in tre passaggi: calcolare la differenza tra la data passata e il momento corrente, selezionare l'unità appropriata (la più grande che non dia zero) e formattare in base alle impostazioni internazionali. Ad esempio, per una differenza di 3720 secondi (1 ora e 2 minuti), viene selezionata l'unità "ora" e il risultato è "1 ora fa", non "62 minuti fa".

Le unità vengono selezionate secondo il principio della "più grande non nulla": se la differenza è maggiore di 86400 secondi (1 giorno), vengono usati i giorni; se maggiore di 604800 (1 settimana) — le settimane, e così via. Questo algoritmo garantisce che il risultato sia sempre letto naturalmente: invece di "518400 secondi fa", l'utente vede "6 giorni fa". I confini esatti delle unità sono determinati dal calendario delle impostazioni internazionali correnti.

Intervallo di differenzaUnitàEsempio per it_IT
0–59 secondiSeconds30 secondi fa
1–59 minutiMinutes5 minuti fa
1–23 oreHours3 ore fa
1–6 giorniDays2 giorni fa
7–27 giorniWeeks1 settimana fa
28 giorni–11 mesiMonths3 mesi fa
12+ mesiYears1 anno fa

Il contesto di formattazione determina la terminazione della frase. Per il passato: "fa" (italiano). Per il futuro: "tra 3 giorni" (italiano). Per il presente: "ora" (italiano). Il contesto viene impostato tramite il metodo localizeString(fromTimeInterval:) o direttamente tramite string(from: Date).

Impostazioni di unità e stili

RelativeDateTimeFormatter fornisce diverse impostazioni per controllare l'output: la proprietà unitsStyle determina lo stile di formattazione (numeric, abbreviated, full, spellOut), e maximumUnitCount limita il numero di unità visualizzate. Ad esempio, con maximumUnitCount = 1, una differenza di 1 ora e 30 minuti viene mostrata come "1 ora fa" invece di "1 ora e 30 minuti fa".

Stili di formattazione

  • .numeric — valore numerico completo: "3 giorni fa", "tra 2 settimane". Raccomandato per l'interfaccia utente predefinita
  • .abbreviated — forma abbreviata: "3 gg. fa", "tra 2 sett.". Per visualizzazione compatta in tabelle e elenchi
  • .full — forma verbale senza cifre: "tre giorni fa". Per Accessibilità e interfacce vocali
  • .spellOut — forma letterale con ortografia alternativa: "three days ago". Usato raramente, principalmente per usi specializzati

Limitazione delle unità: per impostazione predefinita, RelativeDateTimeFormatter mostra solo un'unità (la più grande). Impostare maximumUnitCount = 2 include l'unità successiva per una descrizione più precisa: "1 ora e 30 minuti fa". Tuttavia, ciò può rendere la stringa eccessivamente lunga per messaggi brevi (notifiche push, avvisi). Per l'interfaccia utente, si raccomanda di mantenere maximumUnitCount = 1.

swift
import Foundation

let formatter = RelativeDateTimeFormatter()

// Configure styles
formatter.unitsStyle = .numeric
formatter.maximumUnitCount = 1

// Examples with different dates
let fiveMinAgo = Date().addingTimeInterval(-300)
print("5 min ago: \(formatter.localizedString(for: fiveMinAgo, relativeTo: Date()))")

let twoDaysLater = Date().addingTimeInterval(172800)
print("2 days later: \(formatter.localizedString(for: twoDaysLater, relativeTo: Date()))")

// Abbreviated style
formatter.unitsStyle = .abbreviated
let oneWeekAgo = Date().addingTimeInterval(-604800)
print("Abbreviated: \(formatter.localizedString(for: oneWeekAgo, relativeTo: Date()))")

// Full style (spelled out)
formatter.unitsStyle = .full
let threeHours = Date().addingTimeInterval(10800)
print("Full: \(formatter.localizedString(for: threeHours, relativeTo: Date()))")

Scelta dello stile per contesti diversi: per un feed di notizie, usa .numeric con maximumUnitCount = 1 — questo è lo standard per Twitter, Instagram e Facebook. Per l'Accessibilità (VoiceOver), usa .full — i numeri scritti per esteso vengono letti in modo più naturale. Per elementi compatti (badge di notifica, barra di stato), usa .abbreviated per risparmiare spazio.

RelativeDateTimeFormatter in Swift: esempi

Uso base di RelativeDateTimeFormatter si riduce alla creazione di un'istanza, alla configurazione delle proprietà e alla chiamata di uno dei metodi di formattazione. I metodi principali sono: localizedString(for:relativeTo:) — per una coppia di date, localizedString(fromTimeInterval:) — per una differenza in secondi, e string(for:) — per Date con contesto automatico (passato/futuro).

swift
import Foundation

let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .numeric
formatter.maximumUnitCount = 1

// Social network UI examples
let postDates: [(title: String, date: Date)] = [
    ("Just now", Date().addingTimeInterval(-30)),
    ("5 min ago", Date().addingTimeInterval(-300)),
    ("Yesterday", Date().addingTimeInterval(-90000)),
    ("Last week", Date().addingTimeInterval(-700000)),
    ("Last year", Date().addingTimeInterval(-32000000))
]

for (title, postDate) in postDates {
    let relative = formatter.localizedString(
        for: postDate,
        relativeTo: Date()
    )
    print("\(title): \(relative)")
}

// Future dates
let reminderFormatter = RelativeDateTimeFormatter()
reminderFormatter.unitsStyle = .abbreviated
let inOneHour = Date().addingTimeInterval(3600)
let reminderText = reminderFormatter.localizedString(
    for: inOneHour,
    relativeTo: Date()
)
print("Reminder: \(reminderText)")

Gestione dello scenario "appena adesso" — RelativeDateTimeFormatter non ha supporto integrato per la frase "appena adesso" per intervalli molto piccoli. Per una differenza inferiore a 5 secondi, restituisce "0 secondi fa", che non è gradevole nell'interfaccia utente. Si consiglia di avvolgere la chiamata al formattatore in una logica condizionale: se la differenza è inferiore a una soglia impostata (ad esempio, 5 secondi) — mostra manualmente "appena adesso", altrimenti passa la data al formattatore.

swift
import Foundation

func relativeTimeString(from date: Date) -> String {
    let interval = Date().timeIntervalSince(date)

    // "Just now" threshold
    if interval < 5 {
        return "just now"
    }

    // "Today" threshold
    if interval < 60 {
        return "just now"
    }

    let formatter = RelativeDateTimeFormatter()
    formatter.unitsStyle = .numeric
    formatter.maximumUnitCount = 1

    // Display without "ago" suffix
    return formatter.localizedString(
        for: date,
        relativeTo: Date()
    )
}

print(relativeTimeString(from: Date().addingTimeInterval(-3)))
print(relativeTimeString(from: Date().addingTimeInterval(-120)))
print(relativeTimeString(from: Date().addingTimeInterval(-3600)))

Il metodo string(fromTimeInterval:) accetta una differenza in secondi e determina automaticamente il contesto (valore positivo — futuro, negativo — passato). Questo è comodo quando la differenza è già nota (ad esempio, ricevuta dal server come timestamp unix). In questo caso, non è necessario creare una Date — la differenza viene passata direttamente.

Localizzazione delle date relative

RelativeDateTimeFormatter localizza automaticamente l'output in base a Locale.current. Per cambiare la lingua di formattazione, imposta la proprietà locale — a differenza di DateFormatter, per RelativeDateTimeFormatter le impostazioni internazionali non sono fisse e possono essere modificate per ogni chiamata. Ciò consente di visualizzare date relative in una lingua diversa dalla lingua dell'interfaccia (ad esempio, contenuto nella lingua originale).

La complessità della localizzazione delle date relative risiede nelle caratteristiche grammaticali delle diverse lingue. L'italiano richiede diverse forme di numerali: "1 minuto", "2 minuti". L'arabo usa la forma plurale per i numeri da 3 a 10 e forme speciali per 11+. Il cinese non ha declinazioni, semplificando il compito. RelativeDateTimeFormatter copre tutti questi casi attraverso le regole ICU, senza necessità di codice aggiuntivo.

swift
import Foundation

let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .numeric
formatter.maximumUnitCount = 1

let targetDate = Date().addingTimeInterval(-7200) // 2 hours ago

// Different locales
let locales: [String] = ["ru_RU", "en_US", "de_DE", "fr_FR", "ja_JP", "ar_SA"]

for identifier in locales {
    formatter.locale = Locale(identifier: identifier)
    let result = formatter.localizedString(
        for: targetDate,
        relativeTo: Date()
    )
    print("\(identifier): \(result)")
}

// Check Italian pluralization
formatter.locale = Locale(identifier: "ru_RU")
let intervals: [TimeInterval] = [-60, -120, -180, -300]
for interval in intervals {
    let date = Date().addingTimeInterval(interval)
    print("\(-Int(interval / 60)) min: \(formatter.localizedString(for: date, relativeTo: Date()))")
}

Una sfumatura importante: RelativeDateTimeFormatter ignora TimeZone nel calcolo della differenza per le impostazioni .numeric — usa la differenza assoluta in secondi. Tuttavia, per lo stile .full (con numeri per esteso) e i casi speciali (ieri, oggi), TimeZone viene considerato. Imposta sempre TimeZone esplicitamente per coerenza, specialmente se l'applicazione lavora con date del server in UTC.

Errori comuni di formattazione

Ignorare TimeZone nel calcolo delle date relative — un errore comune quando si lavora con date del server. Se il server invia una Date in UTC e RelativeDateTimeFormatter usa TimeZone.current, la differenza potrebbe essere calcolata in modo errato per date vicine al momento corrente. Si raccomanda di impostare sempre formatter.timeZone = TimeZone(secondsFromGMT: 0) per i dati del server.

Selezione errata dell'unità per intervalli brevi — RelativeDateTimeFormatter arrotonda la differenza all'unità più grande. Per 25 ore, il risultato sarà "1 giorno fa", il che può trarre in inganno l'utente. Se è necessaria un'elevata precisione (ad esempio, per timer di conto alla rovescia), usa DateComponentsFormatter invece di RelativeDateTimeFormatter — consente di visualizzare più unità contemporaneamente.

Mancata verifica del TimeInterval negativo — se una data futura viene passata come passata (valore negativo in string(fromTimeInterval:)), il formattatore potrebbe restituire una stringa errata. Controlla sempre il segno dell'intervallo prima di passarlo al formattatore, specialmente quando lavori con dati del server dove il fuso orario potrebbe distorcere il calcolo.

Secondo Hacker News (2024), uno dei problemi più discussi di RelativeDateTimeFormatter è la mancanza di supporto integrato per "ieri" e "oggi" per la lingua inglese. Invece di "ieri", il formattatore per una differenza di 90000 secondi restituisce "1 giorno fa". Per l'italiano non esiste questo problema — "1 giorno fa" suona naturale, ma per l'interfaccia utente inglese "yesterday" è preferibile. Questa funzionalità non è supportata e richiede una verifica manuale tramite Calendar.isDateInToday/Yesterday.

Domande frequenti

Cos'è RelativeDateTimeFormatter?

RelativeDateTimeFormatter è una classe Foundation per visualizzare le date in formato relativo: "5 minuti fa", "tra 2 giorni". Disponibile da iOS 13 e macOS 10.15.

Come seleziona RelativeDateTimeFormatter le unità?

Secondo il principio dell'unità non nulla più grande — secondi, minuti, ore, giorni, settimane, mesi o anni. Ad esempio, per una differenza di 3720 secondi (1 ora e 2 minuti), viene selezionata l'unità "ora", non "minuti".

Come cambiare la lingua di output?

Imposta la proprietà locale sull'istanza Locale desiderata. Per impostazione predefinita viene usato Locale.current. Esempio: formatter.locale = Locale(identifier: "de_DE") per il tedesco.

Qual è la differenza tra .numeric e .abbreviated?

.numeric — forma completa ("3 giorni fa"), .abbreviated — forma abbreviata ("3 gg. fa"). La scelta dipende dal contesto: numeric per l'interfaccia utente principale, abbreviated per elementi compatti.

Come mostrare "appena adesso" invece di "0 secondi fa"?

Aggiungi un controllo manuale per un intervallo inferiore a 5-10 secondi. RelativeDateTimeFormatter non supporta "appena adesso" — per piccoli intervalli restituisce "0 secondi fa". Usa logica condizionale con una soglia.

Riepilogo

  • RelativeDateTimeFormatter — una classe comoda per visualizzare date relative in iOS 13+
  • Localizzazione automatica — declinazione corretta per tutte le lingue supportate tramite ICU
  • Tre stili — .numeric (standard), .abbreviated (compatto), .full (per esteso)
  • Selezione dell'unità — automatica basata sul valore non nullo più grande
  • Configurazione di TimeZone — obbligatoria per coerenza quando si lavora con date del server
  • Soglia "appena adesso" — non supportata nativamente; richiede verifica manuale dell'intervallo
  • Nessun supporto per "ieri" — il formattatore non usa la forma yesterday per l'inglese

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.

Discuti il progetto

Leggi anche