Продвинутая работа с датами и временем в Rust: chrono и time

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Продвинутая работа с датами и временем в Rust

В современной разработке на Rust работа с датами и временем выходит далеко за рамки простого получения текущего момента. При создании высоконагруженных систем, финансовых приложений или распределенных сервисов требуется точный учет часовых поясов, эффективный парсинг, сериализация и сложные временные вычисления. В этой статье мы рассмотрим продвинутые техники работы с датами и временем, используя крейты chrono и time.

1. Библиотека chrono: основа продвинутой работы

Крейт chrono является стандартом де-факто для работы с датами и временем в Rust. Он предоставляет две ключевые абстракции: NaiveDateTime (дата-время без часового пояса) и DateTime<Tz> (дата-время с привязкой к часовому поясу).

Пример: работа с часовыми поясами

use chrono::{DateTime, Utc, Local, FixedOffset, NaiveDateTime};
use chrono::format::ParseError;

fn main() -> Result<(), ParseError> {
    // Текущее время в UTC
    let utc_now: DateTime<Utc> = Utc::now();
    println!("UTC now: {}", utc_now);

    // Текущее локальное время
    let local_now: DateTime<Local> = Local::now();
    println!("Local now: {}", local_now);

    // Конвертация в другой часовой пояс (Москва, UTC+3)
    let moscow_offset = FixedOffset::east_opt(3 * 3600).unwrap();
    let moscow_time = utc_now.with_timezone(&moscow_offset);
    println!("Moscow time: {}", moscow_time);

    // Парсинг строки с часовым поясом
    let parsed = DateTime::parse_from_rfc3339("2024-03-15T14:30:00+05:00")?;
    println!("Parsed with offset: {}", parsed);

    Ok(())
}

Важно: Используйте Utc для хранения в базах данных и API, а конвертацию в локальные пояса выполняйте только на уровне представления.

2. Сложные временные вычисления и интервалы

Библиотека chrono поддерживает арифметику дат с помощью типа Duration и более сложные операции через трейт TimeDelta. Для работы с интервалами и периодами (например, "следующий месяц") используйте Months и Weeks.

Пример: вычисления с датами

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

fn main() {
    let start = NaiveDate::from_ymd_opt(2024, 1, 15).unwrap();

    // Добавление дней
    let plus_10_days = start + Duration::days(10);
    println!("+10 days: {}", plus_10_days);

    // Добавление месяцев (учитывает разную длину месяцев)
    let plus_2_months = start.checked_add_months(Months::new(2)).unwrap();
    println!("+2 months: {}", plus_2_months);

    // Поиск следующего понедельника
    let next_monday = start.iter_days()
        .skip(1)
        .find(|d| d.weekday() == Weekday::Mon)
        .unwrap();
    println!("Next Monday: {}", next_monday);

    // Разница между датами в днях
    let end = NaiveDate::from_ymd_opt(2024, 12, 31).unwrap();
    let days_diff = end.signed_duration_since(start).num_days();
    println!("Days between: {}", days_diff);
}

Совет: Для финансовых расчетов используйте checked_add_months, а не простое сложение дней, чтобы корректно обрабатывать концы месяцев (например, 31 января + 1 месяц = 28/29 февраля).

3. Сериализация и парсинг дат с serde

При работе с JSON, YAML или базами данных критически важна правильная сериализация дат. Крейт chrono интегрируется с serde через feature serde. Для кастомных форматов используйте атрибуты #[serde(with = "...")].

Пример: сериализация в ISO 8601 и кастомные форматы

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

#[derive(Debug, Serialize, Deserialize)]
struct Event {
    #[serde(with = "chrono::serde::ts_seconds")]
    timestamp: DateTime<Utc>,

    #[serde(with = "chrono::serde::ts_nanoseconds")]
    precise_time: DateTime<Utc>,

    // Кастомный формат через модуль
    #[serde(with = "custom_date_format")]
    date_only: NaiveDateTime,
}

mod custom_date_format {
    use chrono::{NaiveDateTime, ParseError};
    use serde::{self, Deserialize, Serializer, Deserializer};

    const FORMAT: &str = "%Y-%m-%d %H:%M:%S";

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

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

fn main() {
    let event = Event {
        timestamp: Utc::now(),
        precise_time: Utc::now(),
        date_only: chrono::Local::now().naive_local(),
    };

    let json = serde_json::to_string_pretty(&event).unwrap();
    println!("Serialized:\n{}", json);
}

Рекомендация: Для API используйте ts_seconds или ts_milliseconds — это компактно и однозначно. Избегайте строковых форматов без явного указания часового пояса.

4. Библиотека time: легковесная альтернатива

Крейт time — это более современная и безопасная альтернатива chrono. Он использует систему типов для предотвращения ошибок, связанных с часовыми поясами, и имеет встроенную поддержку сериализации.

Пример: работа с time крейтом

use time::{OffsetDateTime, PrimitiveDateTime, Duration, Month, Weekday};
use time::macros::{datetime, format_description};
use time::error::Parse;

fn main() -> Result                

Блоги

Рекомендаций по книгам