Calendar è una classe Foundation che definisce un sistema calendario e fornisce metodi per calcoli calendari: estrazione di componenti di data, calcolo delle differenze tra date, ricerca di limiti di periodi e spostamento di date. Il calendario collega il tempo assoluto (Date) a componenti leggibili dall'uomo e tiene conto delle caratteristiche regionali: inizio della settimana, fuso orario e ora legale. Secondo la Documentazione per Sviluppatori Apple (2025), Foundation supporta 17 sistemi calendario — dal gregoriano al buddista e giapponese — rendendo Calendar uno strumento universale per applicazioni internazionalizzate.
Punti chiave
Calendar è una classe Foundation che implementa calcoli calendari basati su ICU (International Components for Unicode). Il calendario definisce come il tempo assoluto (Date) corrisponde ai componenti del calendario: anno, mese, giorno, ora, minuto, secondo. Senza Calendar è impossibile sapere che anno, mese e giorno sono — Date stesso non contiene queste informazioni.
Il calendario tiene conto di tre gruppi di parametri: il sistema calendario (gregoriano, buddista, giapponese), il fuso orario e le impostazioni regionali. Calendar.current combina tutti e tre dalle impostazioni di sistema dell'utente. Calendar.autoupdatingCurrent è una versione speciale che si aggiorna automaticamente al cambiamento delle impostazioni senza riavviare l'applicazione tramite NotificationCenter.
Calendar è un tipo valore (value type) in Foundation. Calendar(identifier:) crea una nuova istanza con parametri fissi. Calendar può essere copiato, confrontato con == e usato come chiave in un dizionario. Ciò consente di creare calendari con impostazioni specifiche di timeZone e locale per i test.
Calendar è la versione Swift di NSCalendar di Objective-C, con ponte tramite as Calendar / as NSCalendar. Nello Swift moderno, Calendar è usato ovunque. NSCalendar rimane per compatibilità all'indietro con le API Objective-C. Calendar ha un set completo di metodi senza il prefisso NS, con argomenti type-safe e opzionali Swift.
Sicurezza dei thread — Calendar è thread-safe per la lettura. Un'istanza creata può essere letta in sicurezza da più thread. La modifica delle proprietà (timeZone, locale) non è thread-safe — create istanze separate di Calendar per configurazioni diverse.
Foundation supporta 17 sistemi calendario tramite l'enumerazione Calendar.Identifier. Ogni sistema ha le proprie regole per anni bisestili, numero di mesi e inizio dell'era. La scelta del calendario influenza tutti i calcoli: dateComponents, dateInterval, nextDate.
Principali sistemi calendario:
Calendar(identifier: .gregorian) — il più utilizzato. È conforme allo standard internazionale ISO 8601 ed è il calendario predefinito nella maggior parte dei paesi. Per applicazioni con pubblico internazionale, usa Calendar.current — corrisponde automaticamente al calendario di sistema dell'utente.
| Identificatore | Tipo | Regione di utilizzo |
|---|---|---|
| .gregorian | Solare | Internazionale |
| .buddhist | Solare | Thailandia, Cambogia |
| .japanese | Solare | Giappone |
| .hebrew | Lunisolare | Israele |
| .islamic | Lunare | Paesi islamici |
| .chinese | Lunisolare | Cina |
DateComponents e Calendar sono una coppia inscindibile. Calendar.dateComponents(_:from:) estrae componenti da Date rispettando il fuso orario del calendario. Calendar.date(from:) assembla un Date da DateComponents, riempiendo i campi mancanti con valori predefiniti: giorno = 1, ora = 0, minuto = 0, secondo = 0.
Il metodo Calendar.component estrae un singolo componente, comodo per controlli rapidi. Calendar.dateComponents estrae più componenti in una singola chiamata — è più efficiente poiché Calendar esegue i calcoli calendari una sola volta invece che per ogni componente separatamente. Per una lista di 3+ componenti, usa sempre dateComponents.
Calendar.compare confronta due Date con una precisione determinata. Il parametro toGranularity definisce la precisione del componente: .year confronta solo l'anno, .month — anno e mese, .day — anno, mese, giorno. Utile per verificare se due date cadono nello stesso giorno, ignorando l'ora.
let calendar = Calendar.current
let now = Date()
// Estrarre un singolo componente
let year = calendar.component(.year, from: now)
// Estrarre un insieme di componenti
let comps = calendar.dateComponents(
[.year, .month, .day], from: now
)
// Confrontare con granularità di giorno
let isSameDay = calendar.compare(date1, to: date2,
toGranularity: .day) == .orderedSame
// Verificare se la data è oggi
let isToday = calendar.isDateInToday(someDate)
Calendar.isDateInToday, isDateInTomorrow, isDateInYesterday — metodi per controlli relativi. Calendar.isDate(_:inSameDayAs:) verifica se due date cadono nello stesso giorno calendario considerando il fuso orario del calendario. Questi metodi usano Calendar.compare internamente e sono ottimizzati per chiamate frequenti.
Calendar.dateInterval è uno dei metodi più utili per analisi e interfaccia utente. Restituisce un DateInterval per il componente specificato: inizio e fine di un giorno, settimana, mese, anno. DateInterval contiene start (Date) e end (Date) — i limiti del periodo. Ad esempio, dateInterval(of: .weekOfYear, for: Date()) restituisce l'inizio di lunedì e la fine di domenica della settimana corrente.
Calendar.date con byAdding — un metodo per spostare le date. Calendar.date(byAdding: .day, value: 7, to: Date()) restituisce la data una settimana dopo. Calendar.date(byAdding: DateComponents) è una versione più flessibile che consente di spostare più componenti contemporaneamente: +1 mese +3 giorni. Calendar tiene automaticamente conto delle diverse lunghezze dei mesi e degli anni bisestili.
Calendar.nextDate cerca la data successiva corrispondente ai DateComponents specificati. Il parametro matchingPolicy definisce il comportamento in caso di mancata corrispondenza: .nextTime — la prossima corrispondenza oraria, .nextTimePreservingSmallerComponents — conserva minuti e secondi dalla data originale, .strict — richiede una corrispondenza esatta.
let calendar = Calendar.current
let today = Date()
// Inizio e fine della settimana
let weekInterval = calendar.dateInterval(
of: .weekOfYear, for: today
)!
// Spostare di 1 mese
let nextMonth = calendar.date(
byAdding: .month, value: 1, to: today
)!
// Spostare tramite DateComponents
var delta = DateComponents()
delta.month = 1
delta.day = 3
let shifted = calendar.date(byAdding: delta, to: today)!
// Prossimo venerdì 13
let friday13Components = DateComponents(
weekday: 6, day: 13
)
let nextFriday13 = calendar.nextDate(
after: today, matching: friday13Components,
matchingPolicy: .nextTime
)
EnumerateDates — un metodo potente per iterare sulle date per modello. Calendar.enumerateDates(startingAfter:matching:matchingPolicy:using:) chiama un blocco per ogni corrispondenza finché il blocco restituisce stop = true. Utilizzato per generare eventi ricorrenti in calendari e pianificazioni. Questo metodo è più efficiente di un ciclo manuale con nextDate, poiché è ottimizzato da ICU.
TimeZone è parte integrante di Calendar. Il fuso orario determina a quale ora calendario corrisponde un Date assoluto. Lo stesso Date in UTC e a Mosca produce componenti diversi: Date() in UTC può mostrare le 10:00, mentre in MSK — le 13:00. Calendar.timeZone per impostazione predefinita è TimeZone.current.
Locale influisce sul primo giorno della settimana, sul numero minimo di giorni nella prima settimana dell'anno (minDaysInFirstWeek) e sui nomi dei mesi/giorni della settimana (durante la conversione tramite DateFormatter). Calendar.locale per impostazione predefinita è Locale.current. Nelle impostazioni regionali russe la settimana inizia lunedì, in quelle americane — domenica.
Calendar.availableIdentifiers restituisce un elenco di tutti gli identificatori di calendario supportati. La proprietà statica Calendar.availableCalendarIdentifiers è un array di stringhe con gli stessi identificatori. Utilizzato per costruire un'interfaccia di selezione del calendario e per verificare la disponibilità di un sistema calendario specifico sul dispositivo.
// Calendar con fuso orario specifico
var utcCalendar = Calendar(identifier: .gregorian)
utcCalendar.timeZone = TimeZone(identifier: "UTC")!
// Calendar con impostazioni regionali russe
var russianCalendar = Calendar(identifier: .gregorian)
russianCalendar.locale = Locale(identifier: "ru_RU")
// Il primo giorno feriale dipende dalle impostazioni regionali
let firstWeekday = russianCalendar.firstWeekday
// 2 = lunedì (in ru_RU)
// Elenco dei calendari disponibili
for identifier in Calendar.availableIdentifiers {
print(identifier)
}
firstWeekday — una proprietà di Calendar che determina quale giorno della settimana è considerato il primo. Nelle impostazioni regionali russe Sunday = 2 (lunedì è il primo). Nelle impostazioni regionali americane Sunday = 1. Ciò influisce su weekOfMonth e weekOfYear: la stessa data può appartenere a diversi numeri di settimana in diverse impostazioni regionali. Per applicazioni che lavorano con le date, usa Calendar.current o imposta esplicitamente firstWeekday.
Consideriamo scenari pratici che dimostrano le capacità di Calendar. Ogni esempio risolve un compito specifico di sviluppo iOS e mostra il modo corretto di utilizzare i calcoli calendari.
Calendar.dateInterval(of: .month, for:) restituisce i limiti del mese corrente. Verificare se un Date cade all'interno di questo intervallo è il modo più veloce per determinare se una data appartiene al mese corrente. Un'alternativa è Calendar.compare con granularità .month: se il risultato è .orderedSame, il mese corrisponde.
func isInCurrentMonth(_ date: Date) -> Bool {
let calendar = Calendar.current
let monthInterval = calendar.dateInterval(
of: .month, for: Date()
)!
return monthInterval.contains(date)
}
// Numero di giorni in un mese
func daysInMonth(for date: Date) -> Int {
let calendar = Calendar.current
return calendar.range(
of: .day, in: .month, for: date
)?.count ?? 0
}
// Aggiunta di mesi con corretta gestione
func addMonths(_ months: Int, to date: Date) -> Date {
let calendar = Calendar.current
return calendar.date(
byAdding: .month, value: months, to: date
)!
}
Calendar.range(of:in:for:) restituisce l'intervallo di valori validi per un componente specificato nel contesto di un altro componente. Ad esempio, range(of: .day, in: .month, for: date) restituisce 1..<32 per mesi con 31 giorni o 1..<29 per febbraio di un anno non bisestile. Questo è il modo corretto per ottenere il numero di giorni in un mese, senza usare valori fissi.
Aggiunta di mesi tramite Calendar.date(byAdding:value:to:) gestisce correttamente le date limite. Se si aggiunge 1 mese al 31 gennaio, Calendar restituisce il 28 febbraio (o 29 in anno bisestile), invece del 3 marzo che risulterebbe dalla semplice aggiunta di 30 giorni tramite TimeInterval. Questa è un'ulteriore ragione per non usare TimeInterval per calcoli calendari.
| Metodo Calendar | Scopo | Esempio |
|---|---|---|
| dateInterval | Limiti di periodo | Inizio e fine di un mese |
| range(of:in:for:) | Intervallo di componente | Giorni nel mese corrente |
| date(byAdding:) | Spostamento di data | +1 mese da oggi |
| isDateInToday | Verifica di oggi | La data è di oggi? |
| compare(toGranularity:) | Confronto con precisione | Stesso giorno ignorando l'ora |
Domande frequenti
Calendar.current restituisce il calendario dalle impostazioni di sistema dell'utente — potrebbe non essere gregoriano (ad esempio, buddista in Thailandia). Calendar(identifier: .gregorian) crea sempre un calendario gregoriano indipendentemente dalle impostazioni. Usa Calendar.current per visualizzare le date e un identificatore esplicitamente scelto per la logica di business.
Ciò è dovuto alle diverse lunghezze dei mesi. Se la data corrente è il 31 gennaio, l'aggiunta di 1 mese produce il 28 febbraio, poiché febbraio non ha 31 giorni. Calendar regola automaticamente la data all'ultimo giorno valido del mese. Per un controllo preciso, usa DateComponents con day: 1 per passare al primo giorno del mese.
DateFormatter usa Calendar.current — il calendario di sistema dell'utente. Se un'applicazione deve sempre visualizzare le date nel calendario gregoriano indipendentemente dalle impostazioni, imposta formatter.calendar = Calendar(identifier: .gregorian). Ciò garantisce una visualizzazione uniforme per tutti gli utenti.
Calendar.range(of: .day, in: .year, for: date) restituisce 365 o 366 giorni. Più semplice: Calendar.date(from: DateComponents(year: year, month: 2, day: 29)) != nil — se il 29 febbraio esiste, l'anno è bisestile. Calendar gestisce automaticamente le regole del sistema calendario specifico.
Sì, la proprietà firstWeekday è modificabile. La modifica influisce su weekOfMonth, weekOfYear e tutti i calcoli relativi ai numeri di settimana. Impostando locale = Locale(identifier: “ru_RU”), firstWeekday diventa automaticamente 2 (lunedì). L'impostazione manuale sovrascrive il valore dalle impostazioni regionali.
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