Chrono: Manipulação de Datas e Horários em Rust

Guia completo da crate chrono para Rust. Aprenda NaiveDate, DateTime, fusos horários, formatação com strftime, aritmética de datas, Duration, serde e exemplos práticos de agendamento.

A crate chrono é a biblioteca padrão da comunidade Rust para manipulação de datas e horários. Ela fornece tipos ricos e seguros para representar instantes no tempo, datas sem horário, horários sem data, fusos horários e durações. Se você precisa trabalhar com datas em Rust, chrono é quase certamente a crate que você vai usar.

Diferente de muitas linguagens onde datas são uma fonte constante de bugs (fusos horários perdidos, parsing ambíguo, aritmética incorreta), o sistema de tipos de Rust combinado com os tipos de chrono torna erros comuns impossíveis de compilar. Um NaiveDate nunca será confundido com um DateTime<Utc>, e o compilador garante que operações entre fusos horários sejam explícitas.

Instalação

Adicione ao seu Cargo.toml:

[dependencies]
chrono = "0.4"

Para integração com serde (serialização/deserialização):

[dependencies]
chrono = { version = "0.4", features = ["serde"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"

Uso Básico

Tipos Fundamentais

O chrono oferece duas famílias de tipos:

Tipos “Naive” (sem fuso horário):

  • NaiveDate — apenas data (2024-03-15)
  • NaiveTime — apenas horário (14:30:00)
  • NaiveDateTime — data + horário, sem fuso

Tipos com fuso horário (timezone-aware):

  • DateTime<Utc> — data/hora em UTC
  • DateTime<Local> — data/hora no fuso local do sistema
  • DateTime<FixedOffset> — data/hora com offset fixo
use chrono::{Local, NaiveDate, NaiveDateTime, NaiveTime, Utc};

fn main() {
    // Data e hora atual
    let agora_utc = Utc::now();
    let agora_local = Local::now();
    println!("UTC:   {}", agora_utc);
    println!("Local: {}", agora_local);

    // Criando datas naive
    let data = NaiveDate::from_ymd_opt(2024, 3, 15).unwrap();
    let hora = NaiveTime::from_hms_opt(14, 30, 0).unwrap();
    let data_hora = NaiveDateTime::new(data, hora);

    println!("Data: {}", data);           // 2024-03-15
    println!("Hora: {}", hora);           // 14:30:00
    println!("DataHora: {}", data_hora);  // 2024-03-15 14:30:00
}

Criando Datas de Diferentes Formas

use chrono::NaiveDate;

fn main() {
    // Ano, mês, dia
    let d1 = NaiveDate::from_ymd_opt(2024, 12, 25).unwrap();

    // Ano e dia do ano (ordinal)
    let d2 = NaiveDate::from_yo_opt(2024, 1).unwrap(); // 1 de janeiro
    let d3 = NaiveDate::from_yo_opt(2024, 366).unwrap(); // 31 de dezembro (2024 é bissexto)

    // Ano, semana ISO, dia da semana
    use chrono::Weekday;
    let d4 = NaiveDate::from_isoywd_opt(2024, 1, Weekday::Mon).unwrap();

    println!("Natal: {}", d1);
    println!("Primeiro dia: {}", d2);
    println!("Último dia: {}", d3);
    println!("Primeira segunda ISO: {}", d4);

    // Métodos seguros retornam Option
    let invalido = NaiveDate::from_ymd_opt(2024, 2, 30);
    assert!(invalido.is_none()); // 30 de fevereiro não existe!
}

Acessando Componentes

use chrono::{Datelike, Timelike, Utc, Weekday};

fn main() {
    let agora = Utc::now();

    // Componentes de data (trait Datelike)
    println!("Ano: {}", agora.year());
    println!("Mês: {}", agora.month());     // 1-12
    println!("Dia: {}", agora.day());       // 1-31
    println!("Dia do ano: {}", agora.ordinal()); // 1-366
    println!("Dia da semana: {}", agora.weekday()); // Mon, Tue, etc.

    // Componentes de hora (trait Timelike)
    println!("Hora: {}", agora.hour());     // 0-23
    println!("Minuto: {}", agora.minute()); // 0-59
    println!("Segundo: {}", agora.second()); // 0-59
    println!("Nanosegundos: {}", agora.nanosecond());

    // Verificações úteis
    let data = agora.date_naive();
    println!("É fim de semana? {}", matches!(data.weekday(), Weekday::Sat | Weekday::Sun));
}

Recursos Avançados

Formatação com strftime

use chrono::{Local, Utc};

fn main() {
    let agora = Local::now();

    // Formatos comuns
    println!("ISO 8601:    {}", agora.format("%Y-%m-%dT%H:%M:%S%z"));
    println!("Brasileiro:  {}", agora.format("%d/%m/%Y %H:%M:%S"));
    println!("Apenas data: {}", agora.format("%d/%m/%Y"));
    println!("Apenas hora: {}", agora.format("%H:%M:%S"));
    println!("Extenso:     {}", agora.format("%A, %d de %B de %Y"));
    println!("Compacto:    {}", agora.format("%Y%m%d_%H%M%S"));
    println!("Log:         {}", agora.format("[%Y-%m-%d %H:%M:%S%.3f]"));

    // RFC 2822 (email)
    println!("RFC 2822:    {}", agora.to_rfc2822());

    // RFC 3339 / ISO 8601
    println!("RFC 3339:    {}", agora.to_rfc3339());
}

Especificadores mais usados:

EspecificadorDescriçãoExemplo
%YAno com 4 dígitos2024
%mMês (01-12)03
%dDia (01-31)15
%HHora 24h (00-23)14
%MMinuto (00-59)30
%SSegundo (00-59)45
%fNanosegundos123456789
%.3fMilissegundos.123
%ADia da semana por extensoFriday
%aDia da semana abreviadoFri
%BMês por extensoMarch
%bMês abreviadoMar
%zOffset do fuso (+/-HHMM)-0300
%ZNome do fusoBRT

Parsing de Strings

use chrono::{DateTime, NaiveDate, NaiveDateTime, Utc};

fn main() {
    // Parsing de data
    let data = NaiveDate::parse_from_str("15/03/2024", "%d/%m/%Y").unwrap();
    println!("Data: {}", data);

    // Parsing de data+hora
    let dt = NaiveDateTime::parse_from_str(
        "2024-03-15 14:30:00",
        "%Y-%m-%d %H:%M:%S"
    ).unwrap();
    println!("DateTime: {}", dt);

    // Parsing de DateTime com fuso
    let dt_utc = DateTime::parse_from_rfc3339("2024-03-15T14:30:00+00:00").unwrap();
    println!("UTC: {}", dt_utc);

    // Parsing de formato RFC 2822
    let dt_rfc = DateTime::parse_from_rfc2822("Fri, 15 Mar 2024 14:30:00 -0300").unwrap();
    println!("RFC 2822: {}", dt_rfc);

    // Parsing com formato customizado incluindo fuso
    let dt_custom = DateTime::parse_from_str(
        "15/03/2024 14:30:00 -0300",
        "%d/%m/%Y %H:%M:%S %z"
    ).unwrap();
    println!("Custom: {}", dt_custom);

    // Tratamento seguro de parsing
    match NaiveDate::parse_from_str("31/02/2024", "%d/%m/%Y") {
        Ok(d) => println!("Data: {}", d),
        Err(e) => println!("Erro de parsing: {}", e),
    }
}

Fusos Horários

use chrono::{DateTime, FixedOffset, Local, TimeZone, Utc};

fn main() {
    let agora_utc = Utc::now();

    // Converter para fuso fixo (Brasília: UTC-3)
    let brasilia = FixedOffset::west_opt(3 * 3600).unwrap();
    let agora_brt = agora_utc.with_timezone(&brasilia);
    println!("Brasília: {}", agora_brt.format("%d/%m/%Y %H:%M:%S %z"));

    // Converter para fuso local
    let agora_local = agora_utc.with_timezone(&Local);
    println!("Local:    {}", agora_local.format("%d/%m/%Y %H:%M:%S"));

    // Criar DateTime em fuso específico
    let tokyo = FixedOffset::east_opt(9 * 3600).unwrap();
    let reuniao_tokyo = tokyo
        .with_ymd_and_hms(2024, 4, 1, 10, 0, 0)
        .unwrap();
    println!("Reunião Tokyo: {}", reuniao_tokyo);
    println!("Reunião UTC:   {}", reuniao_tokyo.with_timezone(&Utc));
    println!("Reunião BRT:   {}", reuniao_tokyo.with_timezone(&brasilia));

    // Comparação entre fusos
    let hora_ny = FixedOffset::west_opt(5 * 3600).unwrap()
        .with_ymd_and_hms(2024, 3, 15, 12, 0, 0)
        .unwrap();
    let hora_sp = brasilia
        .with_ymd_and_hms(2024, 3, 15, 14, 0, 0)
        .unwrap();

    // Mesmo instante? (NY 12:00 -5 == SP 14:00 -3)
    println!("Mesmo instante? {}", hora_ny == hora_sp);
}

Duration e Aritmética de Datas

use chrono::{Days, Duration, Months, NaiveDate, Utc};

fn main() {
    let hoje = Utc::now().date_naive();

    // Adicionar/subtrair dias com Duration
    let amanha = hoje + Duration::days(1);
    let ontem = hoje - Duration::days(1);
    let proxima_semana = hoje + Duration::weeks(1);

    println!("Ontem:          {}", ontem);
    println!("Hoje:           {}", hoje);
    println!("Amanhã:         {}", amanha);
    println!("Próxima semana: {}", proxima_semana);

    // Adicionar meses (usando checked_add_months)
    let proximo_mes = hoje.checked_add_months(Months::new(1)).unwrap();
    let daqui_6_meses = hoje.checked_add_months(Months::new(6)).unwrap();
    println!("Próximo mês:    {}", proximo_mes);
    println!("Daqui 6 meses:  {}", daqui_6_meses);

    // Adicionar dias com checked (seguro)
    let futuro = hoje.checked_add_days(Days::new(365)).unwrap();
    println!("Daqui 365 dias: {}", futuro);

    // Diferença entre datas
    let inicio = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
    let fim = NaiveDate::from_ymd_opt(2024, 12, 31).unwrap();
    let diferenca = fim - inicio;
    println!("Dias em 2024: {}", diferenca.num_days()); // 365

    // Duration com DateTime
    let agora = Utc::now();
    let daqui_2_horas = agora + Duration::hours(2);
    let faz_30_min = agora - Duration::minutes(30);
    println!("Daqui 2h:  {}", daqui_2_horas.format("%H:%M"));
    println!("30min atrás: {}", faz_30_min.format("%H:%M"));

    // Aritmética com duração precisa
    let duracao = Duration::hours(2) + Duration::minutes(30) + Duration::seconds(15);
    println!("Duração total: {} segundos", duracao.num_seconds());
}

Integração com Serde

use chrono::{DateTime, NaiveDate, Utc};
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct Evento {
    nome: String,

    // Formato ISO 8601 padrão
    #[serde(with = "chrono::serde::ts_seconds")]
    criado_em: DateTime<Utc>,

    // DateTime com formato padrão
    inicio: DateTime<Utc>,

    // Data naive
    data_limite: NaiveDate,
}

// Formato customizado para serde
mod formato_brasileiro {
    use chrono::NaiveDate;
    use serde::{self, Deserialize, Deserializer, Serializer};

    const FORMATO: &str = "%d/%m/%Y";

    pub fn serialize<S>(date: &NaiveDate, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let s = date.format(FORMATO).to_string();
        serializer.serialize_str(&s)
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<NaiveDate, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        NaiveDate::parse_from_str(&s, FORMATO).map_err(serde::de::Error::custom)
    }
}

#[derive(Debug, Serialize, Deserialize)]
struct Pessoa {
    nome: String,
    #[serde(with = "formato_brasileiro")]
    nascimento: NaiveDate,
}

fn main() {
    // Serializar
    let pessoa = Pessoa {
        nome: "Maria Silva".to_string(),
        nascimento: NaiveDate::from_ymd_opt(1990, 5, 20).unwrap(),
    };

    let json = serde_json::to_string_pretty(&pessoa).unwrap();
    println!("JSON:\n{}", json);
    // {"nome": "Maria Silva", "nascimento": "20/05/1990"}

    // Deserializar
    let json_input = r#"{"nome": "João Santos", "nascimento": "15/03/1985"}"#;
    let pessoa: Pessoa = serde_json::from_str(json_input).unwrap();
    println!("\nPessoa: {:?}", pessoa);
}

Iterando sobre Intervalos de Datas

use chrono::{Days, Duration, NaiveDate, Weekday, Datelike};

fn dias_uteis_no_periodo(inicio: NaiveDate, fim: NaiveDate) -> Vec<NaiveDate> {
    let mut dias = Vec::new();
    let mut atual = inicio;

    while atual <= fim {
        match atual.weekday() {
            Weekday::Sat | Weekday::Sun => {} // Pula fins de semana
            _ => dias.push(atual),
        }
        atual = atual.checked_add_days(Days::new(1)).unwrap();
    }

    dias
}

fn proximo_dia_util(data: NaiveDate) -> NaiveDate {
    let mut proxima = data + Duration::days(1);
    while matches!(proxima.weekday(), Weekday::Sat | Weekday::Sun) {
        proxima += Duration::days(1);
    }
    proxima
}

fn main() {
    let inicio = NaiveDate::from_ymd_opt(2024, 3, 1).unwrap();
    let fim = NaiveDate::from_ymd_opt(2024, 3, 31).unwrap();

    let dias_uteis = dias_uteis_no_periodo(inicio, fim);
    println!("Dias úteis em março/2024: {}", dias_uteis.len());
    for dia in &dias_uteis[..5] {
        println!("  {} ({})", dia, dia.weekday());
    }
    println!("  ...");

    let sexta = NaiveDate::from_ymd_opt(2024, 3, 15).unwrap();
    let proximo = proximo_dia_util(sexta);
    println!("\nPróximo dia útil após {}: {} ({})", sexta, proximo, proximo.weekday());
}

Boas Práticas

1. Use UTC Internamente, Local Apenas para Exibição

use chrono::{DateTime, Local, Utc};

struct Registro {
    // BOM: armazene sempre em UTC
    criado_em: DateTime<Utc>,
    atualizado_em: DateTime<Utc>,
}

impl Registro {
    fn novo() -> Self {
        let agora = Utc::now();
        Registro {
            criado_em: agora,
            atualizado_em: agora,
        }
    }

    // Converta para local apenas na exibição
    fn exibir(&self) {
        let local = self.criado_em.with_timezone(&Local);
        println!("Criado em: {}", local.format("%d/%m/%Y %H:%M"));
    }
}

2. Trate Erros de Parsing Graciosamente

use chrono::NaiveDate;

fn parsear_data_flexivel(input: &str) -> Option<NaiveDate> {
    let formatos = [
        "%d/%m/%Y",     // 15/03/2024
        "%Y-%m-%d",     // 2024-03-15
        "%d-%m-%Y",     // 15-03-2024
        "%d.%m.%Y",     // 15.03.2024
        "%d %b %Y",     // 15 Mar 2024
    ];

    for formato in &formatos {
        if let Ok(data) = NaiveDate::parse_from_str(input, formato) {
            return Some(data);
        }
    }

    None
}

fn main() {
    let entradas = ["15/03/2024", "2024-03-15", "15-03-2024", "abc"];
    for entrada in &entradas {
        match parsear_data_flexivel(entrada) {
            Some(data) => println!("'{}' -> {}", entrada, data),
            None => println!("'{}' -> formato não reconhecido", entrada),
        }
    }
}

3. Cuidado com Ano Bissexto e Meses com Dias Diferentes

use chrono::{Months, NaiveDate};

fn main() {
    // 31 de janeiro + 1 mês = 29 de fevereiro (2024 é bissexto)
    let jan31 = NaiveDate::from_ymd_opt(2024, 1, 31).unwrap();
    let fev = jan31.checked_add_months(Months::new(1));
    println!("Jan 31 + 1 mês = {:?}", fev);
    // Some(2024-02-29)

    // 29 de fevereiro + 1 ano = None (2025 não é bissexto)
    let fev29 = NaiveDate::from_ymd_opt(2024, 2, 29).unwrap();
    // Use checked_ para lidar com isso
    let proximo_ano = fev29.with_year(2025);
    println!("29 fev 2024 em 2025 = {:?}", proximo_ano);
    // None — não existe 29/02/2025!
}

4. Use Duration Corretamente

use chrono::Duration;

fn main() {
    // BOM: duração precisa
    let duracao = Duration::hours(1) + Duration::minutes(30);
    println!("Duração: {} minutos", duracao.num_minutes());

    // CUIDADO: Duration::days(1) nem sempre é 24h (horário de verão)
    // Para aritmética de calendário, use checked_add_months/checked_add_days

    // Para medir performance, use std::time::Instant em vez de chrono
    let inicio = std::time::Instant::now();
    // ... trabalho ...
    let decorrido = inicio.elapsed();
    println!("Tempo: {:?}", decorrido);
}

Exemplos Práticos

Exemplo Completo: Sistema de Agendamento

use chrono::{DateTime, Datelike, Days, Duration, Local, NaiveDate, NaiveTime, Utc, Weekday};
use std::collections::BTreeMap;

#[derive(Debug, Clone)]
struct Compromisso {
    titulo: String,
    inicio: DateTime<Utc>,
    duracao: Duration,
    recorrente: Option<Recorrencia>,
}

#[derive(Debug, Clone)]
enum Recorrencia {
    Diaria,
    Semanal,
    Mensal,
}

impl Compromisso {
    fn fim(&self) -> DateTime<Utc> {
        self.inicio + self.duracao
    }

    fn conflita_com(&self, outro: &Compromisso) -> bool {
        self.inicio < outro.fim() && outro.inicio < self.fim()
    }

    fn exibir(&self) {
        let inicio_local = self.inicio.with_timezone(&Local);
        let fim_local = self.fim().with_timezone(&Local);
        println!(
            "  {} | {} - {} ({} min)",
            self.titulo,
            inicio_local.format("%H:%M"),
            fim_local.format("%H:%M"),
            self.duracao.num_minutes()
        );
    }
}

struct Agenda {
    compromissos: Vec<Compromisso>,
}

impl Agenda {
    fn nova() -> Self {
        Agenda {
            compromissos: Vec::new(),
        }
    }

    fn adicionar(&mut self, compromisso: Compromisso) -> Result<(), String> {
        // Verificar conflitos
        for existente in &self.compromissos {
            if compromisso.conflita_com(existente) {
                return Err(format!(
                    "Conflito: '{}' ({}) conflita com '{}' ({})",
                    compromisso.titulo,
                    compromisso.inicio.format("%H:%M"),
                    existente.titulo,
                    existente.inicio.format("%H:%M")
                ));
            }
        }
        self.compromissos.push(compromisso);
        Ok(())
    }

    fn compromissos_do_dia(&self, data: NaiveDate) -> Vec<&Compromisso> {
        let mut resultado: Vec<&Compromisso> = self
            .compromissos
            .iter()
            .filter(|c| c.inicio.date_naive() == data)
            .collect();

        resultado.sort_by_key(|c| c.inicio);
        resultado
    }

    fn proximos_compromissos(&self, quantidade: usize) -> Vec<&Compromisso> {
        let agora = Utc::now();
        let mut futuros: Vec<&Compromisso> = self
            .compromissos
            .iter()
            .filter(|c| c.inicio > agora)
            .collect();

        futuros.sort_by_key(|c| c.inicio);
        futuros.into_iter().take(quantidade).collect()
    }

    fn resumo_semanal(&self, inicio_semana: NaiveDate) -> BTreeMap<NaiveDate, Vec<&Compromisso>> {
        let mut resumo = BTreeMap::new();

        for i in 0..7 {
            let dia = inicio_semana.checked_add_days(Days::new(i)).unwrap();
            let compromissos = self.compromissos_do_dia(dia);
            if !compromissos.is_empty() {
                resumo.insert(dia, compromissos);
            }
        }

        resumo
    }
}

fn calcular_idade(nascimento: NaiveDate) -> (u32, u32, u32) {
    let hoje = Local::now().date_naive();
    let mut anos = (hoje.year() - nascimento.year()) as u32;
    let mut meses = hoje.month() as i32 - nascimento.month() as i32;
    let mut dias = hoje.day() as i32 - nascimento.day() as i32;

    if dias < 0 {
        meses -= 1;
        // Dias do mês anterior
        let mes_anterior = if hoje.month() == 1 {
            NaiveDate::from_ymd_opt(hoje.year() - 1, 12, 1).unwrap()
        } else {
            NaiveDate::from_ymd_opt(hoje.year(), hoje.month() - 1, 1).unwrap()
        };
        let dias_no_mes = (NaiveDate::from_ymd_opt(
            mes_anterior.year(),
            mes_anterior.month() + 1,
            1,
        )
        .unwrap_or(NaiveDate::from_ymd_opt(mes_anterior.year() + 1, 1, 1).unwrap())
            - mes_anterior)
            .num_days();
        dias += dias_no_mes as i32;
    }

    if meses < 0 {
        anos -= 1;
        meses += 12;
    }

    (anos, meses as u32, dias as u32)
}

fn tempo_ate(data_alvo: DateTime<Utc>) -> String {
    let agora = Utc::now();
    let diff = data_alvo - agora;

    if diff.num_seconds() < 0 {
        return "Já passou!".to_string();
    }

    let dias = diff.num_days();
    let horas = diff.num_hours() % 24;
    let minutos = diff.num_minutes() % 60;

    if dias > 0 {
        format!("{} dias, {} horas e {} minutos", dias, horas, minutos)
    } else if horas > 0 {
        format!("{} horas e {} minutos", horas, minutos)
    } else {
        format!("{} minutos", minutos)
    }
}

fn main() {
    println!("=== Sistema de Agendamento ===\n");

    let mut agenda = Agenda::nova();

    // Criar compromissos para hoje
    let hoje = Utc::now().date_naive();
    let amanha = hoje.checked_add_days(Days::new(1)).unwrap();

    let compromissos = vec![
        Compromisso {
            titulo: "Standup diário".to_string(),
            inicio: hoje.and_hms_opt(12, 0, 0).unwrap().and_utc(),
            duracao: Duration::minutes(15),
            recorrente: Some(Recorrencia::Diaria),
        },
        Compromisso {
            titulo: "Review de código".to_string(),
            inicio: hoje.and_hms_opt(14, 0, 0).unwrap().and_utc(),
            duracao: Duration::hours(1),
            recorrente: None,
        },
        Compromisso {
            titulo: "Planning sprint".to_string(),
            inicio: hoje.and_hms_opt(16, 0, 0).unwrap().and_utc(),
            duracao: Duration::hours(2),
            recorrente: Some(Recorrencia::Semanal),
        },
        Compromisso {
            titulo: "1:1 com gestor".to_string(),
            inicio: amanha.and_hms_opt(10, 0, 0).unwrap().and_utc(),
            duracao: Duration::minutes(30),
            recorrente: Some(Recorrencia::Semanal),
        },
        Compromisso {
            titulo: "Almoço com equipe".to_string(),
            inicio: amanha.and_hms_opt(12, 0, 0).unwrap().and_utc(),
            duracao: Duration::hours(1),
            recorrente: None,
        },
    ];

    for c in compromissos {
        match agenda.adicionar(c) {
            Ok(()) => {}
            Err(e) => println!("Aviso: {}", e),
        }
    }

    // Testar conflito
    let conflito = Compromisso {
        titulo: "Reunião extra".to_string(),
        inicio: hoje.and_hms_opt(14, 30, 0).unwrap().and_utc(),
        duracao: Duration::minutes(30),
        recorrente: None,
    };
    match agenda.adicionar(conflito) {
        Ok(()) => println!("Adicionado com sucesso"),
        Err(e) => println!("Conflito detectado: {}", e),
    }

    // Compromissos de hoje
    println!("\n--- Compromissos de hoje ({}) ---", hoje.format("%d/%m/%Y"));
    for c in agenda.compromissos_do_dia(hoje) {
        c.exibir();
    }

    // Compromissos de amanhã
    println!("\n--- Compromissos de amanhã ({}) ---", amanha.format("%d/%m/%Y"));
    for c in agenda.compromissos_do_dia(amanha) {
        c.exibir();
    }

    // Próximos compromissos
    println!("\n--- Próximos 3 compromissos ---");
    for c in agenda.proximos_compromissos(3) {
        let inicio_local = c.inicio.with_timezone(&Local);
        println!(
            "  {} - {} ({})",
            c.titulo,
            inicio_local.format("%d/%m %H:%M"),
            tempo_ate(c.inicio)
        );
    }

    // Cálculo de idade
    println!("\n--- Cálculo de Idade ---");
    let nascimento = NaiveDate::from_ymd_opt(1990, 5, 20).unwrap();
    let (anos, meses, dias) = calcular_idade(nascimento);
    println!(
        "Nascido em {}: {} anos, {} meses e {} dias",
        nascimento.format("%d/%m/%Y"),
        anos,
        meses,
        dias
    );

    // Contagem regressiva
    println!("\n--- Contagens Regressivas ---");
    let ano_novo = NaiveDate::from_ymd_opt(2027, 1, 1)
        .unwrap()
        .and_hms_opt(0, 0, 0)
        .unwrap()
        .and_utc();
    println!("Ano Novo 2027: {}", tempo_ate(ano_novo));
}

Comparação com Alternativas

CrateCaso de usoDestaques
chronoUso geralMais popular, API rica, serde
timeUso geralPuro Rust, mais seguro (#[forbid(unsafe_code)])
jiffUso geralMais nova, API moderna, fusos IANA
humantimeDuração legível“2 hours 30 minutes”
std::timeInstantes/duraçãoApenas SystemTime e Duration

Chrono é a escolha mais estabelecida e amplamente usada. time é uma alternativa mais conservadora sem uso de unsafe. jiff é a mais recente, com suporte nativo a fusos horários IANA.

Conclusão

A crate chrono é essencial para qualquer aplicação Rust que manipule datas e horários. Com seus tipos ricos (NaiveDate, DateTime<Utc>, Duration), formatação flexível via strftime, parsing robusto e integração com serde, ela cobre a vasta maioria dos casos de uso.

Lembre-se de armazenar datas em UTC internamente, converter para fuso local apenas na exibição, usar métodos checked_ para operações que podem falhar (como adicionar meses), e tratar erros de parsing com elegância.

Próximos passos:

  • Explore a crate serde para serializar datas em APIs
  • Veja regex para extrair datas de texto não estruturado
  • Confira tokio para timers e intervalos assíncronos