STL

std::chrono: работа с временем, длительностями и таймзонами в современном C++

Введение

До C++11 работа с временем в стандартной библиотеке ограничивалась C-API (time_t, struct tm, difftime) — без типобезопасности единиц измерения и с неудобной работой с датами. std::chrono ввела строго типизированные длительности (duration) и точки во времени (time_point), а C++20 добавила полноценную поддержку календаря и таймзон, закрыв исторический пробел.

Концепция

Три ключевых сущности std::chrono:

  • duration<Rep, Period> — промежуток времени с типобезопасной единицей измерения (std::chrono::seconds, milliseconds, nanoseconds, и т.д.). Компилятор не позволит случайно сложить секунды с миллисекундами без явного преобразования.
  • time_point<Clock, Duration> — конкретный момент времени относительно эпохи определённых часов (Clock). Основные часы: system_clock (календарное время, может быть скорректировано NTP), steady_clock (монотонные, не идут назад — для измерения интервалов), high_resolution_clock.
  • C++20: std::chrono::year_month_day, zoned_time, таймзоны (std::chrono::current_zone(), locate_zone()) — полноценная работа с календарными датами и переводом времени между часовыми поясами без сторонних библиотек (ранее это требовало Howard Hinnant’s date library, которая фактически и стала основой стандарта).

Пример кода

#include <chrono>
#include <iostream>
#include <thread>

void measureDuration() {
    using Clock = std::chrono::steady_clock;

    auto start = Clock::now();
    std::this_thread::sleep_for(std::chrono::milliseconds(150));
    auto end = Clock::now();

    std::chrono::duration<double, std::milli> elapsed = end - start;
    std::cout << "Elapsed: " << elapsed.count() << " msn";

    // Явные преобразования между единицами -- типобезопасно
    auto elapsedSeconds = std::chrono::duration_cast<std::chrono::seconds>(elapsed);
    std::cout << "Rounded to seconds: " << elapsedSeconds.count() << " sn";
}

void durationArithmetic() {
    using namespace std::chrono_literals; // C++14: суффиксы 1s, 500ms, ...

    auto a = 2h;             // 2 часа
    auto b = 30min;          // 30 минут
    auto total = a + b;      // std::chrono::minutes -> общий тип

    std::cout << "Total: " << total.count() << " minutesn";

    auto inSeconds = std::chrono::duration_cast<std::chrono::seconds>(total);
    std::cout << "In seconds: " << inSeconds.count() << "n";
}

#if __cpp_lib_chrono >= 201907L
void calendarAndTimezones() {
    using namespace std::chrono;

    // Конкретная календарная дата (без времени суток)
    year_month_day date = 2026y / June / 22;
    std::cout << "Date: " << static_cast<int>(date.year()) << "-"
              << static_cast<unsigned>(date.month()) << "-"
              << static_cast<unsigned>(date.day()) << "n";

    // Текущий момент в системных часах
    auto now = system_clock::now();

    try {
        // Перевод текущего момента в конкретную таймзону
        zoned_time zonedMoscow{"Europe/Moscow", now};
        zoned_time zonedNewYork{"America/New_York", now};

        std::cout << "Moscow time:   " << zonedMoscow << "n";
        std::cout << "New York time: " << zonedNewYork << "n";
    } catch (const std::exception& e) {
        std::cout << "Timezone database not available: " << e.what() << "n";
    }
}
#endif

int main() {
    measureDuration();
    durationArithmetic();
#if __cpp_lib_chrono >= 201907L
    calendarAndTimezones();
#else
    std::cout << "Calendar/timezone API requires full C++20 chrono support "
                 "(check your standard library version).n";
#endif
    return 0;
}

Пояснения к коду

measureDuration использует steady_clock — это специально подобранные часы для измерения интервалов: они монотонны (не «прыгают» назад из-за корректировки системного времени или перехода на летнее время) в отличие от system_clock, который отражает календарное время и может скорректироваться. Вычитание двух time_point даёт duration, который явно приводится через duration_cast к нужной единице — компилятор не позволит просто «сложить секунды с миллисекундами» без явного преобразования, что исключает целый класс ошибок единиц измерения.

durationArithmetic демонстрирует литералы длительностей (2h, 30min) из std::chrono_literals (C++14) — они делают код, работающий с временем, читаемым почти как обычный текст, без магических чисел вида 1800 (секунд).

calendarAndTimezones — пример возможностей C++20: year_month_day позволяет конструировать календарные даты через выразительный синтаксис 2026y / June / 22, а zoned_time привязывает момент времени (system_clock::now()) к конкретной IANA-таймзоне («Europe/Moscow», «America/New_York»), автоматически учитывая переход на летнее время и смещения. Этот функционал требует наличия базы данных таймзон в реализации стандартной библиотеки (например, в libstdc++ для полной поддержки может требоваться системная tzdata или явная линковка с библиотекой, поставляющей tz-данные) — поэтому код оборачивает обращение к zoned_time в try/catch.

Подводные камни и советы

1. Используйте steady_clock для измерения длительностей (тестов производительности, таймаутов) и system_clock — для отображения «настенного времени» (даты, времени в UI). Использование system_clock для замера интервалов может дать отрицательную или скачкообразную длительность при корректировке системных часов. 2. Поддержка таймзон в C++20 (<chrono> с zoned_time, tzdb) не всегда полностью реализована во всех версиях компиляторов и стандартных библиотек на момент написания кода — проверяйте конкретную версию GCC/Clang/MSVC и при необходимости используйте библиотеку Howard Hinnant’s date (которая стала основой стандарта) как fallback. 3. duration_cast усекает (не округляет) при сужающем преобразованииduration_cast<seconds>(milliseconds(1999)) даст 1, а не 2. Если нужно округление, реализуйте его явно. 4. Не сравнивайте time_point разных часовsystem_clock::time_point и steady_clock::time_point несовместимы напрямую, у них разные эпохи и гарантии. 5. Для хранения времени в файлах/БД/сетевых протоколах сериализуйте в int64_t с явно зафиксированной единицей (например, миллисекунды с Unix-эпохи через time_point_cast и time_since_epoch()), а не сохраняйте внутреннее представление chrono-типов, которое может отличаться между платформами и компиляторами.