当前位置: 首页 > news >正文

CppCon 2015 学习:Time Programming Fundamentals

Civil Time 公历时间

特点:

  • 共 6 个字段
    • Year(年)
    • Month(月)
    • Day(日)
    • Hour(小时)
    • Minute(分钟)
    • Second(秒)
  • 表示的是人类日常使用的时间,基于格里高利历法(Gregorian Calendar),即我们日常使用的日历系统。

典型表示方式:

  1. 结构体形式
    std::tm timeStruct;
    timeStruct.tm_year = 2025 - 1900; // 年份从 1900 开始算
    timeStruct.tm_mon = 5;            // 6 月,从 0 开始
    timeStruct.tm_mday = 9;
    timeStruct.tm_hour = 14;
    timeStruct.tm_min = 30;
    timeStruct.tm_sec = 0;
    
  2. 6 个独立的整数字段
    int year = 2025, month = 6, day = 9;
    int hour = 14, minute = 30, second = 0;
    
  3. 字符串表示
    • "2015-01-02 03:04:05"
    • 分成两部分:
      • 日期:YYYY-MM-DD
      • 时间:HH:MM:SS

应用场景

  • 显示给用户看(日志、表单、UI)
  • 转换为其他时间格式(如 POSIX 时间戳)
  • 排序、比较、计划调度

注意点:

  • 不包含 时区信息
  • 不考虑 夏令时(DST)调整
  • 并不适合用来做精确计算(如两个事件相差多少秒),需要搭配 UTC 或时间戳使用
    如你需要更进一步的时间处理知识,例如:
  • 时区处理(Time Zone)
  • 高精度时间(std::chrono)
  • 时间戳与 Civil Time 的转换

UTC(协调世界时) 的概念。

UTC — Coordinated Universal Time

核心特性:

特性说明
全球标准时间是全世界本地时间(Local Civil Time)的基础
无夏令时(DST)UTC 不随季节变化,因此没有夏令时切换的复杂性
格里高利历法与 Civil Time 一样,使用我们日常熟悉的公历系统
SI 秒为单位UTC 以国际单位制(SI)的秒为时间刻度
支持闰秒为了与地球自转同步,偶尔会插入闰秒(Leap Seconds)

闰秒(Leap Seconds)

  • 由于地球自转速度不均匀,UTC 需偶尔插入一秒以保持与天文时间(UT1)接近。
  • 举例:一个 UTC 日可能会有 23:59:60 这一秒。

名称由来:UTC

语言缩写原意
英文CUTCoordinated Universal Time
法文TUCTemps Universel Coordonné
折中方案UTC保持中立的国际缩写,没有语言偏向

虽然 “UTC” 不是英语或法语的自然缩写,但它被国际标准化组织(ISO)采纳为统一格式。

应用场景:

  • 全球服务器同步(NTP 协议)
  • 日志时间戳
  • 数据库时间记录
  • 天文与卫星数据
  • 不受夏令时影响的时间计算和对比

小结:

  • UTC 是不随地区变化的标准时间
  • 适合用于跨时区、精确、长期的一致性时间表示;
  • 不包含时区或本地时间语义,仅提供统一的绝对时间基准

以下是对你提供内容的 “Absolute Time”(绝对时间)

Absolute Time(绝对时间)

定义:

Absolute Time 表示某个“唯一且确定的时间点”,不依赖于任何时区或地区设置。

特性:

特性说明
唯一 & 全局一致每个 Absolute Time 值都对应一个确定的“瞬间”
时区无关不会随时区变化;与北京时间、纽约时间等本地时间(Civil Time)无关
基于 Epoch 的计数表示为自某个“起始时刻(epoch)”起经过的时间单位数

形式:

  • 通常表示为:
    time = count of <unit> since <epoch>
    

常见 Epoch 选择:

名称Epoch 起点单位
Unix 时间1970-01-01 00:00:00 UTC秒(或纳秒)
GPS 时间1980-01-06 00:00:00 UTC周/秒
Windows FILETIME1601-01-01 UTC100 纳秒

示例:

示例类型描述
std::time_tUnix 时间戳(以秒为单位的整数)
std::chrono::system_clock::time_pointC++11 提供的时间点类,单位可为秒/毫秒等
int64_t常见自定义绝对时间类型,便于跨平台传输

与 Civil Time 的区别:

比较点Absolute TimeCivil Time
时区依赖
人类可读性差,需转换
适合计算易于比较与计算含复杂规则(闰年、DST)
表达方式数字(秒、毫秒等)格式化字符串(YYYY-MM-DD)

总结:

  • Absolute Time 是跨系统、跨时区最可靠的时间表示形式;
  • 常用于数据库、网络协议、日志记录、缓存失效、事件顺序等;
  • 需要时可转换为用户所在时区的 Civil Time 以供展示。

下面是你提供的内容“Time Zone(时区)”的详解和补充说明,帮助你清晰地掌握概念、用途和常见误区:

Time Zone(时区)

定义:

时区是用于将绝对时间(如 UTC 时间戳)转换为本地民用时间(Civil Time)的一套规则,这些规则由地区/国家设定,可能包含夏令时(DST)等变化因素。

关键特性:

特性说明
UTC 偏移规则每个时区定义一个“相对于 UTC 的时间差”,如 UTC+9(东京)或 UTC−5(纽约冬令时)
DST(夏令时)可选一些时区会根据季节自动切换时钟(如纽约、伦敦)
由政府定义各国或地区的立法机关可以随时改变其本地的时区规则
可能发生变化历史上许多国家变更过自己的时区或废除/采用 DST

表示方式:

推荐使用的标识符(IANA Time Zone Database):
格式示例说明
Area/Location"Asia/Tokyo"明确,推荐使用
"America/New_York"支持历史变更与夏令时规则
"Europe/London"支持 DST
不推荐的模糊缩写:
缩写问题
PST有歧义(可能指多个区域)
EST不随夏令时变化

示例:UTC ↔ Civil Time 的转换

假设绝对时间为 2025-06-09T12:00:00Z(UTC):

时区本地时间(含偏移)是否为夏令时
Asia/Tokyo2025-06-09 21:00:00 +09:00无 DST
America/New_York2025-06-09 08:00:00 -04:00是 DST
Europe/London2025-06-09 13:00:00 +01:00是 DST
Australia/Sydney2025-06-09 22:00:00 +10:00非夏令时

小贴士:

  • 时区信息并非“静态偏移”,而是一个随时间变化的函数
  • 为了可移植性、准确性,应使用 IANA 时区名称(例如 "America/New_York")而不是固定偏移量;
  • 在程序中使用时,推荐用如 chrono-tzdate 库(C++)、zoneinfo(Python)、moment-timezone(JS)等。

总结:

  • 时区是 civil time 和 absolute time 之间转换的关键规则系统
  • 它受制于地理、法律、历史和季节等因素;
  • 开发中应避免简写,优先使用标准命名时区。

Interesting Time Zone Transitions(有趣的时区转换)”的总结和补充说明,帮你更好理解时区的复杂性:

Interesting Time Zone Transitions (有趣的时区转换)

现实中的特殊时区现象:

地区/时区现象描述备注
Arizona/Phoenix不实行夏令时(DST)全年保持标准时间,免去调表麻烦
Asia/Kathmandu1986 年时间跳跃 15 分钟例外的非整分钟偏移
Australia/Lord_Howe夏令时采用 30 分钟偏移大多数地区 DST 为整小时偏移,但这里例外
Pacific/Apia2011 年 12 月 30 日跳过一天(跨日期线调整)直接跳过一个日期,历史罕见事件
Africa/Cairo春季 DST 转换时跳过了午夜的某一小时典型的夏令时时间跳跃
Africa/Monrovia过去曾用 44 分 30 秒的偏移不是标准整分钟偏移,历史遗留问题

Pro Tip(实用建议)

  • 不要对时间转换写硬编码或“特例”处理
    • 因为时区规则随历史、政治、地理变化不断演化,硬编码会导致代码维护困难,出错概率高。
    • 建议使用成熟的时区数据库(IANA tz database)和标准库,自动处理这些复杂情况。

额外说明:

  • 时区偏移不仅是整小时,有些地区使用 15、30、45 分钟偏移,这在编程时特别要注意。
  • 跨日期线跳日(如 Pacific/Apia)更是少见情况,但在全球范围内并非唯一。
  • 夏令时的开始和结束并非统一时间,甚至具体到某一小时的跳过或重复也会影响时间计算。

结构图示意 “2015-09-22 09:20:00 -07:00” 这类时间字符串的组成部分:

"2015-09-22 09:20:00 -07:00"|---------| |--------| |------|Civil      Time     OffsetTime
------------------------------------------------
| Year | Month | Day | Hour | Minute | Second |  Offset (Time Zone)  |
| 2015 |  09   | 22  |  09  |   20   |   00   |       -07:00         |
------------------------------------------------
Civil Time:   "2015-09-22 09:20:00"- Represents human-readable local time (year, month, day, hour, minute, second)
Offset (Time Zone): "-07:00"- Offset from UTC (Coordinated Universal Time)- Indicates this local time is 7 hours behind UTC
Absolute Time:- The unique point in time represented by this string, independent of time zone- Derived by applying the offset to the civil time to get UTC-based time

解释:

  • Civil Time:基于年月日时分秒的时间,通常是人类日常使用的时间格式。
  • Offset (Time Zone):说明该时间相对于 UTC 的偏移(这里是负7小时)。
  • Absolute Time:时间点的唯一标识(如 Unix 时间戳),不依赖时区。

这段内容讲的是经典时间编程接口的基本概念,具体中文解释如下:

经典时间编程
经典时间API:

  • std::time_t
    表示从 1970年1月1日 00:00:00 UTC 开始计算的非闰秒数(即绝对时间点,通常称为Unix时间戳)。
  • std::tm
    拆分后的时间结构,包含年、月、日、时、分、秒等组成部分(民用时间的表示形式)。
    时间转换流程
  • time_ttm
    • gmtime() 将绝对时间(time_t)转换成 UTC 时间的拆分表示(std::tm)。
    • localtime() 将绝对时间(time_t)转换成本地时区对应的拆分时间(std::tm)。
  • tmtime_t
    • mktime() 将本地时间拆分结构(std::tm)转换回绝对时间(time_t)。

这是一个经典的示例程序,用来演示如何使用 C++ 标准库的时间函数获取当前时间,并将其以指定格式分别输出为 UTC 时间和本地时间。

std::string Format(const std::string& fmt, const std::tm& tm);
int main() {const std::time_t now = std::time(nullptr);std::tm tm_utc;gmtime_r(&now, &tm_utc);std::cout << Format("UTC: %F %T\n", tm_utc);std::tm tm_local;localtime_r(&now, &tm_local);std::cout << Format("Local: %F %T\n", tm_local);
}

具体说明:

std::string Format(const std::string& fmt, const std::tm& tm);
  • 声明了一个格式化函数,接受格式字符串和一个 std::tm 结构,返回格式化后的时间字符串。
int main() {const std::time_t now = std::time(nullptr);
  • 获取当前系统时间,返回自1970年1月1日以来的秒数(time_t)。
  std::tm tm_utc;gmtime_r(&now, &tm_utc);
  • 使用线程安全的 gmtime_r 函数,把 now 转换为 UTC 时间,存入 tm_utc
  std::cout << Format("UTC: %F %T\n", tm_utc);
  • 格式化并输出 UTC 时间。
  std::tm tm_local;localtime_r(&now, &tm_local);
  • 使用线程安全的 localtime_r 函数,把 now 转换为本地时区的时间,存入 tm_local
  std::cout << Format("Local: %F %T\n", tm_local);
}
  • 格式化并输出本地时间。
    总结:
    这段代码演示了如何获取当前时间,然后分别以 UTC 和本地时区的形式格式化输出。用到了 gmtime_rlocaltime_r 两个线程安全版本的时间转换函数。

详细分析这段代码和它体现的“Epoch Shifting”(纪元偏移)概念:

int GetOffset(std::time_t t, const std::string& zone);
int main() {const std::time_t now = std::time(nullptr);// Shift epoch: UTC to "local time_t"int off = GetOffset(now, "America/New_York");const std::time_t now_nyc = now + off;std::tm tm_nyc;gmtime_r(&now_nyc, &tm_nyc);std::cout << Format("NYC: %F %T\n", tm_nyc);// Shift back: "local time_t" to UTCoff = GetOffset(now_nyc, "America/New_York");const std::time_t now_utc = now_nyc - off;return now_utc == now ? 0 : 1;
}

代码及概念分析

  1. const std::time_t now = std::time(nullptr);
    获取当前时间的 UTC 时间戳,now 是自 1970-01-01 00:00:00 UTC 起的秒数。
  2. int off = GetOffset(now, "America/New_York");
    通过 GetOffset 函数,获得时间戳 now 对应时区 "America/New_York" 与 UTC 的时差(单位秒)。
    • 这个偏移考虑了时区和可能的夏令时(DST)等规则。
  3. const std::time_t now_nyc = now + off;
    将 UTC 时间戳 now 加上纽约时区偏移,得到一个“纽约时区时间戳”——纪元偏移
    • 这时 now_nyc 实际上是基于纽约时区调整后的时间戳,但仍然用 time_t 表示。
    • 注意,这里是人为调整时间戳,使得它相对于纽约本地时间的“纪元”起点。
  4. gmtime_r(&now_nyc, &tm_nyc);
    将调整后的时间戳 now_nyc 转换成 UTC 分解时间。
    • 因为now_nyc已经是“偏移后的时间”,用 gmtime_r 反映出来的时间是纽约本地时间。
  5. std::cout << Format("NYC: %F %T\n", tm_nyc);
    输出格式化后的纽约时间字符串。
  6. off = GetOffset(now_nyc, "America/New_York");
    重新计算纽约时间戳对应的纽约时区偏移(理论上应该一致)。
  7. const std::time_t now_utc = now_nyc - off;
    将“本地时间戳”通过减去时区偏移恢复成 UTC 时间戳。
  8. return now_utc == now ? 0 : 1;
    检查通过偏移和反偏移的转换是否保持一致,正常情况下应相等,返回 0 表示成功。

关键点总结

  • Epoch Shifting(纪元偏移):通过在 UTC 时间戳基础上加上时区偏移,得到一个对应本地时间的时间戳。
  • 这个技巧可以让开发者以 UTC 时间戳的形式保存本地时间(不是标准做法),方便某些特定计算。
  • 但必须小心偏移的正确计算,尤其是时区规则和夏令时变化。
  • 通过反向偏移,能还原为标准的 UTC 时间戳。
  • 代码中 GetOffset 是关键,它需要正确处理时区和夏令时转换。

为什么用 gmtime_r 而不是 localtime_r

  • 因为经过偏移的 now_nyc 实际上是一个基于纽约时间的“UTC时间戳”,用 gmtime_r 转换才能正确得到纽约时间的分解结构。
  • 如果用 localtime_r,它会以当前系统的本地时区(可能不是纽约时区)进行转换,会出错。

这里讲的是“Epoch Shifting”的问题和陷阱。总结和分析如下:

代码问题回顾

const std::time_t now = std::time(nullptr);
int off = GetOffset(now, "America/New_York");
const std::time_t now_nyc = now + off;
std::tm tm_nyc;
gmtime_r(&now_nyc, &tm_nyc);
std::cout << Format("NYC: %F %T\n", tm_nyc);
off = GetOffset(now_nyc, "America/New_York");
const std::time_t now_utc = now_nyc - off;
return now_utc == now ? 0 : 1;

代码中的主要问题和陷阱

  1. now_nyc 不是一个真正的 time_t
    • time_t 通常定义为从1970-01-01 UTC起的秒数,是绝对时间(UTC时间)。
    • 这里通过 now + off 得到的 now_nyc 实际上是人为加上时区偏移的值,它不再是标准的 UTC 时间戳,而是“本地时间的秒数表示”,这种用法不被标准时间API支持,也没有统一定义。
    • 这就造成了时间语义混乱,后续操作难以预测。
  2. 偏移量的计算和加减问题
    • GetOffset 返回的是“相对于 UTC 的偏移”,但这个偏移是否加或减取决于方向和定义。
    • 在代码里是用 +off 转换成“本地时间”,用 -off 转回 UTC,但如果偏移计算不一致,可能导致错误。
    • 另外,夏令时变动时,这个偏移并不是常数,可能随时间变。
  3. std::tm 结构体可能包含无效字段
    • 通过 gmtime_r 转换带有偏移的时间戳时,tm 的某些字段(如 tm_isdst 等)会不准确。
    • 这会导致日期或时间显示不正确,或者计算不准确。
  4. “本地时间的 time_t”概念是错误的
    • time_t 标准语义是绝对时间点(UTC时间戳),不存在“本地时间的 time_t”。
    • 本地时间应该用 std::tm 或其它结构表示,不应直接修改 time_t 来表示本地时间。
    • 任何试图通过加减偏移在 time_t 上做“本地时间”都会带来难以维护和易出错的代码。

Pro Tip — 不要制造“本地 time_t”

  • 标准做法是
    • time_t 总是 UTC 时间戳。
    • gmtime()localtime() 等函数将其转换成具体时区的tm
    • 不要通过数学运算改写 time_t 来表示本地时间。
  • 如果需要表示特定时区的时间点,用更高级的时区库(如 date 库、chrono 扩展、tz 库)来处理。

总结

  • 代码的 now_nyc = now + off; 试图通过加偏移创造“本地时间戳”是不正确的。
  • 偏移加减不当会导致时间错误,且无法保证夏令时等变化的正确处理。
  • std::tm 可能因此含有无效或错误信息。
  • 不要试图用 time_t 表示本地时间!

这几行表达的是时间转换的核心概念:

关键概念解析

  • Absolute Time
    绝对时间:一个唯一且全局统一的时间点,不受时区影响。比如 UTC 时间戳(如 time_tstd::chrono::system_clock::time_point)。
  • Time Zone
    时区:定义了从绝对时间到当地“民用时间(Civil Time)”的转换规则,包括时区偏移和夏令时等。
  • Civil Time
    民用时间:人类日常使用的时间,包含年、月、日、时、分、秒,通常是时区相关的。

两个重要的函数/映射关系

  • F(Absolute, TZ) → Civil
    给定一个绝对时间和时区,转换出该时区对应的民用时间(年、月、日、时、分、秒)。
    例如:UTC 时间戳 + “America/New_York” → 纽约当地时间。
  • F(Civil, TZ) → Absolute
    给定一个民用时间和时区,转换回对应的绝对时间(UTC 时间戳)。
    例如:纽约当地的“2025-06-09 12:00:00” + “America/New_York” → UTC 时间戳。

总结

  • 绝对时间和民用时间之间的转换依赖于时区信息。
  • 时区是连接这两者的桥梁,定义了偏移和夏令时规则。
  • 时间处理的核心就是准确地进行这两种转换。

传统时间处理中的一些局限,以及为什么需要更完善的时间和时区模型:

什么是缺失的?——传统时区处理的不足

  1. 时区是不透明的(Time zones are opaque)
    传统API只给出数值偏移(比如 -07:00),但无法暴露时区内部复杂规则(如夏令时切换日期、历史变更等)。
  2. 数字偏移不够用(Numeric offsets unnecessary)
    仅靠固定的数字偏移(小时/分钟)无法处理复杂的时区变化,尤其是夏令时和历史调整。
  3. 没有“本地秒数”概念(No “local seconds” / no epoch shifting)
    传统API尝试通过调整 time_t 来表示本地时间,但这样做是不正确的,因为 time_t 本质是UTC秒数,不该被当作“本地时间戳”使用。
  4. 没有访问未来或过去时区转换规则的能力(No access to future/past transitions)
    传统接口通常只提供当前偏移,缺少对时区规则历史和未来变动的支持。

Ken Thompson 的观点

“These concepts fill a much needed gap.”

—— Ken Thompson

Ken Thompson 指出,必须引入更丰富的时间模型来弥补传统API在时区处理上的不足,尤其是透明时区规则、正确时间转换和完整历史记录。

总结

现代时间库(如 IANA 时区数据库、date 库等)应:

  • 隐藏复杂的时区规则细节(时区是黑盒)
  • 避免不正确的“本地time_t”转换
  • 支持查询时区历史和未来转换
  • 提供正确且清晰的绝对时间和民用时间转换接口

经典API对应模型

概念经典API示例说明
Absolute Timestd::time_tstd::chrono::system_clock::time_point表示某个唯一时间点(基于UTC)
Time Zone由系统环境决定,通过函数如 localtime_r() 获取时区信息定义绝对时间与民用时间的转换规则
Civil Timestd::tm,整数字段,UTC或本地时间以年月日时分秒形式表示的时间

关键函数

  • localtime_r()
    把绝对时间转换成对应时区的民用时间(std::tm结构),线程安全版本。
  • mktime()
    把民用时间转换成绝对时间(std::time_t),用于本地时间(含时区信息)。

转换关系

  • F(Absolute, TZ) → Civil
    绝对时间加上时区规则,转换成民用时间。
  • F(Civil, TZ) → Absolute
    民用时间根据时区规则,转换成绝对时间。

总结

经典API映射到了现代时间模型的三大核心要素及它们之间的转换函数,但它们仍有一定局限,尤其在时区和历史规则支持上。

关于 CCTZ(Google的 C++ 时间库),它对应现代时间模型的具体实现:

CCTZ 库中的时间模型对应

时间概念CCTZ 实现说明
Absolute Timecctz::time_point代表绝对时间点(类似std::chrono::time_point
Time Zonecctz::TimeZone封装了时区规则(含历史变更)
Civil Timecctz::Breakdown / int以年月日时分秒的形式表示的民用时间

主要转换函数

  • cctz::BreakTime(time_point, TimeZone)
    功能: 将绝对时间转换成特定时区的民用时间。
  • cctz::MakeTime(Breakdown, TimeZone)
    功能: 根据民用时间和时区信息,计算对应的绝对时间。

说明

  • CCTZ库提供了现代且精确的时区支持,涵盖了时区规则历史变化。
  • 它避免了传统API中“本地time_t”不明确的设计,确保了时间转换的准确性。

这是一个用 CCTZ 库处理时区和时间点的经典示例。让我帮你逐步分析:

int main() {cctz::TimeZone syd;if (!cctz::LoadTimeZone("Australia/Sydney", &syd)) return -1;// Neil Armstrong first walks on the moon// 1969-07-21 12:56:00 在澳大利亚悉尼时间const cctz::time_point tp1 = cctz::MakeTime(1969, 7, 21, 12, 56, 0, syd);// 按照悉尼时区格式化时间字符串,含日期、时间、时区偏移const std::string s = cctz::Format("%F %T %z", tp1, syd);std::cout << s << "\n";  // 输出类似:"1969-07-21 12:56:00 +1000"cctz::TimeZone nyc;cctz::LoadTimeZone("America/New_York", &nyc);// 1969-07-20 22:56:00 在纽约时间const cctz::time_point tp2 = cctz::MakeTime(1969, 7, 20, 22, 56, 0, nyc);// 断言两个时间点相等,说明这两个民用时间在各自时区实际上指向同一绝对时间点assert(tp2 == tp1);
}

关键点理解:

  • cctz::LoadTimeZone 载入具体时区规则(时区数据库)。
  • cctz::MakeTime民用时间+时区转换成绝对时间点time_point)。
  • 不同地区不同时间对应相同的绝对时间点(UTC时间)。
  • cctz::Format 负责将绝对时间转换成指定时区的民用时间字符串格式。
    这个例子很好的展示了时区转换和绝对时间的概念,避免了传统time_ttm的歧义和陷阱。

关于 CCTZ 库的实现细节,关键点总结如下:

  • 遵循 POSIX 约定
    • 忽略闰秒(leap seconds)
    • 使用历元后格里高利历(Proleptic Gregorian Calendar),即日期从很早开始就用格里高利历表示,不考虑历法转换问题。
  • 使用本地系统的 IANA 时区数据库
    • 依赖系统的 /usr/share/zoneinfo 文件,保证时区数据准确且更新。
  • 自动规范化越界字段
    • 例如日期写成 10 月 32 日,会自动调整成 11 月 1 日,避免错误。
  • 性能优于标准 libc 时间函数
    • CCTZ 在时区和时间点转换上效率更高。
  • 良好处理时间不连续点
    • 比如夏令时切换时的时间跳跃,CCTZ 能够默认合理处理。
      整体来说,CCTZ 是一个轻量且高效的时间库,专注于时间点、时区和民用时间之间的转换,弥补了标准库在时区处理上的不足。

关于“夏令时(Daylight-Saving Time,DST)转换”时的时间表现,关键点如下:

  • 重复时间段(Repeated Time)
    • 在秋季时钟回拨(通常是从夏令时切换回标准时),当地时间会“回退”一小时。
    • 例如:从 02:00 回退到 01:00,导致 01:00 到 02:00 之间的时间出现两次。
    • 因此,“01:30”这类时间点在这段时间内是重复的,即相同的民用时间对应两个不同的绝对时间。
  • 跳过时间段(Skipped Time)
    • 在春季时钟拨快(进入夏令时),当地时间直接跳过某一段时间。
    • 例如:从 01:59 直接跳到 03:00,导致 02:00 到 02:59 的时间点“消失”。
    • 所以“02:30”这类时间不存在于当地时间里,即对应的绝对时间没有民用时间匹配。
  • 总结
    • DST 转换导致部分本地时间既不唯一,也不连续。
    • 这对时间转换函数提出了很高的要求,需要正确处理“重复时间”和“跳过时间”的特殊情况。

下面是对 cctz::MakeTime 及其相关 TimeInfo 结构的解释,用中文总结:

cctz::MakeTimeTimeInfo 说明

cctz::MakeTime 是将 民用时间(Civil Time)时区信息(TimeZone) 转换成 绝对时间(Absolute Time) 的函数。

TimeInfo 结构体

  • 用于描述民用时间对应的绝对时间转换结果及其状态。
struct TimeInfo {enum class Kind {UNIQUE,    // 民用时间唯一对应一个绝对时间(普通情况)SKIPPED,   // 民用时间不存在(例如春季跳过的时间)REPEATED,  // 民用时间有歧义(例如秋季重复的时间)} kind;time_point pre;    // 使用转换前偏移的绝对时间time_point trans;  // 过渡点的绝对时间(转换时刻)time_point post;   // 使用转换后偏移的绝对时间bool normalized;   // 标志输入是否被标准化(如处理无效日期)
};

关键函数:

  • TimeInfo MakeTimeInfo(int64_t y, int m, int d, int hh, int mm, int ss, const TimeZone& tz);
    • 返回对应民用时间的详细转换信息,包括是否唯一、被跳过或重复。
  • time_point MakeTime(int64_t y, int m, int d, int hh, int mm, int ss, const TimeZone& tz);
    • 直接返回对应的绝对时间(time_point),如果遇到歧义或跳过时间,通常返回标准化后的结果。

小技巧(Pro Tip)

推荐使用 cctz::MakeTime() 处理民用时间转绝对时间的转换,避免自己手写复杂的时区和夏令时处理逻辑。

这段代码展示了如何将民用时间转换成绝对时间,然后在不同的时区显示该时间。

代码分析

int main() {cctz::TimeZone lax;LoadTimeZone("America/Los_Angeles", &lax); // 加载洛杉矶时区(太平洋时间)// 创建一个表示 2015年9月22日 上午9点的时间点 tp,基于洛杉矶时区const cctz::time_point tp = cctz::MakeTime(2015, 9, 22, 9, 0, 0, lax);cctz::TimeZone nyc;LoadTimeZone("America/New_York", &nyc);  // 加载纽约时区(东部时间)// 输出在洛杉矶时区格式化的时间(09:00 PDT,-0700)std::cout << cctz::Format("Talk starts at %T %z (%Z)\n", tp, lax);// 输出相同时间点在纽约时区对应的民用时间(12:00 EDT,-0400)std::cout << cctz::Format("Talk starts at %T %z (%Z)\n", tp, nyc);
}

运行结果说明

Talk starts at 09:00:00 -0700 (PDT)  // 洛杉矶时间,夏令时,UTC-7
Talk starts at 12:00:00 -0400 (EDT)  // 纽约时间,夏令时,UTC-4
  • tp 是绝对时间点(time_point),它唯一表示了 2015-09-22 09:00:00 在洛杉矶时区的瞬间。
  • 当用不同时区(洛杉矶、纽约)格式化这个 time_point 时,显示对应的当地民用时间。
  • 纽约时间比洛杉矶时间早3小时,所以显示为中午12点。

总结

  • cctz::MakeTime 将民用时间+时区转换成绝对时间。
  • cctz::Format 将绝对时间+时区转换成当地民用时间字符串。
  • 这样就可以准确处理跨时区时间转换,避免手动计算时区偏移。

这段代码展示了如何从字符串解析民用时间,并转换成绝对时间点,然后与当前时间进行比较。

代码分析(中文注释)

int main() {const std::string civil_string = "2015-09-22 09:35:00";  // 待解析的时间字符串cctz::TimeZone lax;LoadTimeZone("America/Los_Angeles", &lax);  // 加载洛杉矶时区cctz::time_point tp;  // 定义一个time_point变量,存放解析结果// 解析字符串civil_string,格式为"%Y-%m-%d %H:%M:%S",结合洛杉矶时区// 解析成功则将绝对时间存到tpconst bool ok = cctz::Parse("%Y-%m-%d %H:%M:%S", civil_string, lax, &tp);if (!ok) return -1;  // 解析失败退出const auto now = std::chrono::system_clock::now();  // 获取当前系统时间// 判断当前时间是否超过解析时间,生成对应的提示字符串const std::string s = now > tp ? "running long!" : "on time!";std::cout << "Talk " << s << "\n";  // 输出结果
}

运行说明

  • 先将 "2015-09-22 09:35:00" 这个民用时间字符串,结合洛杉矶时区,解析为一个绝对时间 tp
  • 然后拿系统当前时间 now 和解析得到的时间 tp 比较。
  • 如果现在时间晚于 tp,输出 "running long!",否则输出 "on time!"
    示例输出:
Talk on time!

说明当前时间还没超过指定时间。

总结

  • cctz::Parse 可以将指定格式的时间字符串转换成指定时区的绝对时间。
  • 这样方便做时间的比较、计算和转换。
  • 避免了复杂的手动时区转换和字符串处理。

这段代码演示了如何使用 CCTZ(C++ Time Zone 库) 来实现“从现在起推 6 个月”的功能,同时在 洛杉矶时区 中进行正确的时区和夏令时(DST)处理。

int main() {cctz::TimeZone lax;LoadTimeZone("America/Los_Angeles", &lax);  // 加载洛杉矶时区const auto now = std::chrono::system_clock::now();  // 获取当前系统时间(绝对时间)const cctz::Breakdown bd = cctz::BreakTime(now, lax);// 将当前时间根据洛杉矶时区拆分成民用时间(年、月、日、时、分、秒)// 构造新的时间:当前月份加6,设为该月第一天凌晨0点const cctz::time_point then =cctz::MakeTime(bd.year, bd.month + 6, 1, 0, 0, 0, lax);// 输出当前时间std::cout << cctz::Format("Now: %F %T %z\n", now, lax);// 输出6个月后的时间std::cout << cctz::Format("6mo: %F %T %z\n", then, lax);
}

示例输出分析

Now: 2015-09-17 09:02:41 -0700
6mo: 2016-03-01 00:00:00 -0800
解释:
  • Now:当前是 2015 年 9 月 17 日,PDT(夏令时,UTC-7)。
  • 6mo:六个月后是 2016 年 3 月 1 日,PST(标准时间,UTC-8)。
亮点:
  • CCTZ 自动处理月份越界(例如 9 + 6 = 15,会变成 2016 年 3 月)。
  • 时区变化(PDT → PST)也正确处理,因为 DST 结束在 11 月初,3 月初还未开始夏令时。

总结

  • BreakTime():将绝对时间拆为年、月、日等民用时间。
  • MakeTime():构建新的时间点(time_point),自动处理月份进位与非法时间(如“2月30日”)。
  • CCTZ 能正确处理跨月、跨年、跨时区的各种复杂情况,适合做长期时间推演与计算。

示例 4:将时间“向下取整”到当日午夜(Floor to Midnight)

这个例子展示了如何使用 CCTZ 库 将某个时间点向下取整到当天的 00:00:00(午夜),即所谓的“Floor to Day”。

FloorDay() 函数
cctz::time_point FloorDay(cctz::time_point tp, cctz::TimeZone tz) {const cctz::Breakdown bd = cctz::BreakTime(tp, tz);const cctz::TimeInfo ti =cctz::MakeTimeInfo(bd.year, bd.month, bd.day, 0, 0, 0, tz);if (ti.kind == cctz::TimeInfo::Kind::SKIPPED) return ti.trans;return ti.pre;
}
步骤说明:
  1. BreakTime:将传入的 tp 拆成民用时间(年月日时分秒),按给定时区 tz
  2. MakeTimeInfo:尝试构造当天凌晨 00:00:00 的时间点,但这时考虑了时区转换和夏令时跳变问题。
  3. 判断 SKIPPED 情况(午夜因夏令时跳跃而“跳过”):返回跳变时间点 ti.trans
  4. 否则返回午夜前偏移 ti.pre(即有效的午夜时间点)。
示例代码说明:
int main() {cctz::TimeZone lax;LoadTimeZone("America/Los_Angeles", &lax); // 加载洛杉矶时区const auto now = std::chrono::system_clock::now(); // 当前绝对时间const auto day = FloorDay(now, lax); // 取整为当天午夜std::cout << cctz::Format("Now: %F %T %z\n", now, lax); // 输出当前时间std::cout << cctz::Format("Day: %F %T %z\n", day, lax); // 输出“当天 00:00:00”
}

示例输出分析:

Now: 2015-09-17 09:12:53 -0700
Day: 2015-09-17 00:00:00 -0700
  • Now 是当前时间(9:12 AM)
  • Day 是当天午夜(0:00 AM)
  • -0700 是 PDT(Pacific Daylight Time)时区的 UTC 偏移

总结

  • FloorDay() 是“向下舍入”到当天 00:00 的通用方法。
  • 能自动处理夏令时跳变(如某些地区会跳过午夜时间)。
  • 使用 CCTZ 可以轻松、准确地操作时区与民用时间之间的转换。

旧代码:手动偏移 + gmtime_r

void GetLocalTime(std::time_t t, const std::string& zone,int* hour, int* min, int* sec) {int off;CalcOffset(zone, t, &off);  // 计算时区偏移std::time_t adjusted_time = t + off;  // 加上偏移得到本地时间std::tm gm_tm;::gmtime_r(&adjusted_time, &gm_tm);  // 转换为 UTC 时间结构*hour = gm_tm.tm_hour;*min = gm_tm.tm_min;*sec = gm_tm.tm_sec;
}

可用
错误率高,处理夏令时不准确
不易维护,可移植性差

新代码(使用 CCTZ 库)

void GetLocalTime(std::time_t t, const std::string& zone,int* hour, int* min, int* sec) {cctz::TimeZone tz;cctz::LoadTimeZone(zone, &tz);  // 加载时区const cctz::time_point tp = std::chrono::system_clock::from_time_t(t);  // 转换为 CCTZ 时间点const cctz::Breakdown bd = cctz::BreakTime(tp, tz);  // 拆解时间*hour = bd.hour;*min = bd.minute;*sec = bd.second;
}

自动处理夏令时
使用现代库接口
更加健壮和可读

更好版本:将时间和时区作为参数传入

void GetLocalTime(cctz::time_point tp, cctz::TimeZone tz,int* hour, int* min, int* sec) {const cctz::Breakdown bd = cctz::BreakTime(tp, tz);*hour = bd.hour;*min = bd.minute;*sec = bd.second;
}

结构更清晰
让调用者负责转换时间和加载时区,提升灵活性和复用性

最佳版本:最终形态

void GetLocalTime(cctz::time_point tp, cctz::TimeZone tz,int* hour, int* min, int* sec) {const cctz::Breakdown bd = cctz::BreakTime(tp, tz);*hour = bd.hour;*min = bd.minute;*sec = bd.second;
}

专业提示:你只需要调用:

cctz::BreakTime(tp, tz);

即可直接获取带有时区转换的本地时间,无需手动计算偏移量。

总结表格

版本优点缺点
旧代码实现简单容易出错、不支持夏令时
新代码标准、健壮初期学习成本略高
更好版本清晰、可复用需要调用者预处理
最佳版本简洁、强大依赖现代库结构

Final ThoughtsPro Tips 是 CCTZ 使用与时区处理的核心理念,非常值得深入理解和实践。下面是每一点的中文解释与背景,帮助你更好地“理解”这些专业建议:

总体建议:使用正确的心智模型与术语

Mental Model(心智模型)

处理时间时,应当始终明确:

  • “绝对时间”是指不依赖时区的时间点(如 UNIX 时间戳或 cctz::time_point)。
  • “本地时间”(civil time)是指人类可读、带有时区上下文的时间(年、月、日、小时等)。
  • 二者间的转换由 时区(TimeZone) 决定,而不是自己手动加减偏移量。

Proper Vocabulary(术语准确)

  • 使用 “UTC” 而不是 “GMT”:

    “UTC” 是标准协调时间,比 “GMT” 更标准、现代、精确。

  • 区分以下术语:

    • Absolute Time:时间点,如 std::chrono::system_clock::time_point
    • Time Zone:如 "America/New_York"
    • Civil Time:可读的本地时间结构,如 cctz::Breakdown

Absolute Time

使用 "%z" 输出绝对时间(含时区偏移)

cctz::Format("%Y-%m-%d %H:%M:%S %z", tp, tz);
  • %z 表示输出格式中带有偏移量(如 +0800),这非常适合日志或跨系统通信。

Time Zone & Civil Time

不要手动计算 UTC 偏移

  • 不要用 time_t 加减偏移量:
    t + offset  // ✗ 错误做法
    
  • 避免 “epoch shifting” —— 它无法处理夏令时变化和历史偏移。

cctz::Breakdown 拆解时间

  • CCTZ 自动处理闰秒、夏令时、历史规则等:
    cctz::Breakdown bd = cctz::BreakTime(tp, tz);
    
  • 然后你就可以使用 bd.year, bd.month, bd.day 等字段做本地时间相关的日历计算。

在 Civil Time 域中进行日历计算

  • 如果你要做“日 +1”、“小时 -5”等计算,必须在本地时间(Civil Time)中做
    auto civil = cctz::convert(tp, tz);
    civil = cctz::civil_day(civil) + 1;  // 正确做法
    

总结:牢记这些关键点

建议含义
使用心智模型分清时间点 vs 本地时间 vs 时区
使用 UTC 术语更标准、可读、跨系统兼容
不做 time_t 加减会误导、错误,难以维护
用 cctz::Breakdown安全可靠,自动处理复杂规则
在 Civil 域做计算更贴近日常需求,易理解
如果你正在设计涉及跨时区、跨日历的系统(如调度、日志分析、全球化应用),这些原则是保证正确性和可维护性的基石。

相关文章:

  • UML 2.0 图的细分类别及其应用
  • 【大厂机试题解法笔记】食堂供餐
  • 如何删除linux空的文件夹
  • deepbayes lecture2:变分推断
  • “详规一张图”——新加坡土地利用数据
  • Open3D 对点云进行去噪(下采样、欧式聚类分割)01
  • 基于算法竞赛的c++编程(25)指针简单介绍和简单应用
  • 【Vue】scoped+组件通信+props校验
  • DingDing机器人群消息推送
  • 二维FDTD算法仿真
  • JVM如何优化
  • Qt学习及使用_第1部分_认识Qt---Qt开发基本流程
  • AirPosture | 通过 AirPods 矫正坐姿
  • while/do while/for循环几个小细节
  • 免费数学几何作图web平台
  • React中子传父组件通信操作指南
  • JavaScript的ArrayBuffer与C++的malloc():两种内存管理方式的深度对比
  • Linux进程信号(一)
  • LLMs 系列实操科普(2)
  • Spring Boot面试题精选汇总
  • 兰州网站seo外包/免费网站搭建平台
  • php网站开发教案/网站seo优化推广外包
  • 宜飞思工业设计网站/seo与sem的区别与联系
  • 最近中文字幕mv在线视频/口碑seo推广公司
  • 微信赌博链接网站建设/aso优化师主要是干嘛的
  • 网站建设人员/商丘关键词优化推广