Date e orari in Rust con chrono
I programmi hanno a che fare con il tempo di continuo. «Quanto ci ha messo?» «Che giorno scade la fattura?» «Quanti giorni mancano al viaggio?» Rust divide queste domande in due tipi.
Il modulo std::time della libreria standard è un cronometro: misura quanto tempo passa. Non sa niente di calendari, mesi o giorni della settimana. Per lavorare con il calendario, quasi ogni progetto Rust usa il crate chrono. Questa pagina comincia dal cronometro e poi passa al calendario.
Misurare il tempo trascorso con Instant
Sezione intitolata “Misurare il tempo trascorso con Instant”Instant::now() fa una lettura dell’orologio del computer. Più tardi, .elapsed() ti dice quanto tempo è passato da quella lettura, sotto forma di Duration:
use std::time::{Duration, Instant};
fn main() { let start = Instant::now();
let mut total: u64 = 0; for i in 0..1_000_000 { total += i; }
let elapsed = start.elapsed(); println!("totale = {total}"); println!("tempo: {:?}", elapsed); println!("tempo: {} ms", elapsed.as_millis()); println!("meno di 5 secondi? {}", elapsed < Duration::from_secs(5));}Qui non mostriamo l’output apposta: il tempo è diverso su ogni computer e a ogni esecuzione. Vedrai qualcosa come tempo: 6.862793ms. Prova a premere il pulsante due volte e confronta.
Un Instant serve solo a misurare. Non puoi stamparlo come data, e va solo in avanti. È esattamente quello che vuoi per cronometrare il codice, perché non viene mai influenzato da qualcuno che cambia l’orologio di sistema.
Una Duration è una quantità di tempo, come «90 secondi». La crei a partire da secondi o millisecondi, puoi sommare più durate e confrontarle:
use std::time::Duration;
fn main() { let short = Duration::from_millis(1500); let long = Duration::from_secs(90);
println!("{:?}", short); println!("{:?}", long); println!("{} ms", short.as_millis()); println!("{:.1} s", short.as_secs_f64());
let total = short + long; println!("totale: {:?}", total); println!("{} min {} s", long.as_secs() / 60, long.as_secs() % 60); println!("{}", short < long);}Output:
1.5s90s1500 ms1.5 stotale: 91.5s1 min 30 strueIncontrerai Duration in tanti posti: std::thread::sleep(Duration::from_millis(100)) mette in pausa un thread, e anche i timeout delle librerie di rete prendono una Duration.
L’ora dell’orologio con SystemTime
Sezione intitolata “L’ora dell’orologio con SystemTime”SystemTime è l’ora dell’orologio del computer, quella che vedi nell’angolo dello schermo. Di solito la si usa per chiedere quanto tempo è passato dall’epoca Unix, il 1° gennaio 1970 (UTC). Quel numero si chiama timestamp Unix:
use std::time::{SystemTime, UNIX_EPOCH};
fn main() { let now = SystemTime::now(); let since_1970 = now .duration_since(UNIX_EPOCH) .expect("l'orologio è impostato prima del 1970"); println!("Secondi dal 1° gennaio 1970: {}", since_1970.as_secs());}Il numero dipende da quando lo esegui, quindi anche qui non mostriamo l’output. Perché duration_since restituisce un Result? Perché l’orologio di sistema può essere sbagliato o essere spostato indietro. È anche per questo che dovresti usare Instant, non SystemTime, per cronometrare il codice.
Questo è più o meno tutto ciò che fa std::time. Non ha idea di cosa significhino «marzo» o «venerdì». Per quello ti serve chrono.
Aggiungere chrono
Sezione intitolata “Aggiungere chrono”Aggiungi il crate al progetto (vedi Cargo e crate se è una novità per te):
cargo add chrono[dependencies]chrono = "0.4"Anche il Rust Playground ha chrono, quindi tutti i pulsanti qui sotto funzionano. chrono ha due famiglie di tipi:
- I tipi naive (
NaiveDate,NaiveTime,NaiveDateTime) sono date e orari senza fuso orario: «14 marzo 2025» o «09:30» scritti su un calendario. La maggior parte del codice di tutti i giorni usa questi. DateTime<Tz>è un istante con un fuso orario, comeDateTime<Utc>oDateTime<Local>.
Creare date da valori fissi
Sezione intitolata “Creare date da valori fissi”NaiveDate::from_ymd_opt(anno, mese, giorno) costruisce una data. Restituisce un Option, perché non tutte le combinazioni sono date reali:
use chrono::{Datelike, NaiveDate, NaiveTime};
fn main() { let date = NaiveDate::from_ymd_opt(2025, 3, 14).unwrap(); println!("{date}"); println!("anno {}, mese {}, giorno {}", date.year(), date.month(), date.day());
let time = NaiveTime::from_hms_opt(9, 30, 0).unwrap(); let meeting = date.and_time(time); println!("{meeting}");
let other = date.and_hms_opt(17, 45, 10).unwrap(); println!("{other}");
let impossible = NaiveDate::from_ymd_opt(2025, 2, 30); println!("{:?}", impossible);}Output:
2025-03-14anno 2025, mese 3, giorno 142025-03-14 09:30:002025-03-14 17:45:10NoneIl 30 febbraio non esiste, quindi ottieni None invece di una data rotta. Con valori fissi scritti da te, .unwrap() va bene. Con valori che arrivano da un utente, gestisci il None.
La riga use chrono::Datelike porta nello scope i metodi .year(), .month() e .day(). (Timelike fa lo stesso per .hour() e .minute().)
La data e l’ora attuali
Sezione intitolata “La data e l’ora attuali”Utc::now() dà l’istante attuale in UTC. Local::now() lo dà nel fuso orario del computer:
use chrono::{Local, Utc};
fn main() { let utc_now = Utc::now(); let local_now = Local::now();
println!("UTC: {}", utc_now); println!("Locale: {}", local_now); println!("Oggi: {}", local_now.format("%Y-%m-%d"));
let today = local_now.date_naive(); println!("Come NaiveDate: {today}");}L’output è la data di oggi, quindi cambia ogni giorno. I server del Playground usano UTC, quindi lì le due righe mostreranno la stessa ora. .date_naive() scarta il fuso orario e l’ora, lasciando una semplice NaiveDate. Una buona abitudine: prendi «adesso» una volta sola ai bordi del programma, poi passa la data alle tue funzioni. Così restano facili da testare, come vedrai nell’esempio delle scadenze.
Formattare le date
Sezione intitolata “Formattare le date”.format() prende un pattern. Ogni codice con % viene sostituito da una parte della data. %Y è l’anno, %m il numero del mese, %d il giorno:
use chrono::NaiveDate;
fn main() { let date = NaiveDate::from_ymd_opt(2025, 3, 14).unwrap(); let dt = date.and_hms_opt(9, 5, 0).unwrap();
println!("{}", date.format("%Y-%m-%d")); println!("{}", date.format("%d/%m/%Y")); println!("{}", date.format("%A, %B %-d, %Y")); println!("{}", date.format("%a %d %b")); println!("{}", dt.format("%H:%M")); println!("{}", dt.format("%-I:%M %p"));
let file_name = date.format("report-%Y%m%d.txt").to_string(); println!("{file_name}");}Output:
2025-03-1414/03/2025Friday, March 14, 2025Fri 14 Mar09:059:05 AMreport-20250314.txtI codici che userai di più: %Y anno, %m mese (01–12), %d giorno (01–31), %H ora (00–23), %M minuto, %S secondo, %A nome del giorno della settimana, %B nome del mese. Un - dopo il % toglie lo zero iniziale, quindi %-d stampa 5 invece di 05. .format() restituisce qualcosa che puoi stampare. Chiama .to_string() quando ti serve una String.
Nota che chrono scrive i nomi di giorni e mesi in inglese («Friday», «March»). Per i nomi in italiano serve la feature unstable-locales di chrono con format_localized; per le date in formato numerico, come %d/%m/%Y, la lingua non conta.
Leggere date dal testo
Sezione intitolata “Leggere date dal testo”NaiveDate::parse_from_str fa il contrario: legge il testo usando lo stesso pattern con %. Restituisce un Result, perché il testo potrebbe non corrispondere:
use chrono::{NaiveDate, NaiveDateTime};
fn main() { let a = NaiveDate::parse_from_str("2025-03-14", "%Y-%m-%d"); println!("{:?}", a);
let b = NaiveDate::parse_from_str("14/03/2025", "%d/%m/%Y"); println!("{:?}", b);
let c: Result<NaiveDate, _> = "2025-03-14".parse(); println!("{:?}", c);
let d = NaiveDateTime::parse_from_str("2025-03-14 09:30", "%Y-%m-%d %H:%M"); println!("{:?}", d);
for text in ["2025-13-01", "March 14", "2025-03"] { match NaiveDate::parse_from_str(text, "%Y-%m-%d") { Ok(date) => println!("Letta: {date}"), Err(e) => println!("Impossibile leggere {text:?}: {e}"), } }}Output:
Ok(2025-03-14)Ok(2025-03-14)Ok(2025-03-14)Ok(2025-03-14T09:30:00)Impossibile leggere "2025-13-01": input is out of rangeImpossibile leggere "March 14": input contains invalid charactersImpossibile leggere "2025-03": premature end of inputPer la forma standard AAAA-MM-GG puoi anche chiamare semplicemente .parse(). Il mese 13 non esiste, quindi il primo esempio sbagliato è «out of range» (fuori intervallo). I messaggi di errore di chrono sono in inglese: oltre a «fuori intervallo», dicono «contiene caratteri non validi» e «fine dell’input prematura». Sono brevi, ma abbastanza chiari da mostrare a un utente.
Calcoli con le date
Sezione intitolata “Calcoli con le date”Aggiungere giorni a una data a mano è sorprendentemente difficile: i mesi hanno lunghezze diverse, ed esistono gli anni bisestili. chrono lo fa per te. Usa checked_add_days con Days, checked_add_months con Months, oppure somma un TimeDelta:
use chrono::{Days, Months, NaiveDate, TimeDelta};
fn main() { let start = NaiveDate::from_ymd_opt(2025, 1, 31).unwrap();
let plus_30 = start.checked_add_days(Days::new(30)).unwrap(); println!("30 giorni dopo: {plus_30}");
let minus_7 = start.checked_sub_days(Days::new(7)).unwrap(); println!("7 giorni prima: {minus_7}");
let next_month = start.checked_add_months(Months::new(1)).unwrap(); println!("1 mese dopo: {next_month}");
let week_later = start + TimeDelta::weeks(1); println!("1 settimana dopo: {week_later}");
let dt = start.and_hms_opt(23, 30, 0).unwrap(); let later = dt + TimeDelta::hours(2); println!("2 ore dopo: {later}");}Output:
30 giorni dopo: 2025-03-027 giorni prima: 2025-01-241 mese dopo: 2025-02-281 settimana dopo: 2025-02-072 ore dopo: 2025-02-01 01:30:00Guarda «1 mese dopo»: il 31 gennaio più un mese fa 28 febbraio, perché il 31 febbraio non esiste. chrono sceglie l’ultimo giorno valido. I metodi checked_ restituiscono un Option, che è None solo se il risultato cade fuori dall’intervallo di anni (molto ampio) gestito da chrono. Aggiungere 2 ore alle 23:30 passa correttamente al giorno dopo.
Giorni della settimana e differenza tra due date
Sezione intitolata “Giorni della settimana e differenza tra due date”.weekday() (da Datelike) ti dice il giorno della settimana. Sottraendo una data da un’altra ottieni un TimeDelta, e .num_days() lo trasforma in un numero:
use chrono::{Datelike, NaiveDate, Weekday};
fn main() { let date = NaiveDate::from_ymd_opt(2025, 3, 15).unwrap(); let day = date.weekday(); println!("{date} è un {day} ({})", date.format("%A"));
let weekend = matches!(day, Weekday::Sat | Weekday::Sun); println!("fine settimana? {weekend}");
let start = NaiveDate::from_ymd_opt(2025, 1, 1).unwrap(); let end = NaiveDate::from_ymd_opt(2025, 12, 25).unwrap(); let gap = end - start; println!("{} giorni, cioè {} settimane intere", gap.num_days(), gap.num_weeks()); println!("al contrario: {} giorni", (start - end).num_days());
let check_in = start.and_hms_opt(14, 0, 0).unwrap(); let check_out = NaiveDate::from_ymd_opt(2025, 1, 4).unwrap().and_hms_opt(11, 0, 0).unwrap(); println!("soggiorno in hotel: {} ore", (check_out - check_in).num_hours());}Output:
2025-03-15 è un Sat (Saturday)fine settimana? true358 giorni, cioè 51 settimane intereal contrario: -358 giornisoggiorno in hotel: 69 oreWeekday è un enum con le varianti da Mon a Sun (abbreviazioni inglesi dei giorni), quindi puoi usarci match. Nota che la differenza può essere negativa se la seconda data è successiva. È utile: un numero negativo di giorni rimasti significa «in ritardo».
Esempio: giorni alla scadenza
Sezione intitolata “Esempio: giorni alla scadenza”Mettiamo tutto insieme. La funzione prende «oggi» come parametro invece di chiamare Local::now() al suo interno. In questo modo l’output qui sotto è sempre lo stesso, e la funzione è facile da testare. In un programma vero le passeresti Local::now().date_naive().
use chrono::NaiveDate;
fn days_left(today: NaiveDate, deadline: NaiveDate) -> String { let days = (deadline - today).num_days(); match days { 0 => String::from("scade oggi"), 1 => String::from("scade domani"), d if d > 1 => format!("mancano {d} giorni"), -1 => String::from("1 giorno di ritardo"), d => format!("{} giorni di ritardo", -d), }}
fn main() { let today = NaiveDate::from_ymd_opt(2025, 6, 10).unwrap();
let tasks = [ ("Pagare l'affitto", "2025-06-10"), ("Inviare il report", "2025-06-11"), ("Rinnovare passaporto", "2025-07-01"), ("Restituire il libro", "2025-06-09"), ("Fattura del dentista", "2025-06-03"), ];
println!("Oggi è {}", today.format("%d/%m/%Y")); for (name, date_text) in tasks { let deadline = NaiveDate::parse_from_str(date_text, "%Y-%m-%d").unwrap(); println!("{:<20} {} {}", name, deadline.format("%d/%m"), days_left(today, deadline)); }}Output:
Oggi è 10/06/2025Pagare l'affitto 10/06 scade oggiInviare il report 11/06 scade domaniRinnovare passaporto 01/07 mancano 21 giorniRestituire il libro 09/06 1 giorno di ritardoFattura del dentista 03/06 7 giorni di ritardoProva a cambiare today con un’altra data ed eseguilo di nuovo. {:<20} allunga il nome fino a 20 caratteri, così le colonne restano allineate (vedi Output). Qui usiamo date numeriche (%d/%m), così non compaiono i nomi inglesi dei mesi.
chrono non è l’unica scelta. Il crate time è un’alternativa più piccola e diffusa, e il più recente jiff dedica un’attenzione particolare ai fusi orari e all’ora legale; qui usiamo chrono perché è il più usato ed è disponibile nel Playground.
Errori comuni
Sezione intitolata “Errori comuni”Dimenticare use chrono::Datelike. Senza, .year(), .month() e .weekday() non funzionano. L’errore dice che il metodo è «private» (privato), il che confonde, ma la riga di aiuto ti indica la vera soluzione:
error[E0624]: method `year` is private --> src/main.rs:5:25 |5 | println!("{}", date.year()); | ^^^^ private method | = help: items from traits can only be used if the trait is in scopehelp: trait `Datelike` which provides `year` is implemented but not in scope; perhaps you want to import it |1 + use chrono::Datelike; |Il suggerimento dice che il trait Datelike, che fornisce year, è implementato ma non è nello scope: basta importarlo.
Confondere %m e %M. %m è il mese, %M è il minuto. Con una data e un’ora, quello sbagliato non dà errore. Stampa i minuti senza dire niente:
use chrono::NaiveDate;
fn main() { let meeting = NaiveDate::from_ymd_opt(2025, 3, 14).unwrap().and_hms_opt(9, 5, 0).unwrap(); println!("{}", meeting.format("%Y-%M-%d")); // sbagliato: minuti println!("{}", meeting.format("%Y-%m-%d")); // giusto: mese}Output:
2025-05-142025-03-14Cronometrare il codice con SystemTime. L’orologio di sistema può saltare (per esempio quando si sincronizza con internet). Usa Instant per misurare, e SystemTime o chrono solo quando ti serve la data vera.
Chiamare Local::now() in profondità nella tua logica. Fa cambiare i risultati ogni giorno e rende difficile scrivere i test. Passa la data come parametro, come fa days_left.
Riepilogo
Sezione intitolata “Riepilogo”std::time::Instantmisura il tempo trascorso;Durationè una quantità di tempo;SystemTimeè l’orologio di sistema (utile per i timestamp Unix).- Per i calendari, aggiungi
chrono. UsaNaiveDate/NaiveDateTimeper date senza fuso orario, eUtc::now()/Local::now()per l’istante attuale. from_ymd_optrestituisce unOption, eparse_from_strrestituisce unResult, perché non ogni input è una data reale..format("%Y-%m-%d")trasforma una data in testo usando i codici con%. Ricorda:%mè il mese,%Mil minuto.- Aggiungi tempo con
checked_add_days,checked_add_monthso+ TimeDelta. Sottrai due date per ottenere unTimeDelta, poi chiama.num_days(). - Passa «oggi» alle tue funzioni invece di leggere l’orologio al loro interno.
Verifica veloce
Sezione intitolata “Verifica veloce”Verifica veloce
1. Quale tipo dovresti usare per misurare quanto tempo impiega un pezzo di codice?
2. Cosa restituisce NaiveDate::from_ymd_opt(2025, 2, 30)?
3. In una stringa di formato di chrono, cosa significa %M?
4. Sottrai due NaiveDate: deadline - today. Come ottieni il numero di giorni?
5. Quanto fa in chrono il 31 gennaio 2025 più Months::new(1)?
Punteggio: 0 / 5
Per approfondire
Sezione intitolata “Per approfondire”- std::time — libreria standard di Rust (in inglese):
Instant,DurationeSystemTimecon altri esempi. - Documentazione di chrono — docs.rs (in inglese): la guida completa a ogni tipo e metodo di chrono.
- Codici di formato di chrono — docs.rs (in inglese): l’elenco completo dei codici
%performateparse_from_str. - Date and Time — Rust Cookbook (in inglese): ricette brevi per i compiti più comuni con date e orari.
- Documentazione di jiff — docs.rs (in inglese): un crate per date e orari più recente, con un ottimo supporto ai fusi orari, se vuoi fare un confronto.