1. Introduzione
Quando incontri per la prima volta il problema “sposta la data di 7 giorni in avanti”, il cervello (soprattutto dopo le lezioni di matematica) dice: “Ok, un giorno e composto da 24 ore, 24*60*60 = 86400 secondi, andiamo”. E questo in effetti a volte funziona, mentre altre volte trasforma il tuo programma in un generatore di enigmi del tipo “perche la scadenza si e spostata di un'ora”.
Il problema e che un “giorno” in senso calendariale non corrisponde sempre esattamente a 24 ore. In alcuni fusi orari ci sono passaggi all'ora legale/solare, e un giorno specifico puo “contenere” 23 o 25 ore. Quindi “aggiungere 86400 secondi” significa “aggiungere esattamente 24 ore”, mentre “aggiungere 1 giorno” significa “passare al giorno calendariale successivo secondo le regole del calendario/fuso orario”.
Confrontiamo due operazioni sotto forma di schema:
flowchart LR
A["Date (momento temporale)"] -->|"+ 86400 secondi"| B["Date (tra 24 ore)"]
A -->|"+ 1 giorno tramite Calendar"| C["Date (giorno calendariale successivo)"]
Entrambe le frecce restituiscono Date, ma il loro significato e diverso. Ed e proprio qui che entra in scena Calendar: sa che cosa sono “giorno”, “mese”, “anno” e come cambiano secondo le regole del calendario.
2. Calendar: le regole delle date calendariali
Se Date e una “coordinata sulla linea temporale”, allora Calendar e “come tradurre la coordinata in parole comprensibili per l'uomo”. Si puo vedere Calendar come un istruttore di sopravvivenza nel mondo dei “mesi di diversa lunghezza”, degli “anni bisestili” e di altri piaceri inventati dagli esseri umani per non far sembrare la vita troppo noiosa.
La stessa Date non memorizza al suo interno “mese = gennaio”. Memorizza un momento temporale. Il calendario invece dice: “secondo le mie regole, questo momento corrisponde a tale anno, mese e giorno”. Per questo in Foundation e corretto pensare cosi: Date e memorizzazione, mentre Calendar/TimeZone sono interpretazione.
In questa lezione useremo il calendario cosi:
- estrarremo da Date i componenti (anno, mese, giorno),
- creeremo Date dai componenti (quando l'utente inserisce una data),
- aggiungeremo giorni/mesi in modo calendariale, non “in secondi”.
Per gli esempi useremo spesso:
let calendar = Calendar(identifier: .gregorian)
Questo rende il comportamento piu prevedibile: il “calendario gregoriano” e quello usato dalla maggior parte dei tuoi futuri utenti (e docenti).
3. Componenti della data: estrarre, costruire e verificare
DateComponents: un contenitore in cui quasi tutto e Optional
DateComponents e un contenitore per le parti della data: anno, mese, giorno, ora, minuto, ecc. E utile quando vuoi sia costruire una data dai pezzi inseriti dall'utente, sia definire uno “spostamento” come “aggiungi 2 mesi e 3 giorni”.
E si, un punto importante: i campi in DateComponents sono spesso Optional. Non perche gli sviluppatori di Foundation amino complicare la vita agli studenti. E perche i componenti possono essere parziali: per esempio, potresti avere “solo anno e mese”, ma non il giorno. Questo modello e piu onesto.
Una piccola tabella promemoria:
| Componente | Cosa significa | Esempio di valore |
|---|---|---|
|
anno calendariale | |
|
mese (1...12) | 1 (gennaio) |
|
giorno del mese | |
|
ora | |
|
minuti | |
E subito un pensiero pratico: DateComponents non e Date. E un “insieme di parti”. A costruire un “vero momento temporale” a partire da quelle parti ci pensa il calendario.
Estrarre i componenti da Date: “che giorno e oggi?”
Finche non impariamo a formattare elegantemente una data in stringa (sara un argomento a parte), il modo piu chiaro per mostrare il risultato a un principiante e estrarre i componenti e stamparli.
Dopo questa sezione sarai in grado di rispondere a domande come: “Che anno e adesso?”, “Che giorno del mese e oggi?” — senza trasformare il codice in una lettura della sfera di cristallo con print(Date()).
import Foundation
let calendar = Calendar(identifier: .gregorian)
let now = Date()
let parts = calendar.dateComponents([.year, .month, .day], from: now)
print(parts.year as Any) // per esempio: Optional(2026)
print(parts.month as Any) // per esempio: Optional(1)
print(parts.day as Any) // per esempio: Optional(16)
Qui as Any e un semplice “trucco” per poter stampare Optional senza unwrap. Ma nella logica reale (soprattutto se calcoli qualcosa) e meglio fare l'unwrap con attenzione.
Esempio con if let:
import Foundation
let calendar = Calendar(identifier: .gregorian)
let parts = calendar.dateComponents([.year, .month, .day], from: Date())
if let y = parts.year, let m = parts.month, let d = parts.day {
print("\(y)-\(m)-\(d)") // per esempio: 2026-1-16
}
Creare Date dai componenti: “una data inserita a mano dall'utente”
Lo spostamento delle date quasi sempre inizia da un punto di partenza. A volte e “adesso”, ma piu spesso e “la data inserita dall'utente”: data di prestito di un libro, data di inizio di un abbonamento, data di vacanza (che nei sogni e sempre un mese prima).
Costruiremo una piccola base per la nostra applicazione didattica da riga di comando: l'utente inserisce anno/mese/giorno, e noi proviamo a creare una Date. E qui e importante abituarsi a questo fatto: calendar.date(from:) restituisce Date? — perche l'utente puo inserire una data impossibile (per esempio, 31 febbraio, un classico).
import Foundation
var comps = DateComponents()
comps.year = 2026
comps.month = 2
comps.day = 31
let calendar = Calendar(identifier: .gregorian)
let date = calendar.date(from: comps)
print(date as Any) // nil (molto probabilmente), perche il 31 febbraio non esiste
Se il risultato e nil, non e un “errore del compilatore”. E il calendario che dice onestamente: “Non posso costruire un giorno del genere secondo le mie regole”.
4. Aggiungere giorni nel modo giusto: calendar.date(byAdding:value:to:)
Ora la parte piu interessante: l'aggiunta calendariale di giorni. In Foundation esiste questo metodo:
calendar.date(byAdding: .day, value: N, to: someDate)
Restituisce Date?, e ha senso: alcune operazioni possono dipendere dal contesto del calendario/fuso orario (e inoltre segue semplicemente il contratto di Foundation: se non si puo fare, restituirò nil).
Esempio “tra una settimana”:
import Foundation
let calendar = Calendar(identifier: .gregorian)
let now = Date()
let nextWeek = calendar.date(byAdding: .day, value: 7, to: now)
print(nextWeek as Any) // Optional(....)
Per renderlo piu “pratico”, aggiungiamo la stampa dei componenti (piu chiaro visivamente):
import Foundation
let calendar = Calendar(identifier: .gregorian)
let now = Date()
guard let nextWeek = calendar.date(byAdding: .day, value: 7, to: now) else {
print("Non siamo riusciti ad aggiungere 7 giorni")
exit(0)
}
let a = calendar.dateComponents([.year, .month, .day], from: now)
let b = calendar.dateComponents([.year, .month, .day], from: nextWeek)
print(a.year!, a.month!, a.day!) // esempio: 2026 1 16
print(b.year!, b.month!, b.day!) // esempio: 2026 1 23
Nota: qui ho usato ! dopo year/month/day. Nel codice reale e meglio fare unwrap con cautela, ma in questo punto supponiamo che il calendario fornisca questi componenti per Date(). Tieni solo a mente: ! e una promessa, e conviene farla solo quando sei davvero sicuro.
5. Aggiungere mesi: “31 gennaio + 1 mese”
Aggiungere mesi sembra quasi altrettanto semplice:
calendar.date(byAdding: .month, value: 1, to: date)
Ma proprio qui nascono le domande: “e se il giorno corrente non esiste nel mese successivo?” Per esempio: 31 gennaio + 1 mese... e in febbraio di solito non c'e il 31.
Ed e qui che e importante ricordare: il calendario sceglie da solo la data corretta secondo le sue regole. Di solito significa che “scivolera” alla fine del mese (per esempio, al 28 o 29 febbraio), ma il comportamento preciso puo dipendere dal calendario.
Esempio (un “caso limite” evidente):
import Foundation
let calendar = Calendar(identifier: .gregorian)
var comps = DateComponents()
comps.year = 2026
comps.month = 1
comps.day = 31
let start = calendar.date(from: comps) ?? Date()
let plusMonth = calendar.date(byAdding: .month, value: 1, to: start)
print(plusMonth as Any) // Optional(...), ma il giorno non sara "31 febbraio"
Per capire cosa abbiamo ottenuto, estraiamo di nuovo i componenti:
import Foundation
let calendar = Calendar(identifier: .gregorian)
var comps = DateComponents()
comps.year = 2026
comps.month = 1
comps.day = 31
guard let start = calendar.date(from: comps),
let plusMonth = calendar.date(byAdding: .month, value: 1, to: start) else {
print("Non siamo riusciti a costruire o spostare la data")
exit(0)
}
let s = calendar.dateComponents([.year, .month, .day], from: start)
let p = calendar.dateComponents([.year, .month, .day], from: plusMonth)
print("\(s.year!)-\(s.month!)-\(s.day!)") // 2026-1-31
print("\(p.year!)-\(p.month!)-\(p.day!)") // 2026-2-28 (spesso cosi)
Il senso e questo: quando aggiungi mesi, lavori con regole calendariali, quindi devi essere pronto al “taglio” del giorno fino alla fine del mese. Non e un bug — e la vita.
6. Aggiungere piu componenti insieme: DateComponents come “spostamento”
A volte e comodo aggiungere non “solo 10 giorni”, ma “2 mesi e 10 giorni”. Per farlo esiste un overload in cui aggiungiamo un intero DateComponents:
calendar.date(byAdding: components, to: date)
Somiglia molto a un “vettore di spostamento”.
import Foundation
let calendar = Calendar(identifier: .gregorian)
let now = Date()
var delta = DateComponents()
delta.month = 2
delta.day = 10
let shifted = calendar.date(byAdding: delta, to: now)
print(shifted as Any) // Optional(...)
Ed e un altro motivo per cui DateComponents e stato progettato con Optional: puoi impostare solo le parti dello spostamento che ti servono. Se non imposti l'ora, significa “l'ora non cambia”.
7. Mini-applicazione: calcoliamo la data di restituzione di un libro
Ora mettiamo tutto insieme in una piccola applicazione da terminale. Immagina che stiamo scrivendo una semplice utility per la routine di una biblioteca: l'utente inserisce la data di prestito del libro, e il programma calcola la data di restituzione. A volte il termine e espresso in giorni (per esempio, 14), altre volte “di 1 mese” (si, succede anche questo, soprattutto per gli abbonamenti).
Non useremo DateFormatter, per non anticipare i tempi: l'input sara composto da tre numeri (anno, mese, giorno), e anche l'output lo faremo tramite i componenti.
Funzione per leggere un numero: un piccolo ma importante mattone
Cominciamo dalla lettura sicura di un Int, perche l'utente e una creatura imprevedibile.
import Foundation
func readInt() -> Int {
let line = readLine() ?? ""
return Int(line) ?? 0
}
Si, questa e una versione molto “morbida”: se l'input non e un numero, otteniamo 0. Per un esempio didattico va bene: stiamo ancora imparando a “cucire” insieme le parti del programma.
Costruiamo la data di prestito dai componenti
import Foundation
let calendar = Calendar(identifier: .gregorian)
print("Anno di prestito:")
let y = readInt()
print("Mese di prestito (1-12):")
let m = readInt()
print("Giorno di prestito:")
let d = readInt()
var issue = DateComponents()
issue.year = y
issue.month = m
issue.day = d
let issueDate = calendar.date(from: issue)
print(issueDate as Any) // Optional(...) oppure nil
Se issueDate == nil, significa che l'utente ha inserito una data impossibile, e andare avanti non ha senso.
Aggiungiamo il termine in giorni e mesi
Facciamo una logica minimale: l'utente inserisce il “termine in giorni” e il “termine in mesi”. Se li inserisce entrambi, li aggiungiamo entrambi (perche no).
import Foundation
guard let issueDate = issueDate else {
print("Data di prestito non valida")
exit(0)
}
print("Quanti giorni aggiungere?")
let days = readInt()
print("Quanti mesi aggiungere?")
let months = readInt()
var delta = DateComponents()
delta.day = days
delta.month = months
let dueDate = calendar.date(byAdding: delta, to: issueDate)
print(dueDate as Any) // Optional(...) oppure nil
Stampiamo il risultato senza formatter: stampiamo i componenti
import Foundation
guard let dueDate = dueDate else {
print("Non siamo riusciti a calcolare la data di restituzione")
exit(0)
}
let out = calendar.dateComponents([.year, .month, .day], from: dueDate)
if let y2 = out.year, let m2 = out.month, let d2 = out.day {
print("Restituire entro: \(y2)-\(m2)-\(d2)") // per esempio: Restituire entro: 2026-2-13
}
A questo punto hai gia qualcosa di funzionante che fa la cosa fondamentale: non cerca di calcolare il calendario attraverso i secondi, ma chiede onestamente a Calendar.
8. Sfumarure: startOfDay(for:) e logica “per giorni”
Quando calcoli una scadenza “per giorni”, e importante capire che Date e sempre un momento temporale, inclusi ore/minuti/secondi. Se hai creato una data dai soli componenti year/month/day, di solito il calendario imposta l'ora all'inizio del giorno (ma dipende dal contesto). Se invece hai preso Date() come “adesso”, allora l'ora e quella corrente.
A volte la logica di business richiede: “la scadenza e proprio il giorno calendariale, senza legame con l'ora”. In questo caso e utile una tecnica: riportare la data all'inizio del giorno tramite startOfDay(for:). Aiuta a evitare sorprese quando “piu uno giorno” dovrebbe essere domani, ma per via dell'ora l'output sembra strano.
import Foundation
let calendar = Calendar(identifier: .gregorian)
let now = Date()
let dayStart = calendar.startOfDay(for: now)
let tomorrow = calendar.date(byAdding: .day, value: 1, to: dayStart)
print(dayStart) // output tecnico
print(tomorrow as Any) // Optional(...)
Questa tecnica e particolarmente utile quando confronti date “per giorni”: prima le normalizzi all'inizio del giorno, poi le confronti.
9. Errori tipici
Errore n. 1: aggiungere giorni tramite addingTimeInterval(86400) e pensare che significhi “giorno calendariale successivo”.
Negli esempi di test spesso sembra “funzionare”, ed e proprio per questo che l'errore e insidioso. Appena entri in un fuso orario reale con transizioni orarie, scopri che hai aggiunto 24 ore e non “sei passato a domani”. Per giorni/mesi/anni calendariali usa Calendar.date(byAdding:...).
Errore n. 2: dimenticare che calendar.date(from:) e calendar.date(byAdding:...) restituiscono Optional.
Il principiante spesso pensa: “Beh, ho scritto tutto correttamente, perche questo e Date??“ Perche l'utente puo inserire una data inesistente, e l'aggiunta potrebbe non essere risolvibile nelle regole del calendario. Lo stile corretto e guard let con un messaggio chiaro e uscita anticipata.
Errore n. 3: confondere “mese” e “giorno” come se avessero la stessa complessita.
I giorni scorrono uno dopo l'altro, e aggiungerli e di solito intuitivo. I mesi invece hanno lunghezze diverse. Quindi casi come “31 gennaio + 1 mese” vanno considerati normali, non come “un bug strano”. Il calendario portera la data a un valore corretto, e questo comportamento va tenuto presente.
Errore n. 4: aspettarsi che DateComponents sia sempre completo e non contenga nil.
I componenti della data e i componenti dello spostamento possono essere parziali. E un modello normale. Se hai estratto year/month/day da Date, fai unwrap con attenzione (if let) oppure stampali come as Any, ma non scrivere il codice come se ci fosse sempre una “terna pronta” di numeri.
Errore n. 5: mescolare “momento temporale” e “giorno calendariale” nella stessa logica senza normalizzazione.
Se confronti date “per giorni”, ma in una data l'ora e 00:00 e nell'altra 23:59, puoi ottenere risultati inattesi. In questi scenari e utile riportare le date all'inizio del giorno con calendar.startOfDay(for:), e solo dopo eseguire confronti e spostamenti.
GO TO FULL VERSION