Fechas y horas en Rust con chrono
Los programas trabajan con el tiempo todo el rato. “¿Cuánto tardó esto?” “¿Qué día vence la factura?” “¿Cuántos días faltan para el viaje?” Rust separa estas preguntas en dos tipos.
El módulo std::time de la biblioteca estándar es un cronómetro: mide cuánto tiempo pasa. No sabe nada de calendarios, meses ni días de la semana. Para trabajar con calendarios, casi todos los proyectos de Rust usan el crate chrono. Esta página empieza con el cronómetro y luego pasa al calendario.
Medir el tiempo transcurrido con Instant
Sección titulada «Medir el tiempo transcurrido con Instant»Instant::now() toma una lectura del reloj del ordenador. Más tarde, .elapsed() te dice cuánto tiempo ha pasado desde esa lectura, como un 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!("tardó {:?}", elapsed); println!("tardó {} ms", elapsed.as_millis()); println!("¿menos de 5 segundos? {}", elapsed < Duration::from_secs(5));}Aquí no mostramos la salida a propósito: el tiempo cambia en cada ordenador y en cada ejecución. Verás algo como tardó 6.862793ms. Prueba a pulsar el botón dos veces y compara.
Un Instant solo sirve para medir. No puedes imprimirlo como una fecha, y solo avanza hacia delante. Eso es justo lo que quieres para cronometrar código, porque nunca le afecta que alguien cambie el reloj del sistema.
Duraciones
Sección titulada «Duraciones»Un Duration es una cantidad de tiempo, como “90 segundos”. Lo creas a partir de segundos o milisegundos, puedes sumar duraciones y compararlas:
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);}Salida:
1.5s90s1500 ms1.5 stotal: 91.5s1 min 30 strueTe encontrarás Duration en muchos sitios: std::thread::sleep(Duration::from_millis(100)) pausa un hilo, y los tiempos de espera (timeouts) de las bibliotecas de red también reciben un Duration.
La hora del reloj con SystemTime
Sección titulada «La hora del reloj con SystemTime»SystemTime es la hora del reloj del ordenador, la que ves en una esquina de la pantalla. Lo habitual es preguntar cuánto tiempo ha pasado desde la época Unix (Unix epoch), el 1 de enero de 1970 (UTC). Ese número se llama marca de tiempo Unix (Unix timestamp):
use std::time::{SystemTime, UNIX_EPOCH};
fn main() { let now = SystemTime::now(); let since_1970 = now .duration_since(UNIX_EPOCH) .expect("el reloj está antes de 1970"); println!("Segundos desde el 1 de enero de 1970: {}", since_1970.as_secs());}El número depende de cuándo lo ejecutes, así que tampoco mostramos la salida. ¿Por qué duration_since devuelve un Result? Porque el reloj del sistema puede estar mal o puede retroceder. Por eso también deberías usar Instant, y no SystemTime, para cronometrar tu código.
Eso es prácticamente todo lo que hace std::time. No tiene ni idea de qué significa “marzo” o “viernes”. Para eso necesitas chrono.
Añadir chrono
Sección titulada «Añadir chrono»Añade el crate a tu proyecto (consulta Cargo y crates si esto es nuevo para ti):
cargo add chrono[dependencies]chrono = "0.4"El Playground de Rust también tiene chrono, así que todos los botones de abajo funcionan. chrono tiene dos familias de tipos:
- Los tipos naive (“ingenuos”:
NaiveDate,NaiveTime,NaiveDateTime) son fechas y horas sin zona horaria. “14 de marzo de 2025” o “09:30” apuntados en un calendario. La mayoría del código del día a día usa estos. DateTime<Tz>es un momento en el tiempo con zona horaria, comoDateTime<Utc>oDateTime<Local>.
Crear fechas a partir de valores fijos
Sección titulada «Crear fechas a partir de valores fijos»NaiveDate::from_ymd_opt(year, month, day) construye una fecha (año, mes, día). Devuelve un Option, porque no todas las combinaciones son una fecha real:
use chrono::{Datelike, NaiveDate, NaiveTime};
fn main() { let date = NaiveDate::from_ymd_opt(2025, 3, 14).unwrap(); println!("{date}"); println!("año {}, mes {}, día {}", 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);}Salida:
2025-03-14año 2025, mes 3, día 142025-03-14 09:30:002025-03-14 17:45:10NoneEl 30 de febrero no existe, así que obtienes None en lugar de una fecha rota. Con valores fijos que escribiste tú, .unwrap() está bien. Con valores que vienen de un usuario, gestiona el None.
La línea use chrono::Datelike trae los métodos .year(), .month() y .day(). (Timelike hace lo mismo con .hour() y .minute().)
La fecha y hora actuales
Sección titulada «La fecha y hora actuales»Utc::now() te da el momento actual en UTC. Local::now() te lo da en la zona horaria del propio ordenador:
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!("Hoy: {}", local_now.format("%Y-%m-%d"));
let today = local_now.date_naive(); println!("Como NaiveDate: {today}");}La salida es la fecha de hoy, así que cambia cada día. Los servidores del Playground usan UTC, así que allí las dos líneas mostrarán la misma hora. .date_naive() descarta la zona horaria y la hora, y deja un NaiveDate sin más. Un buen hábito: obtén “ahora” una sola vez en el borde de tu programa y pasa la fecha a tus funciones. Así son fáciles de probar, como verás en el ejemplo de la fecha límite.
Formatear fechas
Sección titulada «Formatear fechas».format() recibe un patrón. Cada código % se sustituye por una parte de la fecha. %Y es el año, %m el número de mes y %d el día:
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("informe-%Y%m%d.txt").to_string(); println!("{file_name}");}Salida:
2025-03-1414/03/2025Friday, March 14, 2025Fri 14 Mar09:059:05 AMinforme-20250314.txtLos códigos que más usarás: %Y año, %m mes (01–12), %d día (01–31), %H hora (00–23), %M minuto, %S segundo, %A nombre del día de la semana, %B nombre del mes. Un - después del % quita el cero inicial, así que %-d imprime 5 en lugar de 05. .format() devuelve algo que puedes imprimir. Llama a .to_string() cuando necesites un String.
Fíjate en que los nombres de días y meses salen en inglés (“Friday”, “March”). Es lo que chrono hace por defecto. Si necesitas nombres en español, chrono tiene una feature opcional, unstable-locales, con el método format_localized. Para fechas que lee una máquina (archivos, APIs), los formatos numéricos como %Y-%m-%d son la mejor opción de todos modos.
Convertir texto en fechas
Sección titulada «Convertir texto en fechas»NaiveDate::parse_from_str hace lo contrario: lee texto usando el mismo patrón con %. Devuelve un Result, porque puede que el texto no encaje:
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 marzo", "2025-03"] { match NaiveDate::parse_from_str(text, "%Y-%m-%d") { Ok(date) => println!("Leída: {date}"), Err(e) => println!("No se pudo leer {text:?}: {e}"), } }}Salida:
Ok(2025-03-14)Ok(2025-03-14)Ok(2025-03-14)Ok(2025-03-14T09:30:00)No se pudo leer "2025-13-01": input is out of rangeNo se pudo leer "14 de marzo": input contains invalid charactersNo se pudo leer "2025-03": premature end of inputPara el formato estándar YYYY-MM-DD también puedes llamar simplemente a .parse(). El mes 13 no existe, así que el primer ejemplo malo da “input is out of range” (la entrada está fuera de rango). Los mensajes de error de chrono están en inglés: “input contains invalid characters” significa que la entrada tiene caracteres no válidos, y “premature end of input” que el texto se acabó antes de tiempo.
Aritmética con fechas
Sección titulada «Aritmética con fechas»Sumar días a una fecha a mano es sorprendentemente difícil: los meses tienen longitudes distintas y existen los años bisiestos. chrono lo hace por ti. Usa checked_add_days con Days, checked_add_months con Months, o suma 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 días después: {plus_30}");
let minus_7 = start.checked_sub_days(Days::new(7)).unwrap(); println!("7 días antes: {minus_7}");
let next_month = start.checked_add_months(Months::new(1)).unwrap(); println!("1 mes después: {next_month}");
let week_later = start + TimeDelta::weeks(1); println!("1 semana después: {week_later}");
let dt = start.and_hms_opt(23, 30, 0).unwrap(); let later = dt + TimeDelta::hours(2); println!("2 horas después: {later}");}Salida:
30 días después: 2025-03-027 días antes: 2025-01-241 mes después: 2025-02-281 semana después: 2025-02-072 horas después: 2025-02-01 01:30:00Mira “1 mes después”: el 31 de enero más un mes es el 28 de febrero, porque no existe el 31 de febrero. chrono elige el último día válido. Los métodos checked_ devuelven un Option, que solo es None si el resultado se sale del rango de años de chrono (que es enorme). Sumar 2 horas a las 23:30 pasa correctamente al día siguiente.
Días de la semana y la diferencia entre dos fechas
Sección titulada «Días de la semana y la diferencia entre dos fechas».weekday() (de Datelike) te dice el día de la semana. Restar una fecha a otra te da un TimeDelta, y .num_days() lo convierte en un 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} es {day} ({})", date.format("%A"));
let weekend = matches!(day, Weekday::Sat | Weekday::Sun); println!("¿fin 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!("{} días, o {} semanas completas", gap.num_days(), gap.num_weeks()); println!("al revés: {} días", (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!("estancia en el hotel: {} horas", (check_out - check_in).num_hours());}Salida:
2025-03-15 es Sat (Saturday)¿fin de semana? true358 días, o 51 semanas completasal revés: -358 díasestancia en el hotel: 69 horasWeekday es un enum con las variantes Mon a Sun (de lunes a domingo, en inglés), así que puedes usar match con él. Fíjate en que la diferencia puede ser negativa si la segunda fecha es posterior. Eso es útil: un número negativo de días restantes significa “vencido”.
Ejemplo: días hasta una fecha límite
Sección titulada «Ejemplo: días hasta una fecha límite»Vamos a juntarlo todo. La función recibe “hoy” como parámetro en lugar de llamar a Local::now() por dentro. Así la salida de abajo es siempre la misma, y la función es fácil de probar. En un programa real le pasarías 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 hoy"), 1 => String::from("vence mañana"), d if d > 1 => format!("quedan {d} días"), -1 => String::from("vencido hace 1 día"), d => format!("vencido hace {} días", -d), }}
fn main() { let today = NaiveDate::from_ymd_opt(2025, 6, 10).unwrap();
let tasks = [ ("Pagar el alquiler", "2025-06-10"), ("Enviar el informe", "2025-06-11"), ("Renovar pasaporte", "2025-07-01"), ("Devolver un libro", "2025-06-09"), ("Pagar al dentista", "2025-06-03"), ];
println!("Hoy es {}", today.format("%A, %-d %B %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 %b"), days_left(today, deadline)); }}Salida:
Hoy es Tuesday, 10 June 2025Pagar el alquiler 10 Jun vence hoyEnviar el informe 11 Jun vence mañanaRenovar pasaporte 01 Jul quedan 21 díasDevolver un libro 09 Jun vencido hace 1 díaPagar al dentista 03 Jun vencido hace 7 díasPrueba a cambiar today por otra fecha y vuelve a ejecutarlo. El {:<20} rellena el nombre hasta 20 caracteres para que las columnas queden alineadas (consulta Salida por pantalla). Los nombres de las tareas no llevan tildes a propósito: así las columnas se ven alineadas en cualquier terminal.
chrono no es la única opción. El crate time es una alternativa más pequeña y popular, y el crate más reciente jiff cuida especialmente las zonas horarias y el horario de verano. Aquí usamos chrono porque es el más usado y está en el Playground.
Errores comunes
Sección titulada «Errores comunes»Olvidar use chrono::Datelike. Sin él, .year(), .month() y .weekday() no funcionan. El error dice que el método es “private” (privado), lo que confunde, pero la línea de ayuda te da la solución real: el trait Datelike no está en el ámbito, así que impórtalo.
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 y %M. %m es el mes y %M es el minuto. Con una fecha y hora, el equivocado no da ningún error. Imprime los minutos sin decir nada:
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")); // mal: minutos println!("{}", meeting.format("%Y-%m-%d")); // bien: mes}Salida:
2025-05-142025-03-14Cronometrar código con SystemTime. El reloj del sistema puede dar saltos (por ejemplo, cuando se sincroniza con internet). Usa Instant para medir, y SystemTime o chrono solo cuando necesites la fecha de verdad.
Llamar a Local::now() en lo más profundo de tu lógica. Hace que los resultados cambien cada día y que las pruebas sean difíciles de escribir. Pasa la fecha como parámetro, como hace days_left.
Resumen
Sección titulada «Resumen»std::time::Instantmide el tiempo transcurrido;Durationes una cantidad de tiempo;SystemTimees la hora del reloj (útil para marcas de tiempo Unix).- Para calendarios, añade
chrono. UsaNaiveDate/NaiveDateTimepara fechas sin zona horaria, yUtc::now()/Local::now()para el momento actual. from_ymd_optdevuelve unOptionyparse_from_strdevuelve unResult, porque no todas las entradas son una fecha real..format("%Y-%m-%d")convierte una fecha en texto usando códigos%. Recuerda:%mes el mes y%Mes el minuto.- Suma tiempo con
checked_add_days,checked_add_monthso+ TimeDelta. Resta dos fechas para obtener unTimeDeltay luego llama a.num_days(). - Pasa “hoy” a tus funciones en lugar de leer el reloj dentro de ellas.
Comprueba lo que aprendiste
Sección titulada «Comprueba lo que aprendiste»Comprueba lo aprendido
1. ¿Qué tipo deberías usar para medir cuánto tarda un trozo de código?
2. ¿Qué devuelve NaiveDate::from_ymd_opt(2025, 2, 30)?
3. En un patrón de formato de chrono, ¿qué significa %M?
4. Restas dos NaiveDate: deadline - today. ¿Cómo obtienes el número de días?
5. ¿Cuánto es el 31 de enero de 2025 más Months::new(1) en chrono?
Puntuación: 0 / 5
Para leer más
Sección titulada «Para leer más»- std::time — biblioteca estándar de Rust (en inglés):
Instant,DurationySystemTime, con más ejemplos. - Documentación de chrono — docs.rs (en inglés): la guía completa de todos los tipos y métodos de chrono.
- Códigos de formato de chrono — docs.rs (en inglés): la lista completa de códigos
%paraformatyparse_from_str. - Date and Time — Rust Cookbook (en inglés): recetas cortas para tareas habituales con fechas y horas.
- Documentación de jiff — docs.rs (en inglés): un crate de fechas y horas más reciente con muy buen soporte de zonas horarias, por si quieres compararlo.