Datas e horas em Rust com chrono
Programas lidam com tempo o tempo todo. “Quanto tempo isso levou?” “Em que dia vence o boleto?” “Quantos dias faltam para a viagem?” O Rust divide essas perguntas em dois tipos.
O módulo std::time da biblioteca padrão é um cronômetro: ele mede quanto tempo passa. Ele não sabe nada de calendários, meses ou dias da semana. Para trabalhar com calendário, quase todo projeto Rust usa o crate (pacote) chrono. Esta página começa pelo cronômetro e depois passa para o calendário.
Medindo o tempo decorrido com Instant
Seção intitulada “Medindo o tempo decorrido com Instant”Instant::now() faz uma leitura do relógio do computador. Depois, .elapsed() diz quanto tempo passou desde essa leitura, como um 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!("total = {total}"); println!("levou {:?}", elapsed); println!("levou {} ms", elapsed.as_millis()); println!("menos de 5 segundos? {}", elapsed < Duration::from_secs(5));}Não mostramos a saída aqui de propósito: o tempo é diferente em cada computador e em cada execução. Você vai ver algo como levou 6.862793ms. Clique no botão duas vezes e compare.
Um Instant só serve para medir. Você não consegue imprimi-lo como data, e ele só anda para a frente. É exatamente o que você quer para cronometrar código, porque ele nunca é afetado por alguém mudando o relógio do sistema.
Durações
Seção intitulada “Durações”Um Duration é uma quantidade de tempo, como “90 segundos”. Você cria um a partir de segundos ou milissegundos, soma durações e compara umas com as outras:
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!("total: {:?}", total); println!("{} min {} s", long.as_secs() / 60, long.as_secs() % 60); println!("{}", short < long);}Saída:
1.5s90s1500 ms1.5 stotal: 91.5s1 min 30 strueRepare que o Rust usa ponto como separador decimal (1.5), não vírgula. Você vai encontrar Duration em muitos lugares: std::thread::sleep(Duration::from_millis(100)) pausa uma thread, e os timeouts das bibliotecas de rede também recebem um Duration.
A hora do relógio com SystemTime
Seção intitulada “A hora do relógio com SystemTime”SystemTime é a hora do relógio do computador, aquela que aparece no canto da sua tela. O uso mais comum é perguntar quanto tempo passou desde a época Unix (Unix epoch), 1º de janeiro de 1970 (UTC). Esse número se chama timestamp Unix:
use std::time::{SystemTime, UNIX_EPOCH};
fn main() { let now = SystemTime::now(); let since_1970 = now .duration_since(UNIX_EPOCH) .expect("o relógio está antes de 1970"); println!("Segundos desde 1º de janeiro de 1970: {}", since_1970.as_secs());}O número depende de quando você roda o programa, então de novo não há saída aqui. Por que duration_since retorna um Result? Porque o relógio do sistema pode estar errado ou pode ser atrasado. É também por isso que você deve usar Instant, e não SystemTime, para cronometrar o seu código.
E é praticamente só isso que o std::time faz. Ele não tem ideia do que significa “março” ou “sexta-feira”. Para isso, você precisa do chrono.
Adicionando o chrono
Seção intitulada “Adicionando o chrono”Adicione o crate ao seu projeto (veja Cargo e crates se isso for novidade):
cargo add chrono[dependencies]chrono = "0.4"O Rust Playground também tem o chrono, então todos os botões abaixo funcionam. O chrono tem duas famílias de tipos:
- Os tipos naive (“ingênuos”:
NaiveDate,NaiveTime,NaiveDateTime) são datas e horas sem fuso horário. “14 de março de 2025” ou “09:30”, como escritos num calendário. A maior parte do código do dia a dia usa esses. DateTime<Tz>é um momento no tempo com fuso horário, comoDateTime<Utc>ouDateTime<Local>.
Criando datas a partir de valores fixos
Seção intitulada “Criando datas a partir de valores fixos”NaiveDate::from_ymd_opt(ano, mês, dia) monta uma data. Ele retorna um Option, porque nem toda combinação é uma data de verdade:
use chrono::{Datelike, NaiveDate, NaiveTime};
fn main() { let date = NaiveDate::from_ymd_opt(2025, 3, 14).unwrap(); println!("{date}"); println!("ano {}, mês {}, dia {}", 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);}Saída:
2025-03-14ano 2025, mês 3, dia 142025-03-14 09:30:002025-03-14 17:45:10NoneO dia 30 de fevereiro não existe, então você recebe None em vez de uma data quebrada. Com valores fixos que você mesmo digitou, .unwrap() não tem problema. Com valores vindos de um usuário, trate o None.
A linha use chrono::Datelike traz os métodos .year(), .month() e .day(). (Timelike faz o mesmo para .hour() e .minute().)
A data e a hora atuais
Seção intitulada “A data e a hora atuais”Utc::now() dá o momento atual em UTC. Local::now() dá o momento no fuso horário do próprio computador:
use chrono::{Local, Utc};
fn main() { let utc_now = Utc::now(); let local_now = Local::now();
println!("UTC: {}", utc_now); println!("Local: {}", local_now); println!("Hoje: {}", local_now.format("%Y-%m-%d"));
let today = local_now.date_naive(); println!("Como NaiveDate: {today}");}A saída é a data de hoje, então ela muda todo dia. Os servidores do Playground usam UTC, então as duas primeiras linhas mostram a mesma hora lá. .date_naive() descarta o fuso horário e a hora, deixando um NaiveDate simples. Um bom hábito: pegue o “agora” uma única vez, na borda do seu programa, e depois passe a data para as suas funções. Assim elas ficam fáceis de testar, como você vai ver no exemplo do prazo.
Formatando datas
Seção intitulada “Formatando datas”.format() recebe um padrão. Cada código com % é trocado por uma parte da data. %Y é o ano, %m o número do mês, %d o dia:
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("relatorio-%Y%m%d.txt").to_string(); println!("{file_name}");}Saída:
2025-03-1414/03/2025Friday, March 14, 2025Fri 14 Mar09:059:05 AMrelatorio-20250314.txtOs códigos que você mais vai usar: %Y ano, %m mês (01–12), %d dia (01–31), %H hora (00–23), %M minuto, %S segundo, %A nome do dia da semana, %B nome do mês. Um - depois do % tira o zero à esquerda, então %-d imprime 5 em vez de 05. .format() retorna algo que você pode imprimir. Chame .to_string() quando precisar de uma String.
Repare que os nomes de dias e meses (Friday, March) saem em inglês: é o padrão do chrono. Para datas em português, o formato numérico %d/%m/%Y, que é o que usamos no Brasil, resolve a maioria dos casos.
Convertendo texto em datas
Seção intitulada “Convertendo texto em datas”NaiveDate::parse_from_str faz o caminho inverso: ele lê um texto usando o mesmo padrão com %. Ele retorna um Result, porque o texto pode não bater com o padrão:
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", "14 de março", "2025-03"] { match NaiveDate::parse_from_str(text, "%Y-%m-%d") { Ok(date) => println!("Data lida: {date}"), Err(e) => println!("Não foi possível ler {text:?}: {e}"), } }}Saída:
Ok(2025-03-14)Ok(2025-03-14)Ok(2025-03-14)Ok(2025-03-14T09:30:00)Não foi possível ler "2025-13-01": input is out of rangeNão foi possível ler "14 de março": input contains invalid charactersNão foi possível ler "2025-03": premature end of inputPara o formato padrão AAAA-MM-DD você também pode simplesmente chamar .parse(). O mês 13 não existe, então o primeiro exemplo ruim dá “out of range” (fora do intervalo). As mensagens de erro do chrono vêm em inglês: “invalid characters” quer dizer que o texto tem caracteres que não cabem no padrão, e “premature end of input” quer dizer que o texto acabou antes do padrão terminar. Elas são curtas, mas claras o bastante para mostrar a um usuário (ou para você trocar por uma mensagem sua).
Cálculos com datas
Seção intitulada “Cálculos com datas”Somar dias a uma data na mão é surpreendentemente difícil: os meses têm tamanhos diferentes, e existem anos bissextos. O chrono faz isso para você. Use checked_add_days com Days, checked_add_months com Months, ou some um 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 dias depois: {plus_30}");
let minus_7 = start.checked_sub_days(Days::new(7)).unwrap(); println!("7 dias antes: {minus_7}");
let next_month = start.checked_add_months(Months::new(1)).unwrap(); println!("1 mês depois: {next_month}");
let week_later = start + TimeDelta::weeks(1); println!("1 semana depois: {week_later}");
let dt = start.and_hms_opt(23, 30, 0).unwrap(); let later = dt + TimeDelta::hours(2); println!("2 horas depois: {later}");}Saída:
30 dias depois: 2025-03-027 dias antes: 2025-01-241 mês depois: 2025-02-281 semana depois: 2025-02-072 horas depois: 2025-02-01 01:30:00Olhe o “1 mês depois”: 31 de janeiro mais um mês dá 28 de fevereiro, porque 31 de fevereiro não existe. O chrono escolhe o último dia válido. Os métodos checked_ retornam um Option, que só é None se o resultado cair fora do intervalo de anos do chrono (que é enorme). Somar 2 horas a 23:30 passa corretamente para o dia seguinte.
Dias da semana e a diferença entre duas datas
Seção intitulada “Dias da semana e a diferença entre duas datas”.weekday() (de Datelike) diz o dia da semana. Subtrair uma data de outra dá um TimeDelta, e .num_days() transforma isso em um número:
use chrono::{Datelike, NaiveDate, Weekday};
fn main() { let date = NaiveDate::from_ymd_opt(2025, 3, 15).unwrap(); let day = date.weekday(); println!("{date} cai num {day} ({})", date.format("%A"));
let weekend = matches!(day, Weekday::Sat | Weekday::Sun); println!("fim de semana? {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!("{} dias, ou {} semanas completas", gap.num_days(), gap.num_weeks()); println!("ao contrário: {} dias", (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!("estadia no hotel: {} horas", (check_out - check_in).num_hours());}Saída:
2025-03-15 cai num Sat (Saturday)fim de semana? true358 dias, ou 51 semanas completasao contrário: -358 diasestadia no hotel: 69 horasWeekday é um enum com as variantes de Mon (segunda) a Sun (domingo), então você pode usar match nele. Repare que a diferença pode ser negativa se a segunda data for a mais tarde. Isso é útil: um número negativo de dias restantes quer dizer “atrasado”.
Exemplo: dias até um prazo
Seção intitulada “Exemplo: dias até um prazo”Vamos juntar tudo. A função recebe “hoje” como parâmetro, em vez de chamar Local::now() lá dentro. Assim a saída abaixo é sempre a mesma, e a função fica fácil de testar. Num programa de verdade, você passaria 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("vence hoje"), 1 => String::from("vence amanhã"), d if d > 1 => format!("faltam {d} dias"), -1 => String::from("1 dia de atraso"), d => format!("{} dias de atraso", -d), }}
fn main() { let today = NaiveDate::from_ymd_opt(2025, 6, 10).unwrap();
let tasks = [ ("Pay rent", "2025-06-10"), ("Send report", "2025-06-11"), ("Renew passport", "2025-07-01"), ("Return library book", "2025-06-09"), ("Dentist bill", "2025-06-03"), ];
println!("Hoje é {}", 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)); }}Saída:
Hoje é 10/06/2025Pay rent 10/06 vence hojeSend report 11/06 vence amanhãRenew passport 01/07 faltam 21 diasReturn library book 09/06 1 dia de atrasoDentist bill 03/06 7 dias de atrasoTente mudar today para outra data e rode de novo. O {:<20} completa o nome com espaços até 20 caracteres para as colunas ficarem alinhadas (veja Saída). Os nomes das tarefas ficaram em inglês de propósito: a largura do {:<20} é contada em chars, e alguns caracteres (como emojis, ou acentos escritos como dois chars) não ocupam exatamente uma coluna na tela. Em vez dos nomes de mês em inglês (%d %b), usamos o formato brasileiro %d/%m.
O chrono não é a única opção. O crate time é uma alternativa menor e popular, e o crate jiff, mais novo, dá atenção extra a fusos horários e ao horário de verão. Usamos o chrono aqui porque ele é o mais usado e está disponível no Playground.
Erros comuns
Seção intitulada “Erros comuns”Esquecer o use chrono::Datelike. Sem ele, .year(), .month() e .weekday() não funcionam. O erro diz que o método é “private” (privado), o que confunde, mas a linha de help mostra a correção de verdade: importar o trait Datelike.
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; |Confundir %m com %M. %m é o mês, %M é o minuto. Com uma data e hora, o código errado não dá erro. Ele simplesmente imprime os minutos, sem avisar:
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")); // errado: minutos println!("{}", meeting.format("%Y-%m-%d")); // certo: mês}Saída:
2025-05-142025-03-14Cronometrar código com SystemTime. O relógio do sistema pode dar saltos (por exemplo, quando ele se sincroniza com a internet). Use Instant para medir, e SystemTime ou o chrono só quando você precisar da data de verdade.
Chamar Local::now() lá no meio da sua lógica. Isso faz os resultados mudarem todo dia e deixa os testes difíceis de escrever. Passe a data como parâmetro, como a days_left faz.
std::time::Instantmede o tempo decorrido;Durationé uma quantidade de tempo;SystemTimeé o relógio do sistema (bom para timestamps Unix).- Para calendários, adicione o
chrono. UseNaiveDate/NaiveDateTimepara datas sem fuso horário, eUtc::now()/Local::now()para o momento atual. from_ymd_optretorna umOption, eparse_from_strretorna umResult, porque nem toda entrada é uma data de verdade..format("%Y-%m-%d")transforma uma data em texto usando códigos com%. Lembre:%mé mês,%Mé minuto.- Some tempo com
checked_add_days,checked_add_monthsou+ TimeDelta. Subtraia duas datas para obter umTimeDeltae depois chame.num_days(). - Passe “hoje” para as suas funções em vez de ler o relógio dentro delas.
Teste o que aprendeu
Seção intitulada “Teste o que aprendeu”Teste o que aprendeu
1. Qual tipo você deve usar para medir quanto tempo um trecho de código leva?
2. O que NaiveDate::from_ymd_opt(2025, 2, 30) retorna?
3. Numa string de formato do chrono, o que %M significa?
4. Você subtrai dois NaiveDates: deadline - today. Como você obtém o número de dias?
5. Quanto é 31 de janeiro de 2025 mais Months::new(1) no chrono?
Pontuação: 0 / 5
Para saber mais
Seção intitulada “Para saber mais”- std::time — biblioteca padrão do Rust (em inglês):
Instant,DurationeSystemTime, com mais exemplos. - Documentação do chrono — docs.rs (em inglês): o guia completo de todos os tipos e métodos do chrono.
- Códigos de formato do chrono — docs.rs (em inglês): a lista completa dos códigos com
%paraformateparse_from_str. - Date and Time — Rust Cookbook (em inglês): receitas curtas para tarefas comuns com datas e horas.
- Documentação do jiff — docs.rs (em inglês): um crate de datas e horas mais novo, com ótimo suporte a fusos horários, se você quiser comparar.