CppCon 2021 学习:C++2O‘s <chrono> Calendars and Time Zones in MSVC
提供的内容是 C++20 <chrono> 中新增的**日历类型(Calendrical Types)**示例。下面我帮你逐步用理解和解释:
1⃣ 新增的类类型
C++20 <chrono> 不再只是时间点和持续时间(time_point / duration),还增加了专门表示“日历概念”的类型,比如:
| 类型 | 说明 |
|---|---|
chrono::day |
表示一个月中的某一天(1–31) |
chrono::month |
表示一年中的某个月(1–12) |
chrono::year |
表示年份 |
chrono::month_day |
组合:月+日 |
chrono::weekday |
星期几(Mon、Tue、…) |
chrono::weekday_indexed |
第几个星期几(如第四个星期四) |
chrono::year_month_day |
组合:年+月+日 |
chrono::year_month_day_last |
组合:年+月+该月最后一天 |
chrono::year_month_weekday_last |
组合:年+月+该月最后一个指定星期几 |
可以看到,这些类型都是为日历运算和表示日期而设计的,比以前的 time_point 更直观。
2⃣ 示例解析:年份和月份运算
#include <chrono>
#include <iostream>
using namespace std::chrono;
int main() {
year y{2021};
std::cout << y << "\n"; // 输出年份
month m{October};
auto result = m + months{3}; // 月份加上3个月
std::cout << result << "\n"; // 输出结果
}
输出
2021
Jan
理解:
year y{2021};→ 构造年份对象2021。month m{October};→ 构造月份对象October(10 月)。m + months{3}→ 月份加法:October + 3 个月 → 1 月(跨年了!)。
所以它直接支持跨年的月份计算,而不需要手动处理。
3⃣ 示例解析:星期运算
#include <chrono>
#include <iostream>
using namespace std::chrono;
int main() {
weekday wd{Thursday}; // 星期四
auto result = wd + days{4}; // 星期加上4天
std::cout << result << "\n"; // 输出结果
weekday sun1{0};
weekday sun2{7};
std::cout << sun1 << "\t" << sun2 << "\n"; // 输出两种表示的星期天
weekday_indexed wdi{wd, 4}; // 第四个星期四
std::cout << wdi << "\n";
}
输出
Mon
Sun Sun
Thu[4]
理解:
weekday wd{Thursday}; wd + days{4}→ 星期四加 4 天 → 星期一。weekday sun1{0}; weekday sun2{7};→ C++20 允许 0 或 7 都表示星期天。weekday_indexed wdi{wd, 4};→ 第四个星期四,用Thu[4]表示。
这里
weekday_indexed非常方便做“每月第 N 个星期几”这种运算,比如美国感恩节(每年 11 月第四个星期四)。
总结
- C++20
<chrono>增加了丰富的日历类型,可以表示年、月、日、星期等组合。 - 支持日期/星期的算术运算(加减天、月等),并能自动处理跨年、跨月。
weekday_indexed等类型让处理“每月第几个星期几”变得简单。
这段代码展示的是 C++20 <chrono> 中的“复合日历类型(Compound Calendrical Types)” 的用法。我帮你逐行用解释:
1⃣ 代码解析
#include <chrono>
#include <iostream>
using namespace std::chrono;
int main() {
year this_year{2021};
year last_year{2020};
year_month_day ymd{this_year, October, day{28}};
std::cout << ymd << "\n";
理解:
year_month_day是一个复合类型,表示 年-月-日。- 构造时传入:年份
this_year(2021)、月份October(10 月)、日期28。 - 输出结果:
2021-10-28
month_weekday mwd{November, Thursday[4]};
std::cout << mwd << "\n";
理解:
month_weekday表示 某月的第几个星期几。November, Thursday[4]→ 11 月的第四个星期四(感恩节就是这个)。- 输出结果:
Nov/Thu[4]
month_day_last mdlast{February};
year_month_day_last ymdlast_leap{last_year, mdlast};
year_month_day_last ymdlast_noleap{this_year, mdlast};
std::cout << ymdlast_leap << "\t" << ymdlast_leap.day() << "\n";
std::cout << ymdlast_noleap << "\t" << ymdlast_noleap.day() << "\n";
理解:
month_day_last→ 表示某个月的最后一天(不指定年份)。year_month_day_last→ 表示 指定年份 + 指定月最后一天。last_year = 2020是闰年 → 2 月最后一天是 29 日this_year = 2021不是闰年 → 2 月最后一天是 28 日
输出结果:
2020/Feb/last 29
2021/Feb/last 28
2⃣ 总结
| 类型 | 含义 | 示例 |
|---|---|---|
year_month_day |
年 + 月 + 日 | 2021-10-28 |
month_weekday |
月 + 第 N 个星期几 | Nov/Thu[4] |
month_day_last |
月 + 最后一天 | February → last day |
year_month_day_last |
年 + 月 + 最后一天 | 2020/Feb/last → 29 日(闰年) |
| 特点: |
- 可以直接表达“复合日期”,不需要自己计算天数或闰年。
- 自动处理闰年问题,
ymdlast_leap.day()返回 29,非闰年返回 28。 month_weekday非常适合表示节日,如“每年 11 月第四个星期四”。
如果你愿意,我可以帮你画一张 “复合日历类型可视化图”,显示year_month_day、month_weekday、year_month_day_last的结构和关系,会很直观。
整理一个完整的、可直接编译运行的 C++20 示例,涵盖你提到的“简单日历类型”和“复合日历类型”的所有例子,并且包含注释:
#include <chrono>
#include <iostream>
using namespace std::chrono;
int main() {
// ============================
// 简单日历类型示例
// ============================
std::cout << "=== 简单日历类型 ===\n";
// 年份
year y{2021};
std::cout << "年份: " << y << "\n";
// 月份
month m{October};
auto result_month = m + months{3}; // 10月 + 3个月 → 次年1月
std::cout << "月份加3个月: " << result_month << "\n";
// 星期
weekday wd{Thursday};
auto result_weekday = wd + days{4}; // 星期四 + 4天 → 星期一
std::cout << "星期四 + 4天: " << result_weekday << "\n";
// 星期天的两种表示
weekday sun1{0};
weekday sun2{7};
std::cout << "星期天表示: " << sun1 << "\t" << sun2 << "\n";
// 第四个星期四
weekday_indexed wdi{wd, 4};
std::cout << "第四个星期四: " << wdi << "\n\n";
// ============================
// 复合日历类型示例
// ============================
std::cout << "=== 复合日历类型 ===\n";
year this_year{2021};
year last_year{2020};
// 年月日
year_month_day ymd{this_year, October, day{28}};
std::cout << "年月日: " << ymd << "\n";
// 月 + 第N个星期几
month_weekday mwd{November, Thursday[4]}; // 11月第四个星期四
std::cout << "月+第N个星期几: " << mwd << "\n";
// 月最后一天
month_day_last mdlast{February};
// 年+月最后一天
year_month_day_last ymdlast_leap{last_year, mdlast};
year_month_day_last ymdlast_noleap{this_year, mdlast};
std::cout << "闰年2月最后一天: " << ymdlast_leap
<< "\t天数: " << ymdlast_leap.day() << "\n";
std::cout << "非闰年2月最后一天: " << ymdlast_noleap
<< "\t天数: " << ymdlast_noleap.day() << "\n";
return 0;
}
输出示例
=== 简单日历类型 ===
年份: 2021
月份加3个月: Jan
星期四 + 4天: Mon
星期天表示: Sun Sun
第四个星期四: Thu[4]
=== 复合日历类型 ===
年月日: 2021-10-28
月+第N个星期几: Nov/Thu[4]
闰年2月最后一天: 2020/Feb/last 天数: 29
非闰年2月最后一天: 2021/Feb/last 天数: 28
这段代码完整演示了 C++20 日历类型的基本用法:
year/month/weekday等基本类型year_month_day/month_weekday/year_month_day_last等复合类型- 自动处理闰年、跨年、月份运算和“每月第 N 个星期几”的情况
C++20 <chrono> 新的字面量(literals)和 / 运算符的用法,让创建日期和复合日期类型更加直观。下面我用逐行解释:
1⃣ 新增字面量和运算符
28d→std::chrono::day{28}2021y→std::chrono::year{2021}/运算符可以把year、month、day组合成 复合类型:year / month / day→year_month_dayyear / (month + months{1}) / last→year_month_day_lastmonth / day→month_daymonth / weekday[N]→month_weekday
这样就可以用类似“日期文字”的形式写日期,非常直观。
2⃣ 代码解析
year_month_day ymd1{October/28d/2021y};
year_month_day ymd2{2021y/October/28d};
std::cout << ymd1 << "\t" << ymd2 << "\n";
理解:
October/28d/2021y→ 先写月/日/年2021y/October/28d→ 写年/月/日- 两者效果相同,输出:
2021-10-28 2021-10-28
year_month_day_last ymdlast{2021y/(October + months{1})/last};
std::cout << ymdlast << "\n";
理解:
(October + months{1})→ 10 月 + 1 月 → 11 月(自动处理跨年情况)/last→ 表示该月最后一天- 类型是
year_month_day_last - 输出:
2021/Nov/last
month_day md{October/28};
std::cout << md << "\n";
理解:
month_day表示 月+日- 输出:
Oct/28
month_weekday mwd{March/Wednesday[2]};
std::cout << mwd << "\n";
理解:
Wednesday[2]→ 第二个星期三month_weekday表示 月+第 N 个星期几- 输出:
Mar/Wed[2]
3⃣ 总结
| 表达式 | 类型 | 含义 | 示例输出 |
|---|---|---|---|
year/month/day 或 month/day/year |
year_month_day |
年月日 | 2021-10-28 |
year/(month+months{N})/last |
year_month_day_last |
年 + 月(加偏移) + 月最后一天 | 2021/Nov/last |
month/day |
month_day |
月+日 | Oct/28 |
month/weekday[N] |
month_weekday |
月 + 第 N 个星期几 | Mar/Wed[2] |
| 特点: |
/运算符和字面量让日期构造像自然语言一样直观。- 自动处理跨年、月份偏移。
- 支持多种组合类型,方便表示节日或每月固定模式日期。
如果你需要,我可以帮你画一张chrono新字面量和/运算符构造日期的示意图,把各种类型之间的组合关系直观地展示出来。
一个完整、可编译运行的 C++20 示例,并加上注释,涵盖你提到的使用 新字面量 (28d, 2021y) 和 / 运算符 的日历类型用法:
#include <chrono>
#include <iostream>
using namespace std::chrono;
int main() {
std::cout << "=== 使用新字面量和 / 运算符构造日期 ===\n";
// ----------------------------
// year_month_day 示例
// ----------------------------
year_month_day ymd1{October/28d/2021y}; // 月/日/年
year_month_day ymd2{2021y/October/28d}; // 年/月/日
std::cout << "ymd1: " << ymd1 << "\tymd2: " << ymd2 << "\n";
// ----------------------------
// year_month_day_last 示例
// ----------------------------
year_month_day_last ymdlast{2021y/(October + months{1})/last}; // 11月最后一天
std::cout << "ymdlast: " << ymdlast << "\n";
// ----------------------------
// month_day 示例
// ----------------------------
month_day md{October/28}; // 月 + 日
std::cout << "month_day: " << md << "\n";
// ----------------------------
// month_weekday 示例
// ----------------------------
month_weekday mwd{March/Wednesday[2]}; // 3月第二个星期三
std::cout << "month_weekday: " << mwd << "\n";
return 0;
}
输出示例
=== 使用新字面量和 / 运算符构造日期 ===
ymd1: 2021-10-28 ymd2: 2021-10-28
ymdlast: 2021/Nov/last
month_day: Oct/28
month_weekday: Mar/Wed[2]
理解
28d、2021y等字面量让数字直接表示日期元素。/运算符用于组合:year / month / day→year_month_dayyear / (month + months{N}) / last→year_month_day_lastmonth / day→month_daymonth / weekday[N]→month_weekday
- 自动处理跨年、月份加法,无需手动计算。
- 非常适合表示节日或固定模式日期,比如“每年 3 月第二个星期三”。
这个例子展示了 C++20 <chrono> **计算每月固定星期(比如第二个星期二)**的做法。下面我帮你逐行理解:
1⃣ 代码解析
std::cout << "Patch Tuesdays in 2021:\n";
year target_year{2021};
- 打印标题
"Patch Tuesdays in 2021" - 创建年份对象
target_year= 2021
for (int mo = 1; mo <= 12; ++mo) {
// the second Tuesday of each month
year_month_weekday patch_tues{mo/Tuesday[2]/target_year};
for循环从 1 到 12,表示 每个月Tuesday[2]→ 每月 第二个星期二mo/Tuesday[2]/target_year→/运算符组合成year_month_weekday类型year_month_weekday可以表示 “某年某月的第 N 个星期几”
year_month_day ymd{sys_days{patch_tues}};
std::cout << ymd.month() << " " << ymd.day() << "\n";
sys_days{patch_tues}→ 将year_month_weekday转为 绝对日历日期(year_month_day)- 输出格式:
月份 月份号,例如Jan 12
2⃣ 输出理解
Patch Tuesdays in 2021:
Jan 12
Feb 09
Mar 09
Apr 13
May 11
Jun 08
Jul 13
Aug 10
Sep 14
Oct 12
Nov 09
Dec 14
- 每行表示 2021 年每月 第二个星期二
- 结果与日历对应,例如:
- 1 月第二个星期二 → 1 月 12 日
- 2 月第二个星期二 → 2 月 9 日
- …
3⃣ 总结
year_month_weekday可以表示 每年每月固定的第 N 个星期几,非常适合节日、会议或软件补丁日等规律日期计算。- 使用
/运算符可以方便地组合年份、月份、星期几和索引。 sys_days{...}可以把year_month_weekday转为具体的year_month_day,方便输出或进一步运算。
如果你愿意,我可以帮你画一张**“year_month_weekday → sys_days → year_month_day”流程图**,直观展示从“第二个星期二”到具体日期的转换。
整理成一个完整、可编译运行的 C++20 示例,计算 2021 年每个月的第二个星期二(Patch Tuesday),并附注释:
#include <chrono>
#include <iostream>
using namespace std::chrono;
int main() {
std::cout << "=== Patch Tuesdays in 2021 ===\n";
year target_year{2021}; // 目标年份
// 循环每个月
for (int mo = 1; mo <= 12; ++mo) {
// mo/Tuesday[2]/target_year 表示每月第二个星期二
year_month_weekday patch_tues{mo/Tuesday[2]/target_year};
// 将 year_month_weekday 转换为具体日期 year_month_day
year_month_day ymd{sys_days{patch_tues}};
// 输出月份和日期
std::cout << ymd.month() << " " << ymd.day() << "\n";
}
return 0;
}
输出示例
=== Patch Tuesdays in 2021 ===
Jan 12
Feb 09
Mar 09
Apr 13
May 11
Jun 08
Jul 13
Aug 10
Sep 14
Oct 12
Nov 09
Dec 14
理解
year_month_weekday→ 表示 某年某月的第 N 个星期几/运算符组合 →月份 / 星期[N] / 年sys_days{...}→ 转为具体日期(year_month_day),可以直接输出或用于计算- 适合表示每月固定模式的日期,比如节日或软件补丁日
我可以帮你画一张**“year_month_weekday → sys_days → year_month_day 流程图”**,直观展示从“第二个星期二”到具体日期的转换。
C++20 <chrono> 中新增日历类型(calendrical types)的设计动机,主要从 类型安全(Type Safety) 和 抽象(Abstraction) 两个角度解释。我帮你逐段理解:
1⃣ 为什么需要日历类型?——类型安全(Type Safety)
示例:
year_month_day ymd1{October/28d/2021y};
year_month_day ymd2{2021y/October/28d};
如果不用专门的类型,而直接用 unsigned int:
year_month_day ymd1{October/28/2021};
year_month_day ymd2{2021/October/28};
问题:
- 很容易搞混 年、月、日的顺序
- 比如不小心把
2021当作日,28当作年,逻辑就完全错了 - 使用
day、month、year类型,编译器会强制类型检查,确保 值的含义正确 - 这就是 类型安全 的好处:通过类型区分概念,而不是单纯依赖整数
小结: - “简单”类型如
day、month、year虽然很直观,但它们提升了 编译时检查能力 - 避免了混淆和逻辑错误
2⃣ 为什么需要日历类型?——抽象(Abstraction)
- 你完全可以用
unsigned int或底层数据表示年、月、日 - 但是新类型提供了有价值的抽象层,好处包括:
- 无需关心内部实现
- 例如:星期(weekday)、月中的第 N 个星期几(weekday_indexed)
- 自动处理 环绕运算(wrap-around)、格式化、跨年等细节
- 你只需使用类型提供的接口即可,不必自己计算
- 接口直观
- 例如
October/28d/2021y语法很清晰 - 可以直接操作日期,而不用自己写复杂逻辑
- 例如
- 常量化
- 很多类型最终都可以看作 常量,但用类型包装让代码可读性更高、安全性更好
小结:
- 很多类型最终都可以看作 常量,但用类型包装让代码可读性更高、安全性更好
- 新类型不仅是类型安全,也是抽象层次的提升
- 使用这些类型,客户端可以专注“用日期”,而不必关心内部如何计算、跨月或格式化
3⃣ 总结
| 设计动机 | 解释 |
|---|---|
| 类型安全(Type Safety) | 区分年/月/日概念,防止数字混淆,编译时检查 |
| 抽象(Abstraction) | 封装环绕计算、格式化等细节,让客户端只关心使用,接口直观 |
简单来说,日历类型既是 防错工具,也是 便捷接口,让日期操作安全且可读。
如果你愿意,我可以画一张类型安全 + 抽象的示意图,直观展示day/month/year和weekday/weekday_indexed如何封装逻辑,让你更容易理解设计动机。
你提供的内容讲的是 C++20 <chrono> 中的 Clock(时钟)概念,我帮你逐条理解:
1⃣ Clock 的定义
对于一个类型
T,要被认定为 Clock,必须满足以下条件:
① 必须存在的类型成员(type members)
| 成员 | 说明 |
|---|---|
T::rep |
表示计时器的底层表示类型(通常是整数或浮点数) |
T::period |
表示时间刻度的单位(如秒、毫秒、微秒) |
T::duration |
表示时间长度的类型(通常是 std::chrono::duration<rep, period>) |
T::time_point |
表示时刻的类型(通常是 std::chrono::time_point<T>) |
② 必须存在的表达式(表达式成员)
| 表达式 | 说明 |
|---|---|
T::is_steady |
constexpr bool,表示时钟是否稳定(steady clock),即时间不会倒退 |
T::now() |
返回当前时刻(time_point 类型) |
③ 特性检测
- 新增 trait:
is_clock- 用于检测一个类型是否符合 Clock 的要求
- 用法示例:
static_assert(is_clock_v<MyClock>);
2⃣ 示例解析
#include <chrono>
#include <ratio>
using namespace std::chrono;
class MyClock {
public:
using rep = long long; // 底层计数类型
using period = std::milli; // 毫秒为单位
using duration = milliseconds; // 持续时间类型
using time_point = time_point<MyClock>; // 时刻类型
static constexpr bool is_steady = false; // 非稳定时钟
static time_point now() {} // 获取当前时间
};
// 静态检查是否符合 Clock
static_assert(is_clock_v<MyClock>);
理解
rep = long long→ 计时器内部用long long表示时间点period = std::milli→ 时钟精度为 毫秒duration = milliseconds→ 表示持续时间time_point = time_point<MyClock>→ 表示具体时刻is_steady = false→ 不是稳定时钟(系统时间可能会调整)now()→ 返回当前时刻(这里空函数只是示意)static_assert(is_clock_v<MyClock>)→ 编译时检查,确认MyClock满足 Clock 要求
3⃣ 总结
- Clock 类型 = 能表示时间点和持续时间的对象
- 必须有
rep/period/duration/time_point类型成员 - 必须有
is_steady和now()表达式 - 可以用
is_clocktrait 静态检测类型是否符合 Clock - 这样做的好处:让自定义时钟可以与
<chrono>的各种算法和类型兼容
一个 完整可编译的 C++20 示例,展示如何自定义一个 Clock,并用 is_clock 检查,同时输出时间点:
#include <chrono>
#include <iostream>
#include <ratio>
using namespace std::chrono;
// 自定义 Clock
class MyClock {
public:
using rep = long long; // 底层计数类型
using period = std::milli; // 毫秒为单位
using duration = std::chrono::duration<rep, period>; // 持续时间类型
using time_point = std::chrono::time_point<MyClock>; // 时刻类型
static constexpr bool is_steady = false; // 是否稳定时钟
// 返回当前时间(示例用 system_clock 时间模拟)
static time_point now() {
return time_point(duration(std::chrono::duration_cast<duration>(
std::chrono::system_clock::now().time_since_epoch()
)));
}
};
int main() {
// 编译时检查 MyClock 是否符合 Clock 要求
static_assert(std::chrono::is_clock_v<MyClock>, "MyClock is not a valid clock");
// 获取当前时间点
MyClock::time_point tp = MyClock::now();
std::cout << "Current time since epoch (ms): "
<< tp.time_since_epoch().count() << "\n";
std::cout << "Is steady? " << std::boolalpha << MyClock::is_steady << "\n";
return 0;
}
运行输出示例
Current time since epoch (ms): 1705678901234
Is steady? false
理解
- 类型成员:
rep→ 底层计数类型period→ 时间刻度(毫秒)duration→ 表示持续时间time_point→ 表示具体时刻
- 表达式成员:
is_steady→ 是否稳定时钟now()→ 返回当前时间点
- 静态检查:
static_assert(std::chrono::is_clock_v<MyClock>);- 编译器会检查
MyClock是否符合 Clock 概念
- 编译器会检查
- 用途:
- 可以将自定义时钟与
<chrono>的算法(如time_point算术、计时器)兼容 - 提供类型安全和抽象,避免直接使用整数表示时间点
- 可以将自定义时钟与
这段内容讲的是 C++20 <chrono> 中新增和已有的时钟类型,我帮你理解如下:
1⃣ 现有(C++20 之前的)时钟
| 时钟 | 说明 |
|---|---|
system_clock |
系统时间,通常对应当前日历时间(UTC 或本地时间可转换) |
file_clock |
用于文件系统时间戳(文件创建/修改时间) |
注意:
file_clock在某些实现中可能是可选或实验性的
2⃣ C++20 新增时钟
| 时钟 | 说明 |
|---|---|
utc_clock |
UTC 时间(协调世界时),提供跨系统统一时间标准 |
tai_clock |
国际原子时(TAI),无闰秒,连续性好,适合精确测量 |
gps_clock |
GPS 时间,跟踪卫星导航系统时间,适合定位和导航应用 |
file_clock |
仍然可用,用于文件系统时间戳 |
3⃣ 总结
- C++20 扩展了
<chrono>的时钟体系,增加了对科学、导航和标准化时间的支持 - 新增时钟类型可以直接与
time_point、duration、chrono算法结合使用 - 旧时钟(
system_clock)仍然可用,但精度和连续性可能不如 TAI 或 GPS 时钟
如果你愿意,我可以画一张 C++20 chrono 时钟家族图,把 旧时钟 vs 新时钟 和它们用途直观地展示出来。
为什么 C++20 <chrono> 有这么多不同的时钟类型,我帮你逐条理解:
1⃣ 不同时钟的比较表
| 时钟 | Epoch(纪元起点) | 描述 | 是否追踪闰秒 |
|---|---|---|---|
system_clock |
1970-01-01 00:00:00 | 系统时间,一般跟 UTC 或 GMT 对齐 | 否 |
utc_clock |
1970-01-01 00:00:00 | 协调世界时(UTC),精确追踪闰秒 | 是 |
tai_clock |
1958-01-01 00:00:00 | 国际原子时(TAI),通过多个原子钟加权平均获得 | 否 |
gps_clock |
1980-01-06 00:00:00(1980 年第一个周日) | GPS 卫星原子钟维护的时间 | 否 |
file_clock |
不固定 | 文件系统时间戳的基础时间,POSIX 通常 1970-01-01,Windows 通常 1601-01-01 | 不确定 |
2⃣ 理解
- Epoch(纪元)
- 每个时钟都有自己的零点(起始时间),用于计算
time_point - 例如
system_clock和utc_clock通常从 Unix 纪元 1970-01-01 00:00:00 开始
- 每个时钟都有自己的零点(起始时间),用于计算
- 用途差异
system_clock→ 一般用于当前时间、日志、普通应用utc_clock→ 精确的 UTC 时间,考虑闰秒,适合科学、金融等需要精确 UTC 的应用tai_clock→ 原子时,不受闰秒影响,适合连续性要求高的计时gps_clock→ GPS 系统时间,适合导航、定位、卫星通信file_clock→ 文件系统时间戳,用于文件创建/修改时间
- 闰秒(Leap seconds)
- 闰秒会调整 UTC,保证日历时间与地球自转一致
- 只有
utc_clock会追踪闰秒 - 其他时钟(
system_clock、tai_clock、gps_clock)通常不会调整
3⃣ 总结
- 不同的时钟类型是为了满足不同场景的需求:
- 系统时间 vs 原子时间
- 是否追踪闰秒
- 卫星导航 vs 文件系统时间
- 通过
<chrono>提供多种时钟,C++ 可以统一处理各种时间计算,同时保证类型安全和抽象
C++20 <chrono> 中的 local_t 类型,我帮你理解如下:
1⃣ local_t 是“伪时钟”(Pseudo Clock)
local_t行为类似时钟,但不是真正的 Clock- 例如:
is_clock_v<local_t> // 返回 false
- 原因:它没有
now()成员函数,因此不符合 Clock 的要求
2⃣ 用途
local_t用来表示 本地时间,但尚未指定具体时区- 它不是用来获取当前时间的,而是用来表示 某个本地时间的概念
3⃣ 未来内容
- 该类型与 时区(time zones)相关的功能 密切关联
- 后续会讲到如何将
local_t与具体时区结合,得到可用的本地时间
总结
local_t≠ Clock,只是伪时钟- 不支持
now(),所以不能用作时间源 - 用于表示本地时间概念,等待时区定义
- 主要目的是为后续时区处理提供类型抽象
如果你愿意,我可以画一张local_t与 Clock 的对比图,直观显示为什么它不是 Clock,以及它与时区的关系。
提供的内容讲的是 C++20 <chrono> 中的 clock_cast,我帮你理解如下:
1⃣ clock_cast 是什么?
clock_cast可以 将一个时钟的 time_point 转换为另一个时钟的等效 time_point- 例如,把
system_clock::time_point转换为utc_clock::time_point或tai_clock::time_point
2⃣ 转换考虑因素
在转换过程中,clock_cast 会自动处理:
- 时钟纪元(Epoch)差异
- 不同时钟可能有不同的起点时间,例如:
system_clock→ 1970-01-01tai_clock→ 1958-01-01
clock_cast会根据纪元调整偏移
- 不同时钟可能有不同的起点时间,例如:
- 闰秒(Leap seconds)处理
- 如果源时钟或目标时钟追踪闰秒(如
utc_clock),clock_cast会正确计算
- 如果源时钟或目标时钟追踪闰秒(如
3⃣ 内部实现机制
- 内部使用
clock_time_conversions - 依赖函数:
to_utc→ 转换为 UTCfrom_utc→ 从 UTC 转换回目标时钟to_sys/from_sys→ 与系统时钟之间转换
- 这些函数保证转换正确、精确,同时考虑闰秒和纪元偏移
总结
- 用途:在不同时钟之间安全、精确地转换时间点
- 优势:
- 自动处理纪元差异
- 自动处理闰秒
- 保持时间点等效
- 实现原理:先转到 UTC 或系统时间,再转到目标时钟
如果你愿意,我可以画一张clock_cast转换流程图,直观展示从源时钟到目标时钟的转换路径及内部函数调用。
提供的内容讲的是 闰秒(Leap Seconds) 的概念和历史记录,我帮你理解如下:
1⃣ 闰秒是什么?
- 定义:闰秒是一种 在协调世界时(UTC)中偶尔加入或删除的一秒时间调整
- 目的:弥补精确原子时间与观测太阳时间之间的差异
- 因为地球自转速度不是完全恒定,太阳时间会慢慢偏离原子时
- 通过闰秒,UTC 能保持与地球自转时间大致同步
3⃣ 闰秒的类型
- 正闰秒(Positive leap second)
- 在 UTC 时钟上增加一秒
- 时间显示可能为:58, 59, 60, 00
- 这是历史上唯一发生过的闰秒类型
- 负闰秒(Negative leap second)
- 从 UTC 时钟中删除一秒
- 时间显示可能为:57, 58, 00(跳过 59 秒)
- 历史上尚未发生过,但理论上可能发生
4⃣ 总结
- 闰秒是为了保持 UTC 与地球自转一致
- 正闰秒:加一秒(历史上出现过)
- 负闰秒:减一秒(理论上可能出现)
- C++20 的
<chrono>对 UTC 时钟的实现需要考虑闰秒,以保证时间计算准确
如果你愿意,我可以画一张 闰秒时间轴示意图,直观显示正闰秒如何插入 UTC 秒的序列中(比如 58, 59, 60, 00)。
你提供的内容讲的是 clock_cast 在 C++20 <chrono> 中的使用示例,我帮你理解如下:
1⃣ 示例说明
tai_time tai_now = tai_clock::now();
std::cout << tai_now << "\n";
utc_time utc_now = clock_cast<utc_clock>(tai_now);
std::cout << utc_now << "\n";
- 步骤:
- 获取当前 TAI 时间 (
tai_clock::now()) - 使用
clock_cast将 TAI 时间转换为 UTC 时间
- 获取当前 TAI 时间 (
- 输出示例:
2021-10-19 01:13:06.2810034 // TAI 时间
2021-10-19 01:12:29.2810034 // 转换后的 UTC 时间
2⃣ 为什么会有 37 秒差异?
(1) 时钟纪元(Epoch)差异
tai_clock的纪元:1958-01-01 00:00:00utc_clock的纪元:1970-01-01 00:00:00- 因此 初始差异约 10 秒
(2) 闰秒(Leap seconds)
utc_clock会追踪闰秒tai_clock不追踪闰秒- 到目前为止,已经发生了 27 个闰秒
(3) 总差异
- 初始差异 + 闰秒 = 10s + 27s = 37s
- 所以转换后 UTC 时间比 TAI 时间早 37 秒
3⃣ 总结
clock_cast可以在不同类型的时钟之间安全转换- 转换过程中会自动考虑:
- 纪元差异(Epoch offset)
- 是否追踪闰秒(Leap seconds)
- 在本例中,TAI → UTC 转换时,出现了 37 秒的差异
- 这种差异是正常且可预期的,因为 TAI 和 UTC 定义不同
如果你愿意,我可以帮你画一张 TAI 与 UTC 时间差异示意图,直观显示初始差异 + 闰秒的叠加。
一个 完整可编译的 C++20 示例,展示 TAI 时间转换到 UTC 时间,使用 clock_cast 并打印输出:
#include <chrono>
#include <iostream>
#include <chrono> // C++20 for formatted output
using namespace std::chrono;
int main() {
// 获取当前 TAI 时间
tai_time tai_now = tai_clock::now();
std::cout << "TAI time: " << tai_now << "\n";
// 将 TAI 时间转换为 UTC 时间
utc_time utc_now = clock_cast<utc_clock>(tai_now);
std::cout << "UTC time: " << utc_now << "\n";
return 0;
}
说明
tai_clock::now()→ 获取当前 国际原子时(TAI)clock_cast<utc_clock>(tai_now)→ 将 TAI 时间转换为 协调世界时(UTC)- 输出中可能会看到 37 秒差异,原因:
- 纪元差异:TAI 从 1958-01-01 开始,UTC 从 1970-01-01 开始 → 初始 10 秒差
- 闰秒:UTC 会追踪闰秒,目前已经发生 27 个 → 总差异 37 秒
示例输出(示意)
TAI time: 2025-09-19 14:09:42.334617796
UTC time: 2025-09-19 14:09:05.334617796
特点:
- 自动考虑闰秒和纪元偏移
- 使用 C++20
<chrono>新增时钟类型和clock_cast - 可直接进行时钟类型安全的时间转换
内容讲的是 MSVC(Microsoft Visual C++)和 Windows 对闰秒(Leap Seconds)的处理历史和实现方式,我帮你理解如下:
1⃣ Windows 闰秒历史(复杂情况)
- 早期 Windows 系统:
- 不单独记录闰秒
- 当系统时间同步时,如果发现时间落后 1 秒,会在同步时进行调整
- 也就是说,闰秒是 隐式调整,没有专门的记录
- Windows 10 2018 年 10 月更新 & Windows Server 2019:
- 系统开始 追踪闰秒
- 但只从 2018 年起,之前的闰秒仍未记录
2⃣ MSVC 的实现方式
- 获取闰秒数据:
- 2018 年以后的闰秒:
- 从 Windows 注册表读取
SYSTEM\CurrentControlSet\Control\LeapSecondInformation - 2018 年以前的闰秒:
- 使用 静态 constexpr 表(在库中维护)
- 2018 年以后的闰秒:
- 限制:
- 旧 Windows(2018 年前版本)无法自动检测未来的闰秒
- 解决方案:定期更新静态表,库更新后即可检测较新闰秒
3⃣ 参考来源
- 微软官方文章:
- Leap Seconds for the IT Pro: What you need to know - Microsoft Tech Community
- Leap Seconds for the AppDev: What you should know - by Daniel Havey, Microsoft Tech Community
4⃣ 总结
- Windows 以前对闰秒处理不完整,现在逐步支持(从 2018 起)
- MSVC 库实现:
- 新闰秒 → 从注册表读
- 老闰秒 → 静态表
- 旧 OS 需要定期更新库才能获取新闰秒
- 因为闰秒出现频率低,所以这种静态表更新方式可行
C++20 <chrono> 中的时区(Time Zones)支持,我帮你逐条理解如下:
1⃣ 新增的时区相关类型
C++20 <chrono> 提供了对 IANA 时区数据库 的访问接口,需要新增以下类型:
| 类型 | 说明 |
|---|---|
tzdb |
时区数据库,包含所有时区信息 |
tzdb_list |
所有可用时区的列表 |
time_zone |
表示某个具体时区及其所有时间转换规则 |
zoned_time |
表示时区与时间点的组合,得到该时区的具体时间 |
time_zone_link |
时区别名(例如 “US/Eastern” → “America/New_York”) |
ambiguous_local_time |
异常类型,当本地时间模糊(重复)时抛出 |
nonexistent_local_time |
异常类型,当本地时间不存在(跳过)时抛出 |
2⃣ time_zone
- 表示 某个地理区域的时区(如
America/New_York) - 存储:
- 时区名称
- UTC 偏移
- 是否处于夏令时(DST)
- 历史与未来时间转换规则
3⃣ zoned_time
- 表示时区 + 时间点的组合
- 可以:
- 生成对应的 本地时间(local_time)
- 生成对应的 系统时间(sys_time)
4⃣ sys_time 与 local_time 转换
template<class Duration>
using sys_time = time_point<system_clock, Duration>;
template<class Duration>
using local_time = time_point<local_t, Duration>;
- sys_time → local_time 或 local_time → sys_time 的转换通常很简单
- 但在夏令时转换时可能出现复杂情况
5⃣ 夏令时转换异常
① ambiguous_local_time
- 出现场景:夏令时结束时重复的一小时
- 该小时在本地时间上发生两次
- 如果转换时没有指定选择早或晚(
choose::earliest/choose::latest),会抛出 ambiguous_local_time 异常
② nonexistent_local_time
- 出现场景:夏令时开始时跳过的一小时
- 该时间段不存在对应的系统时间
- 转换时会抛出 nonexistent_local_time 异常
总结
- C++20
<chrono>提供了 完整的时区支持,包括 IANA 数据库访问 - 新增类型:
time_zone:表示时区zoned_time:表示时区 + 时间点- 异常类型:
ambiguous_local_time/nonexistent_local_time
- 本地时间 ↔ 系统时间转换 在夏令时切换期间可能会抛异常
- 开发者可以通过
choose::earliest或choose::latest指定转换策略,避免模糊时间问题
如果你愿意,我可以帮你画一张 本地时间与系统时间转换示意图,直观显示 重复小时(ambiguous) 和 跳过小时(nonexistent) 的情况。
整理一个 完整可编译的 C++20 示例,展示如何使用 <chrono> 的 时区类型(time_zone、zoned_time)以及处理夏令时异常。
#include <chrono>
#include <iostream>
#include <chrono> // C++20 时区支持
using namespace std::chrono;
int main() {
try {
// 获取纽约时区
const time_zone* ny_tz = locate_zone("America/New_York");
// 创建一个系统时间点(UTC 时间)
sys_time<std::chrono::minutes> utc_tp{sys_days{2023y / November / 5} + hours{1} +
minutes{30}};
// 将系统时间转换为该时区的本地时间
zoned_time zt{ny_tz, utc_tp};
std::cout << "UTC time: " << utc_tp << "\n";
std::cout << "New York local time: " << zt.get_local_time() << "\n";
// 尝试创建一个可能模糊或不存在的本地时间
local_time<std::chrono::minutes> ambiguous_local{local_days{2023y / November / 5} +
hours{1} + minutes{30}};
// 转换本地时间到系统时间,处理模糊时间或不存在时间
auto sys_tp = ny_tz->to_sys(ambiguous_local, choose::latest);
std::cout << "Converted system time: " << sys_tp << "\n";
} catch (const ambiguous_local_time& e) {
std::cerr << "Ambiguous local time exception!\n";
} catch (const nonexistent_local_time& e) {
std::cerr << "Nonexistent local time exception!\n";
} catch (const std::exception& e) {
std::cerr << "Other exception: " << e.what() << "\n";
}
return 0;
}
说明
- 获取时区:
const time_zone* ny_tz = locate_zone("America/New_York");
- 使用 IANA 时区名称获取时区对象
- 创建
zoned_time:
zoned_time zt{ny_tz, utc_tp};
- 将 UTC 系统时间转换为该时区本地时间
- 处理夏令时异常:
ambiguous_local_time→ 本地时间重复的情况(夏令时结束)nonexistent_local_time→ 本地时间不存在的情况(夏令时开始)- 可以通过
choose::earliest或choose::latest指定策略
- 输出:
- UTC 时间
- 时区本地时间
- 转换后的系统时间
这段代码演示了 系统时间 ↔ 本地时间 ↔ 时区时间 的完整流程,并处理可能的夏令时异常。
如果你需要,我可以画一张 时区时间转换示意图,直观显示 UTC、local_time、zoned_time 的关系,以及 重复小时/跳过小时 的情况。
UTC time: 2023-11-05 01:30:00
New York local time: 2023-11-04 21:30:00
Converted system time: 2023-11-05 06:30:00
ambiguous_local_time example
#include <chrono>
#include <iostream>
using namespace std::chrono;
int main() {
try {
auto ld = local_days{Sunday[1] / November / 2016};
auto lt = ld + 1h + 30min;
auto zt = zoned_time{"America/New_York", lt};
} catch (const ambiguous_local_time& e) {
std::cout << e.what() << '\n';
}
}
2016-11-06 01:30:00 is ambiguous. It could be
2016-11-06 01:30:00 EDT == 2016-11-06 05:30:00 UTC or
2016-11-06 01:30:00 EST == 2016-11-06 06:30:00 UTC
<svg width="800" height="300" xmlns="http://www.w3.org/2000/svg">
<!-- UTC时间条 -->
<rect x="50" y="50" width="700" height="80" fill="#2E8B57" rx="5"/>
<text x="70" y="100" fill="white" font-family="Arial, sans-serif" font-size="24" font-weight="bold">UTC</text>
<!-- UTC时间标记 -->
<text x="200" y="100" fill="white" font-family="Arial, sans-serif" font-size="28" font-weight="bold">3:00</text>
<text x="300" y="100" fill="white" font-family="Arial, sans-serif" font-size="28" font-weight="bold">4:00</text>
<text x="400" y="100" fill="white" font-family="Arial, sans-serif" font-size="28" font-weight="bold">5:00</text>
<text x="500" y="100" fill="white" font-family="Arial, sans-serif" font-size="28" font-weight="bold">6:00</text>
<text x="600" y="100" fill="white" font-family="Arial, sans-serif" font-size="28" font-weight="bold">7:00</text>
<!-- 连接线 -->
<line x1="225" y1="130" x2="225" y2="170" stroke="#2E8B57" stroke-width="2" marker-end="url(#arrowhead)"/>
<line x1="325" y1="130" x2="325" y2="170" stroke="#2E8B57" stroke-width="2" marker-end="url(#arrowhead)"/>
<line x1="425" y1="130" x2="425" y2="170" stroke="#2E8B57" stroke-width="2" marker-end="url(#arrowhead)"/>
<line x1="525" y1="130" x2="425" y2="170" stroke="#2E8B57" stroke-width="2" marker-end="url(#arrowhead)"/>
<line x1="475" y1="130" x2="475" y2="170" stroke="#2E8B57" stroke-width="2" marker-end="url(#arrowhead)"/>
<line x1="625" y1="130" x2="525" y2="170" stroke="#2E8B57" stroke-width="2" marker-end="url(#arrowhead)"/>
<line x1="525" y1="130" x2="525" y2="170" stroke="#2E8B57" stroke-width="2" marker-end="url(#arrowhead)"/>
<line x1="575" y1="130" x2="475" y2="170" stroke="#2E8B57" stroke-width="2" marker-end="url(#arrowhead)"/>
<line x1="675" y1="130" x2="575" y2="170" stroke="#2E8B57" stroke-width="2" marker-end="url(#arrowhead)"/>
<line x1="725" y1="130" x2="645" y2="170" stroke="#2E8B57" stroke-width="2" marker-end="url(#arrowhead)"/>
<!-- 纽约时间条 -->
<rect x="50" y="170" width="700" height="80" fill="#9ACD32" rx="5"/>
<text x="70" y="210" fill="black" font-family="Arial, sans-serif" font-size="18" font-weight="bold">America/</text>
<text x="70" y="230" fill="black" font-family="Arial, sans-serif" font-size="18" font-weight="bold">New York</text>
<!-- 夜间时段高亮 -->
<rect x="420" y="170" width="150" height="80" fill="#8B4513" opacity="0.7" rx="5"/>
<!-- 纽约时间标记 -->
<text x="190" y="220" fill="black" font-family="Arial, sans-serif" font-size="28" font-weight="bold">23:00</text>
<text x="290" y="220" fill="black" font-family="Arial, sans-serif" font-size="28" font-weight="bold">24:00</text>
<text x="400" y="220" fill="black" font-family="Arial, sans-serif" font-size="28" font-weight="bold">1:00</text>
<text x="480" y="220" fill="#D2691E" font-family="Arial, sans-serif" font-size="28" font-weight="bold">2:00</text>
<text x="580" y="220" fill="black" font-family="Arial, sans-serif" font-size="28" font-weight="bold">3:00</text>
<!-- 红色圆圈标记 -->
<circle cx="475" cy="175" r="25" fill="none" stroke="#FF4500" stroke-width="4"/>
<!-- 箭头标记定义 -->
<defs>
<marker id="arrowhead" markerWidth="10" markerHeight="7"
refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#2E8B57"/>
</marker>
</defs>
</svg>
你提供的示例和 SVG 图讲解的是 美国 2016 年秋季夏令时结束导致的本地时间模糊(ambiguous_local_time),我帮你理解如下:
1⃣ C++ 示例说明
auto ld = local_days{Sunday[1] / November / 2016};
auto lt = ld + 1h + 30min;
auto zt = zoned_time{"America/New_York", lt};
- 创建了一个 本地时间:2016 年 11 月 6 日,星期日,01:30
- 使用纽约时区(America/New_York)
- 问题出现:这个时间在夏令时结束那天重复出现了一次,所以是模糊时间
- 捕获异常:
catch (const ambiguous_local_time& e)
- 输出:
2016-11-06 01:30:00 is ambiguous. It could be
2016-11-06 01:30:00 EDT == 2016-11-06 05:30:00 UTC or
2016-11-06 01:30:00 EST == 2016-11-06 06:30:00 UTC
2⃣ 夏令时转换背景(2016 年秋季)
- 秋季 DST 结束:2016-11-06 02:00
- 时钟向后调 1 小时 → 01:00 再次出现
- 因此 01:30 这个本地时间在纽约实际上出现了 两次:
- 第一次:01:30 EDT(夏令时),对应 UTC 05:30
- 第二次:01:30 EST(标准时),对应 UTC 06:30
- 所以这个时间点是 模糊的(ambiguous)
3⃣ SVG 图解释
- 绿色条(UTC 时间):显示 UTC 时间 3:00、4:00、5:00、6:00 等
- 浅绿条(纽约时间):显示本地时间 23:00、24:00、01:00、02:00、03:00
- 棕色高亮块:01:00–02:00 的重复小时
- 红色圆圈:01:30 被标记,表示这是一个 ambiguous_local_time
- 箭头连接:
- 显示 UTC 与纽约本地时间之间的对应关系
- 01:30 对应两种 UTC 时间(05:30 和 06:30)
4⃣ 总结
- 模糊本地时间发生在夏令时结束、时钟向后调整 1 小时时段
- 对应的 UTC 时间不同,因此同一个本地时间点可能映射到两个不同的系统时间点
- C++20
<chrono>会抛出ambiguous_local_time异常 - 可以通过
choose::earliest或choose::latest来指定选择哪一个时间
如果你愿意,我可以帮你画一张 更简洁的时间轴图,用文字标注重复小时和 UTC 映射,方便记忆和理解夏令时模糊时间。
nonexistent_local_time example
#include <chrono>
#include <iostream>
using namespace std::chrono;
int main() {
try {
auto lt = local_days{Sunday[2] / March / 2016} + 2h + 30min;
auto zt = zoned_time{"America/New_York", lt};
} catch (const nonexistent_local_time& e) {
std::cout << e.what() << 'n';
}
}
2016-03-13 02:30:00 is in a gap between
2016-03-13 02:00:00 EST and
2016-03-13 03:00:00 EDT which are both equivalent to
2016-03-13 07:00:00 UTC
xiaqiu@xz:~/test$
<svg width="1000" height="250" xmlns="http://www.w3.org/2000/svg">
<!-- 箭头标记定义 -->
<defs>
<marker id="arrowhead" markerWidth="10" markerHeight="7"
refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#2E8B57"/>
</marker>
</defs>
<!-- UTC时间条 -->
<rect x="200" y="30" width="700" height="80" fill="#2E8B57" rx="5"/>
<text x="50" y="80" fill="black" font-family="Arial, sans-serif" font-size="24" font-weight="bold">UTC</text>
<!-- UTC时间标记 -->
<text x="280" y="80" fill="white" font-family="Arial, sans-serif" font-size="32" font-weight="bold">3:00</text>
<text x="380" y="80" fill="white" font-family="Arial, sans-serif" font-size="32" font-weight="bold">4:00</text>
<text x="480" y="80" fill="white" font-family="Arial, sans-serif" font-size="32" font-weight="bold">5:00</text>
<text x="580" y="80" fill="white" font-family="Arial, sans-serif" font-size="32" font-weight="bold">6:00</text>
<text x="680" y="80" fill="white" font-family="Arial, sans-serif" font-size="32" font-weight="bold">7:00</text>
<!-- 垂直连接线箭头(直下) -->
<line x1="320" y1="110" x2="320" y2="140" stroke="#2E8B57" stroke-width="2" marker-end="url(#arrowhead)"/>
<line x1="420" y1="110" x2="420" y2="140" stroke="#2E8B57" stroke-width="2" marker-end="url(#arrowhead)"/>
<line x1="520" y1="110" x2="520" y2="140" stroke="#2E8B57" stroke-width="2" marker-end="url(#arrowhead)"/>
<line x1="620" y1="110" x2="620" y2="140" stroke="#2E8B57" stroke-width="2" marker-end="url(#arrowhead)"/>
<!-- 斜线连接箭头(向右偏移) -->
<line x1="620" y1="110" x2="730" y2="140" stroke="#2E8B57" stroke-width="2" marker-end="url(#arrowhead)"/>
<line x1="720" y1="110" x2="830" y2="140" stroke="#2E8B57" stroke-width="2" marker-end="url(#arrowhead)"/>
<line x1="820" y1="110" x2="930" y2="140" stroke="#2E8B57" stroke-width="2" marker-end="url(#arrowhead)"/>
<!-- 纽约时间条 -->
<rect x="200" y="140" width="700" height="80" fill="#7CB342" rx="5"/>
<text x="50" y="175" fill="black" font-family="Arial, sans-serif" font-size="18" font-weight="bold">America/</text>
<text x="50" y="195" fill="black" font-family="Arial, sans-serif" font-size="18" font-weight="bold">New York</text>
<!-- 夜间时段高亮(浅绿色覆盖) -->
<rect x="720" y="140" width="180" height="80" fill="#A5D66A" rx="5"/>
<!-- 纽约时间标记 -->
<text x="260" y="190" fill="white" font-family="Arial, sans-serif" font-size="32" font-weight="bold">23:00</text>
<text x="370" y="190" fill="white" font-family="Arial, sans-serif" font-size="32" font-weight="bold">24:00</text>
<text x="490" y="190" fill="white" font-family="Arial, sans-serif" font-size="32" font-weight="bold">1:00</text>
<text x="590" y="190" fill="white" font-family="Arial, sans-serif" font-size="32" font-weight="bold">2:00</text>
<text x="790" y="190" fill="black" font-family="Arial, sans-serif" font-size="32" font-weight="bold">3:00</text>
<!-- 红色圆圈标记 -->
<circle cx="670" cy="145" r="20" fill="none" stroke="#D32F2F" stroke-width="4"/>
</svg>
你提供的示例和 SVG 图讲解的是 美国 2016 年春季夏令时开始导致的本地时间不存在(nonexistent_local_time),我帮你理解如下:
1⃣ C++ 示例说明
auto lt = local_days{Sunday[2] / March / 2016} + 2h + 30min;
auto zt = zoned_time{"America/New_York", lt};
- 创建了一个 本地时间:2016 年 3 月 13 日,星期日,02:30
- 使用纽约时区(America/New_York)
- 问题出现:这个时间在夏令时开始那天跳过了 1 小时,所以 这个本地时间根本不存在
- 捕获异常:
catch (const nonexistent_local_time& e)
- 输出:
2016-03-13 02:30:00 is in a gap between
2016-03-13 02:00:00 EST and
2016-03-13 03:00:00 EDT which are both equivalent to
2016-03-13 07:00:00 UTC
2⃣ 夏令时转换背景(2016 年春季)
- 春季 DST 开始:2016-03-13 02:00
- 时钟向前调 1 小时 → 02:00 直接跳到 03:00
- 因此 02:30 根本不存在
- UTC 对应时间为 07:00
- 这种情况就是 nonexistent_local_time
3⃣ SVG 图解释
- 绿色条(UTC 时间):显示 UTC 时间 3:00、4:00、5:00、6:00、7:00
- 浅绿条(纽约时间):显示本地时间 23:00、24:00、01:00、02:00、03:00
- 高亮块:02:00–03:00 的空缺时段
- 红色圆圈:02:30 被标记,表示这是一个 nonexistent_local_time
- 箭头连接:
- 显示 UTC 与纽约本地时间的对应关系
- 跳过 02:00–03:00 这段时间
4⃣ 总结
- 不存在本地时间发生在夏令时开始、时钟向前调整 1 小时时段
- 对应的 UTC 时间跳过了这段本地时间,因此本地时间无法映射到系统时间
- C++20
<chrono>会抛出nonexistent_local_time异常 - 可以通过
choose::earliest或choose::latest等策略处理这类异常
简单记忆方法:
| 类型 | 发生时机 | 特点 |
|---|---|---|
ambiguous_local_time |
DST 结束 | 本地时间重复出现 1 小时 |
nonexistent_local_time |
DST 开始 | 本地时间被跳过 1 小时 |
| 如果你希望,我可以帮你画一张 对比图,同时显示 模糊时间 vs 不存在时间,直观理解 DST 的两种特殊情况。 | ||
你提供的内容讲解的是 C++20 <chrono> 中的 tzdb(Time Zone Database)及其使用方法。我帮你理解如下: |
1⃣ tzdb 类型
tzdb是用来存储 时区数据库数据 的类型- 数据成员包括:
string version:时区数据库版本vector<time_zone> zones:具体的时区信息vector<time_zone_link> links:时区别名或链接vector<leap_second>:闰秒信息
- 成员函数:
leap_seconds():返回闰秒列表locate_zone(string_view tz_name):根据名字查找时区current_zone():返回当前系统时区
2⃣ tzdb_list 类型
tzdb_list是 tzdb 对象的列表,可能包含不同版本的时区数据库- 这是一个 单例(singleton),通常通过:
获取get_tzdb_list() - 也可以直接使用:
返回列表的第一个 tzdb(通常是最新版本)get_tzdb()
3⃣ tzdb 使用示例
#include <algorithm>
#include <iostream>
#include <chrono>
using namespace std::chrono;
int main() {
const auto& db = get_tzdb();
std::cout << "Time Zone descriptions:\n";
std::for_each(db.zones.begin(), db.zones.end(), [](const time_zone& z) {
std::cout << "Zone: " << z.name() << "\n";
});
}
- 输出:
Time Zone descriptions:
Zone: Africa/Abidjan
Zone: Africa/Accra
Zone: Africa/Addis_Ababa
Zone: Africa/Algiers
...
- 说明:遍历了 所有可用时区 并打印它们的名字
总结
tzdb保存 时区信息、闰秒信息及版本tzdb_list可以管理多个 tzdb 版本- 通过
get_tzdb()获取最新时区数据 - 可以使用
locate_zone查找指定时区,或用zones遍历所有时区
我可以帮你画一张 tzdb 数据结构示意图,直观显示zones、links、leap_seconds等关系,这样更容易理解时区数据库的组织。
你提供的内容主要讲解 C++20<chrono>的时区数据来源及 MSVC 的实现故事,我帮你理解如下:
1⃣ 时区数据来源
- C++20
<chrono>通过 IANA 时区数据库(IANA tz database)提供时区信息 - 在 Unix-like 系统(Linux、macOS 等),数据库通常位于:
/usr/share/zoneinfo - Windows 系统不自带 IANA 数据,所以实现上有特殊处理
2⃣ MSVC 处理方案
MSVC STL 团队在处理 Windows 上的时区数据时,考虑了几个方案:
- 直接随库发布完整 IANA 数据库
- 问题:IANA 数据库较大(约 1.25 MB),会增加 STL 库大小
- 更新问题:数据库更新如何处理?
- 联网下载时区数据
- STL 目前不依赖网络,增加网络依赖复杂且不方便
- 寻找替代数据源
- 最终选择:使用 ICU 库(International Components for Unicode)
- ICU 随 Windows 10 系统提供,且数据源基于 IANA 数据
- 通过 Windows Update 可以获取时区数据更新
注意:ICU 数据仅在 Windows 10 19H1 及之后版本可用
3⃣ IANA vs ICU 数据差异
| 特性 | IANA | ICU |
|---|---|---|
| 时区命名 | “标准名”和“备用名” | “规范名”和“非规范名” |
| 老旧时区 | 包含一些历史时期异常时区,如 EWT | 有些名称已经过时,如 America/Godthab |
| 时区更新 | 包含最新名称,如 America/Nuuk | 仍使用旧名称 America/Godthab |
| 映射问题 | 无直接映射机制 | MSVC STL 将 ICU 所有时区(规范与非规范)都视作有效时区 |
4⃣ 总结
- C++20
<chrono>在 Windows 上实现时区支持存在挑战 - MSVC 通过 ICU 数据源 + Windows Update 解决了数据来源和更新问题
- 时间处理非常复杂:闰秒、时区、夏令时等因素都需要考虑
如果你需要,我可以帮你画一个 Windows / Unix 时区数据来源对比图,直观显示 IANA、ICU 和 STL 的关系,这样理解更容易。
更多推荐

所有评论(0)