Введение в работу с датами и временем в Rust
Работа с датами и временем — одна из ключевых задач в большинстве современных приложений. Будь то логирование событий, расчёт дедлайнов, обработка временных меток из API или работа с календарём — без качественной библиотеки для работы с временем не обойтись. В экосистеме Rust стандартная библиотека предоставляет лишь базовые возможности (std::time::Instant и std::time::Duration), которые подходят для замера производительности, но не для полноценной работы с календарными датами.
На помощь приходит крейт chrono — де-факто стандарт для работы с датами и временем в Rust. Он предлагает широкий функционал: от создания простых дат до работы с часовыми поясами, форматированием и арифметикой. В этой статье мы подробно разберём основные возможности chrono, приведём практические примеры и покажем, как избежать типичных ошибок.
1. Установка и основные типы chrono
Для начала добавьте зависимость в ваш Cargo.toml:
[dependencies]
chrono = "0.4"
После этого вы можете импортировать необходимые типы. Chrono предлагает три ключевых типа для представления дат и времени:
NaiveDate— дата без времени (год, месяц, день).NaiveDateTime— дата и время без учёта часового пояса.DateTime<Tz>— дата и время с привязкой к часовому поясу (например,UtcилиLocal).
Рассмотрим простейший пример создания этих типов:
use chrono::{NaiveDate, NaiveDateTime, DateTime, Utc, Local};
fn main() {
// Создаём наивную дату (без времени)
let date = NaiveDate::from_ymd_opt(2025, 10, 15).unwrap();
println!("Дата: {}", date);
// Создаём наивное время (без даты)
let time = NaiveTime::from_hms_opt(14, 30, 0).unwrap();
println!("Время: {}", time);
// Комбинируем в NaiveDateTime
let naive_dt = NaiveDateTime::new(date, time);
println!("Наивный datetime: {}", naive_dt);
// Текущее время в UTC и локальное
let now_utc: DateTime<Utc> = Utc::now();
let now_local: DateTime<Local> = Local::now();
println!("Сейчас UTC: {}", now_utc);
println!("Сейчас локальное: {}", now_local);
}
Обратите внимание на использование from_ymd_opt и from_hms_opt — они возвращают Option, что защищает от создания невалидных дат (например, 32 января).
2. Форматирование и парсинг дат
Chrono использует мощную систему форматирования на основе спецификаторов, очень похожую на strftime из C/PHP. Это позволяет гибко преобразовывать даты в строки и обратно.
2.1 Форматирование даты в строку
use chrono::{Utc, DateTime};
fn main() {
let now: DateTime<Utc> = Utc::now();
// Стандартный ISO 8601
println!("ISO: {}", now.to_rfc3339());
// Кастомный формат
println!("Кастом: {}", now.format("%Y-%m-%d %H:%M:%S"));
println!("День недели: {}", now.format("%A"));
println!("Месяц: {}", now.format("%B"));
println!("Год-месяц-день: {}", now.format("%d.%m.%Y"));
}
Основные спецификаторы форматирования:
%Y— год (4 цифры)%m— месяц (01-12)%d— день месяца (01-31)%H— часы (00-23)%M— минуты (00-59)%S— секунды (00-60)%A— полное название дня недели%B— полное название месяца
2.2 Парсинг строки в дату
Для обратного преобразования используется метод parse_from_str:
use chrono::{NaiveDateTime, DateTime, Utc, format::ParseResult};
fn main() {
// Парсинг наивного datetime
let date_str = "2025-10-15 14:30:00";
let naive = NaiveDateTime::parse_from_str(date_str, "%Y-%m-%d %H:%M:%S");
match naive {
Ok(dt) => println!("Распарсено: {}", dt),
Err(e) => eprintln!("Ошибка парсинга: {}", e),
}
// Парсинг с часовым поясом
let rfc_str = "2025-10-15T14:30:00+03:00";
let dt_with_tz = DateTime::parse_from_rfc3339(rfc_str);
println!("С часовым поясом: {:?}", dt_with_tz);
// Преобразование в UTC
if let Ok(dt) = dt_with_tz {
let utc_dt = dt.with_timezone(&Utc);
println!("В UTC: {}", utc_dt);
}
}
Важно: при парсинге всегда обрабатывайте ошибки через match или unwrap_or_else, так как пользовательский ввод может быть некорректным.
3. Арифметика с датами и временем
Chrono поддерживает сложение и вычитание с помощью Duration (из стандартной библиотеки) и собственного TimeDelta. Это позволяет легко выполнять операции вроде "добавить 3 дня" или "вычесть 2 часа".
use chrono::{NaiveDate, Duration, TimeDelta};
fn main() {
let start = NaiveDate::from_ymd_opt(2025, 10, 15).unwrap();
// Добавляем дни
let plus_10_days = start + Duration::days(10);
println!("Через 10 дней: {}", plus_10_days);
// Вычитаем недели
let minus_2_weeks = start - Duration::weeks(2);
println!("2 недели назад: {}", minus_2_weeks);
// Используем TimeDelta (более гибко)
let delta = TimeDelta::try_days(30).unwrap();
let plus_month = start + delta;
println!("Через 30 дней: {}", plus_month);
// Разница между датами
let end = NaiveDate::from_ymd_opt(