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 UTCDateTime<Local>— data/hora no fuso local do sistemaDateTime<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:
| Especificador | Descrição | Exemplo |
|---|---|---|
%Y | Ano com 4 dígitos | 2024 |
%m | Mês (01-12) | 03 |
%d | Dia (01-31) | 15 |
%H | Hora 24h (00-23) | 14 |
%M | Minuto (00-59) | 30 |
%S | Segundo (00-59) | 45 |
%f | Nanosegundos | 123456789 |
%.3f | Milissegundos | .123 |
%A | Dia da semana por extenso | Friday |
%a | Dia da semana abreviado | Fri |
%B | Mês por extenso | March |
%b | Mês abreviado | Mar |
%z | Offset do fuso (+/-HHMM) | -0300 |
%Z | Nome do fuso | BRT |
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
| Crate | Caso de uso | Destaques |
|---|---|---|
chrono | Uso geral | Mais popular, API rica, serde |
time | Uso geral | Puro Rust, mais seguro (#[forbid(unsafe_code)]) |
jiff | Uso geral | Mais nova, API moderna, fusos IANA |
humantime | Duração legível | “2 hours 30 minutes” |
std::time | Instantes/duração | Apenas 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: