C++20的 是否提供用于对本地时间进行运算、解析和存储的工具?
我经常听到这样的建议:
坚持在后端存储、日志记录和内部逻辑中全部使用UTC,被广泛认为是软件工程中的黄金标准。只有在严格需要用于人机输入或显示时,才转换到本地时区或偏移量。
在 <chrono>(C++20及更高版本)中,这仍然成立吗?如果不是,<chrono> 如何能够在时区中进行操控与计算,以及一个具体且有用的示例?
解决方案
是的。C++20引入了 本地时间运算,在很多出人意料的情形下都非常有用。本地时间运算可以与UTC运算进行对比,以便在夏令时变更时让代码更健壮。我并不是说本地时间运算一定比UTC运算好,它并不是。我只是说,有时它确实是完成这项工作的合适工具。
为便于理解,下面有一个示例,模拟一家受欢迎的烧烤店的夜间日常。该店早上7点开门,晚上10点关门(22:00)。关门时会发生两件事:
- 一名保安上班,确保烧烤在接下来的一天继续安全地烹调。
- 第二天肉类的烟熏工作开始。肉必须恰好烹调9小时;不能是8小时,也不能是10小时。它应在第二天早上7点开门时正好熟成。
因此,在正常的夜晚:
- 在晚上10点,保安到岗,肉被放入熏烤炉。
- 在第二天早上7点,保安下班,肉从熏烤炉中取出。
保安按工作小时计酬。肉必须恰好烹调9小时,并在7点准时完成。
一年中的两次在 America/New_York,政客通过在凌晨2点不连续地改变本地时间来干扰这一精心设计的日程。夜间日常必须适应这些夏令时的变化,以便保安按时得到每小时的报酬,肉也不会被煮过头或煮不足。
#include <chrono>
#include <iostream>
void
nightly_routine(std::chrono::local_days dt, std::chrono::time_zone const* tz)
{
using namespace std;
using namespace chrono;
// dt is the local midnight which starts a date
// security guard works from 10pm on the previous day to 7am local time
zoned_time sg_end{tz, dt + 7h}; // 7am local time
// Subtract 9 hours in local time to always get 10pm the previous evening
zoned_time sg_beg{tz, sg_end.get_local_time() - 9h};
// Actual number of hours worked, usually 9h, but not always
auto sg_hours = sg_end.get_sys_time() - sg_beg.get_sys_time();
// bbq must cook for 9h and be ready at 7am local time
zoned_time bbq_end{tz, dt + 7h}; // 7am local time
// Subtract 9 hours in system time to reliably get 9h of cooking time
// Usually this is 10pm the previous evening, but not always
zoned_time bbq_beg{tz, bbq_end.get_sys_time() - 9h};
// Actual number of hours cooked, always 9h
auto bbq_hours = bbq_end.get_sys_time() - bbq_beg.get_sys_time();
cout << "nightly routine for " << dt << " in " << tz->name() << "\n\n";
cout << "security guard begins at " << sg_beg << '\n';
cout << "security guard finishes at " << sg_end << '\n';
cout << "security guard works " << sg_hours/1h << " hours\n";
cout << '\n';
cout << "bbq begins cooking at " << bbq_beg << '\n';
cout << "bbq finishes cooking at " << bbq_end << '\n';
cout << "bbq cooks for " << bbq_hours/1h << " hours\n\n";
}
int
main()
{
using namespace std::chrono;
auto tz = locate_zone("America/New_York");
nightly_routine(local_days{January/1/2026}, tz);
nightly_routine(local_days{Sunday[2]/March/2026}, tz);
nightly_routine(local_days{July/1/2026}, tz);
nightly_routine(local_days{Sunday[1]/November/2026}, tz);
nightly_routine(local_days{December/1/2026}, tz);
}
- 本地时间运算 会在
sg_end构造函数中将本地午夜加上7小时,以得到本地时间的7点。若在这里使用 UTC运算,在出现凌晨2点的夏令时变更时就不会得到本地的7点。 - 本地时间运算 会在
sg_beg构造函数中在本地时间减去9小时,以得到保安到达的时间。这在前一晚的22:00本地时间始终成立,即使出现凌晨2点的夏令时变更也是如此。这也可以写成dt - 2h,但我想把这个本地时间的计算与用于找出放肉进熏烤炉的UTC计算做对比。 - UTC运算 用于获取保安工作的小时数。通常是9小时,但在这个时区也可能是8小时或10小时。
- 烧烤结束时间使用与保安结束时间完全相同的本地时间运算,以可靠地得到本地的7点。
- 烧烤的开始时间使用 UTC时间运算 减去9小时。对照这一点,
sg_beg使用的是 本地时间运算 的计算。与可能工作8、9或10小时的保安不同,肉必须恰好熏制9小时。因此,UTC运算现在成为合适的工具。 - 作为sanity check,肉被熏制的小时数在UTC时间中计算。这是一个不必要的计算,因为它始终是9 小时。
该程序的输出是:
nightly routine for 2026-01-01 in America/New_York
security guard begins at 2025-12-31 22:00:00 EST
security guard finishes at 2026-01-01 07:00:00 EST
security guard works 9 hours
bbq begins cooking at 2025-12-31 22:00:00 EST
bbq finishes cooking at 2026-01-01 07:00:00 EST
bbq cooks for 9 hours
nightly routine for 2026-03-08 in America/New_York
security guard begins at 2026-03-07 22:00:00 EST
security guard finishes at 2026-03-08 07:00:00 EDT
security guard works 8 hours
bbq begins cooking at 2026-03-07 21:00:00 EST
bbq finishes cooking at 2026-03-08 07:00:00 EDT
bbq cooks for 9 hours
nightly routine for 2026-07-01 in America/New_York
security guard begins at 2026-06-30 22:00:00 EDT
security guard finishes at 2026-07-01 07:00:00 EDT
security guard works 9 hours
bbq begins cooking at 2026-06-30 22:00:00 EDT
bbq finishes cooking at 2026-07-01 07:00:00 EDT
bbq cooks for 9 hours
nightly routine for 2026-11-01 in America/New_York
security guard begins at 2026-10-31 22:00:00 EDT
security guard finishes at 2026-11-01 07:00:00 EST
security guard works 10 hours
bbq begins cooking at 2026-10-31 23:00:00 EDT
bbq finishes cooking at 2026-11-01 07:00:00 EST
bbq cooks for 9 hours
nightly routine for 2026-12-01 in America/New_York
security guard begins at 2026-11-30 22:00:00 EST
security guard finishes at 2026-12-01 07:00:00 EST
security guard works 9 hours
bbq begins cooking at 2026-11-30 22:00:00 EST
bbq finishes cooking at 2026-12-01 07:00:00 EST
bbq cooks for 9 hours
可以看到,尽管肉的正常开始熏制时间是晚上10点,有时需要在晚上9点就放入熏烤炉,有时则晚到11点才放入。
这里的结论是,本地时间运算 与UTC运算一样有用。难点在于你对领域的理解要足够透彻,才能知道在什么情况下选择哪种工具才是对的。
本地时间的存储
RFC-9557 引入了本地时间及其时区名称的扩展存储格式,使得本地时间在格式化和解析(往返)时不会丢失信息,也不会产生歧义。
示例:
2022-07-08T00:14:07+02:00[Europe/Paris]
这里有一个SO问答,介绍如何使用C++20的 <chrono> 解析和格式化这类扩展的本地日期时间的ISO模式。
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。