Глава 14: Работа со временем и датами в Rust

Содержание: Основы std::time: SystemTime, Duration Библиотека chrono: DateTime, таймзоны Замер времени выполнения Форматирование и парсинг дат Примеры: таймер, логирование с метками времени Упражнение: Написать таймер с логированием Практические советы Проверочное задание

Добро пожаловать в раздел 14 нашего курса по Rust! Сегодня мы подробно разберём всё, что связано с работой со временем и датами в этом языке программирования. Мы начнём с базовых инструментов стандартной библиотеки std::time, затем углубимся в мощную стороннюю библиотеку chrono, научимся измерять время выполнения кода, форматировать и парсить даты, а также рассмотрим практические примеры и упражнение. Эта лекция будет максимально самодостаточной, так что даже если вы новичок, вы сможете всё понять и применить на практике.


1. Основы: std::time

Стандартная библиотека Rust предоставляет модуль std::time для работы с системным временем и длительностями. Это базовый инструмент, который полезен для простых задач, таких как замер времени выполнения или работа с временными интервалами.

1.1 SystemTime

SystemTime — это тип, представляющий момент времени относительно системных часов. Обычно он отсчитывается от так называемой "эпохи UNIX" (1 января 1970 года, 00:00:00 UTC).

Пример использования:

use std::time::{SystemTime, UNIX_EPOCH};

fn main() {
    // Получаем текущее системное время
    let now = SystemTime::now();

    // Вычисляем, сколько времени прошло с эпохи UNIX
    match now.duration_since(UNIX_EPOCH) {
        Ok(duration) => println!("Секунд с эпохи UNIX: {}", duration.as_secs()),
        Err(e) => println!("Ошибка: {:?}", e),
    }
}

Объяснение:

Важно: SystemTime зависит от системных часов, которые могут быть неточными или изменяться (например, при синхронизации времени). Для задач, где нужна монотонность (например, замер времени выполнения), лучше использовать Instant (о нём ниже).

1.2 Duration

Duration — это структура для представления промежутков времени. Она измеряется в секундах и наносекундах.

Пример:

use std::time::Duration;

fn main() {
    let five_seconds = Duration::from_secs(5); // 5 секунд
    let millis = Duration::from_millis(250);   // 250 миллисекунд

    let total = five_seconds + millis;
    println!("Общая длительность: {} секунд", total.as_secs_f64());
}

Объяснение:

Совет: Используйте Duration для любых операций с временными интервалами — это надёжный и безопасный способ.

1.3 Instant для замера времени

Для измерения времени выполнения кода лучше использовать Instant, так как он монотонен (не зависит от скачков системных часов).

Пример:

use std::time::Instant;

fn main() {
    let start = Instant::now();
    
    // Имитация долгой работы
    for _ in 0..1_000_000 {
        // Пустой цикл
	// Просто перебирает числа от 0 до 999_999 (всего 1 миллион итераций)
	// но поскольку переменная _ не используется, это пример пустого цикла
	// для тестирования производительности или ожидания.
	// Без тела цикла {} он ничего не делает, кроме итераций.
    }
    
    let duration = start.elapsed();
    println!("Время выполнения: {:?}", duration);
}

Объяснение:


2. Библиотека chrono

Стандартная библиотека Rust покрывает только базовые потребности. Для более сложных задач (даты, таймзоны, форматирование) используется популярная библиотека chrono. Добавьте её в ваш проект через Cargo.toml:

[dependencies]
chrono = "0.4"

2.1 DateTime и основные операции

DateTime из chrono позволяет работать с датами и временем с учётом часовых поясов.

Пример:

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

fn main() {
    // Текущее время в UTC
    let now = Utc::now();
    println!("Текущее время в UTC: {}", now);

    // Конкретная дата и время
    let specific_date = Utc.with_ymd_and_hms(2025, 3, 26, 12, 0, 0).unwrap();
    println!("Конкретная дата: {}", specific_date);

    // Разница между датами
    let difference = now - specific_date;
    println!("Разница: {:?}", difference);
}

Объяснение:

2.2 Таймзоны

chrono поддерживает работу с таймзонами через тип DateTime<Tz>, где Tz — это часовой пояс.

Пример:

use chrono::{Utc, FixedOffset};

fn main() {
    // Время в UTC
    let utc_time = Utc::now();

    // Смещение +03:00 (например, Москва)
    let offset = FixedOffset::east_opt(3 * 3600).unwrap(); // 3 часа в секундах
    let local_time = utc_time.with_timezone(&offset);
    println!("Время в +03:00: {}", local_time);
}

Объяснение:

Для работы с именованными таймзонами (например, "Europe/Moscow") нужно подключить chrono-tz:

[dependencies]
chrono-tz = "0.8"

Пример с chrono-tz:

use chrono::{Utc};
use chrono_tz::Europe::Moscow;

fn main() {
    let utc_time = Utc::now();
    let moscow_time = utc_time.with_timezone(&Moscow);
    println!("Время в Москве: {}", moscow_time);
}

3. Замер времени выполнения

Мы уже видели Instant для замера времени. Давайте создадим более практичный пример — функцию, которая измеряет время выполнения другой функции.

Пример:

use std::time::Instant;

fn measure_time<F>(task: F) -> Duration 
where F: FnOnce() {
    let start = Instant::now();
    task();
    start.elapsed()
}

fn main() {
    let duration = measure_time(|| {
        for _ in 0..1_000_000 {
            // Долгая операция
        }
    });
    println!("Задача выполнена за: {:?}", duration);
}

Объяснение:

Итого: эта функция принимает замыкание (например, кусок кода, который нужно измерить), засекает время перед его выполнением, выполняет его, а затем возвращает, сколько времени заняло выполнение.


4. Форматирование и парсинг дат

chrono позволяет форматировать даты в строки и парсить строки в даты.

4.1 Форматирование

Пример:

use chrono::Utc;

fn main() {
    let now = Utc::now();
    // Простой формат
    println!("Простой: {}", now.format("%Y-%m-%d %H:%M:%S"));
    // Пользовательский формат
    println!("Кастом: {}", now.format("Дата: %d %B %Y, %A, %H:%M"));
}

Объяснение:

Полный список спецификаторов: смотрите документацию chrono (strftime).

4.2 Парсинг

Пример:

use chrono::{DateTime, Utc};

fn main() {
    let date_str = "2025-03-26 12:00:00";
    let parsed_date = DateTime::parse_from_str(date_str, "%Y-%m-%d %H:%M:%S").unwrap();
    println!("Спарсенная дата: {}", parsed_date);
}

Объяснение:


5. Практические примеры

5.1 Таймер

Создадим простой таймер с обратным отсчётом:

use std::time::{Duration, Instant};
use std::thread::sleep;

fn countdown(seconds: u64) {
    let duration = Duration::from_secs(seconds);
    let start = Instant::now();

    while start.elapsed() < duration {
        let remaining = duration - start.elapsed();
        println!("Осталось: {} секунд", remaining.as_secs());
        sleep(Duration::from_secs(1));
    }
    println!("Таймер завершён!");
}

fn main() {
    countdown(5);
}

В Rust функция sleep не принимает число напрямую. Она принимает аргумент типа std::time::Duration, который представляет промежуток времени. Чтобы указать время в секундах, миллисекундах или других единицах, нужно использовать методы создания Duration, такие как Duration::from_secs или Duration::from_millis.

5.2 Логирование с метками времени

Пример логирования с использованием chrono:

use chrono::Utc;

fn log(message: &str) {
    let timestamp = Utc::now().format("%Y-%m-%d %H:%M:%S");
    println!("[{}] {}", timestamp, message);
}

fn main() {
    log("Программа запущена");
    log("Выполняется операция...");
    log("Программа завершена");
}

6 Упражнение: Написать таймер с логированием

Задание

Напишите программу, которая:

  1. Запускает таймер на заданное количество секунд (например, 10).
  2. Каждую секунду выводит оставшееся время.
  3. Логирует начало, каждую секунду и окончание таймера с метками времени в формате "ГГГГ-ММ-ДД ЧЧ:ММ:СС".

Решение

use std::time::{Duration, Instant};
use std::thread::sleep;
use chrono::Utc;

fn log(message: &str) {
    let timestamp = Utc::now().format("%Y-%m-%d %H:%M:%S");
    println!("[{}] {}", timestamp, message);
}

fn countdown_with_logging(seconds: u64) {
    log("Таймер запущен");
    
    let duration = Duration::from_secs(seconds);
    let start = Instant::now();

    while start.elapsed() < duration {
        let remaining = duration - start.elapsed();
        log(&format!("Осталось {} секунд", remaining.as_secs()));
        sleep(Duration::from_secs(1));
    }
    
    log("Таймер завершён!");
}

fn main() {
    countdown_with_logging(10);
}

Разбор:

Проверка:

Запустите программу и убедитесь, что она выводит сообщения с метками времени каждую секунду в течение 10 секунд.


7. Практические советы

  1. Выбор инструмента:
  2. Обработка ошибок:
  3. Производительность:
  4. Локализация:

8. Проверочное задание

  1. Напишите программу, которая:
  2. Добавьте обработку ошибок для некорректного ввода.

Подсказка: Используйте std::env::args для аргументов и chrono для парсинга и вычислений.


На этом лекция завершена! Вы теперь знаете, как работать со временем и датами в Rust на уровне от новичка до эксперта. Практикуйтесь и экспериментируйте!