Временные зоны в C++: как перестать путаться в часовых поясах

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

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

Начать курс

Почему время — это боль (и как C++ может помочь)

Представьте: ваш сервер обрабатывает заказы из Нью-Йорка, Лондона и Токио. Вы записываете время создания заказа в UTC, а потом показываете пользователю: «Ваш заказ создан в 03:15». Для жителя Нью-Йорка это 22:15 предыдущего дня, для лондонца — 04:15 утра, а токиец вообще проснется от нотификации посреди ночи. Классическая проблема, которая превращает простой лог в головоломку.

В C++ долгое время не было нормальной стандартной работы с временными зонами. Разработчики либо мучились с time_t и struct tm, либо подключали тяжелые библиотеки вроде Boost.DateTime. Но с приходом C++20 ситуация кардинально изменилась. Библиотека std::chrono обзавелась полноценной поддержкой часовых поясов. И хотя многие компиляторы все еще догоняют стандарт, мы разберем, как уже сегодня писать чистый и переносимый код для работы с временными зонами в C++.

В этой статье я покажу вам, как на C++ превратить хаос из часовых поясов в стройную систему. Вы научитесь работать с std::chrono::tzdb, конвертировать UTC в локальное время, учитывать летнее время и избегать типичных ошибок.

Основы: что такое временная зона в C++ с точки зрения chrono

В C++20 временная зона — это не просто смещение в часах относительно UTC. Это полноценный объект, который знает об истории изменений времени в регионе: когда переводили стрелки, когда отменяли переход на летнее время и какие там действуют правила. Вся эта информация хранится в базе данных временных зон (tzdb), которая встроена в стандартную библиотеку.

Ключевые типы для работы с временными зонами

Чтобы не запутаться, запомните три основных типа:

  • std::chrono::time_zone — представляет конкретную временную зону (например, "America/New_York").
  • std::chrono::zoned_time — связка «временная зона + момент времени». Это самый удобный тип для хранения и отображения времени с учетом пояса.
  • std::chrono::time_zone_link — альтернативные названия зон (например, "US/Eastern" может быть ссылкой на "America/New_York").

Получаем список всех временных зон

Давайте начнем с простого: выведем все доступные временные зоны. Это полезно, когда вы даете пользователю выбрать свой часовой пояс.

#include <iostream>
#include <chrono>
#include <string>

int main() {
    // Получаем доступ к базе данных временных зон
    const auto& tzdb = std::chrono::get_tzdb();
    
    // Выводим названия всех зон
    std::cout << "Доступные временные зоны (" << tzdb.zones.size() << "):\n";
    for (const auto& zone : tzdb.zones) {
        std::cout << zone.name() << '\n';
    }
    return 0;
}

Обратите внимание: get_tzdb() возвращает константную ссылку. База данных загружается один раз при первом вызове и кэшируется. Это значит, что вы можете смело вызывать эту функцию в разных частях программы — оверхед будет только при первом обращении.

Преобразование времени между часовыми поясами в C++

Теперь перейдем к самому главному — конвертации. Допустим, у вас есть время в UTC, и вы хотите показать его пользователю в его локальном поясе. Или наоборот: пользователь вводит дату в своем времени, а сохранить нужно в UTC.

Из UTC в локальное время

Сценарий: сервер получил время события в UTC (например, 2024-12-01 15:30:00 UTC). Нужно показать его для зоны "Europe/Moscow".

#include <iostream>
#include <chrono>
#include <format> // C++20 для форматирования

int main() {
    using namespace std::chrono;
    
    // 1. Создаем момент времени в UTC
    // 2024-12-01 15:30:00 UTC
    auto tp = sys_days{December / 1 / 2024} + 15h + 30min;
    
    // 2. Получаем временную зону
    const auto* moscow_tz = get_tzdb().locate_zone("Europe/Moscow");
    
    // 3. Создаем zoned_time
    zoned_time moscow_time{moscow_tz, tp};
    
    // 4. Выводим (C++20 std::format)
    std::cout << std::format("Время в Москве: {:%Y-%m-%d %H:%M:%S %Z}\n", moscow_time);
    // Вывод: Время в Москве: 2024-12-01 18:30:00 MSK
    
    return 0;
}

Магия здесь в том, что zoned_time автоматически учитывает смещение зоны (в Москве зимой +3 часа) и даже летнее время, если оно есть. Обратите внимание на %Z в формате — он выводит аббревиатуру временной зоны (MSK, EST, PDT и т.д.).

Из локального времени в UTC

Обратная задача: пользователь вводит время в своем поясе, а вы хотите сохранить его в UTC для базы данных.

#include <iostream>
#include <chrono>
#include <format>

int main() {
    using namespace std::chrono;
    
    // Пользователь в Нью-Йорке ввел: 2024-07-04 14:00:00 (летнее время EDT)
    const auto* ny_tz = get_tzdb().locate_zone("America/New_York");
    
    // Создаем локальное время (без зоны)
    auto local_tp = local_days{July / 4 / 2024} + 14h + 0min;
    
    // Преобразуем в zoned_time
    zoned_time ny_time{ny_tz, local_tp};
    
    // Извлекаем время в UTC
    sys_time<seconds> utc_time = ny_time.get_sys_time();
    
    std::cout << std::format("Время в UTC: {:%Y-%m-%d %H:%M:%S}\n", utc_time);
    // Вывод: Время в UTC: 2024-07-04 18:00:00 (EDT = UTC-4)
    
    return 0;
}

Важный момент: если время неоднозначно (например, при переводе часов осенью, когда стрелки переводят назад и время повторяется), zoned_time выбросит исключение std::chrono::nonexistent_local_time или std::chrono::ambiguous_local_time. В реальном приложении это нужно обрабатывать.

Сложные случаи: летнее время и неоднозначности

Переход на летнее время — это голов

Блоги

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