解析muduo源码之 TimeZone.h & TimeZone.cc

目录

[一、 TimeZone.h](#一、 TimeZone.h)

[1. TimeZone.h 整体定位](#1. TimeZone.h 整体定位)

[2. 逐模块拆解代码](#2. 逐模块拆解代码)

[3. 核心使用场景示例](#3. 核心使用场景示例)

[4. 设计思想](#4. 设计思想)

[5. 总结](#5. 总结)

[二、 TimeZone.cc](#二、 TimeZone.cc)

[1. 代码整体定位](#1. 代码整体定位)

[2. 核心数据结构解析(Pimpl 模式的 Data 结构体)](#2. 核心数据结构解析(Pimpl 模式的 Data 结构体))

[3. 辅助工具模块(detail 命名空间)](#3. 辅助工具模块(detail 命名空间))

[4. 核心逻辑:时区文件解析(readTimeZoneFile/readDataBlock)](#4. 核心逻辑:时区文件解析(readTimeZoneFile/readDataBlock))

[5. 核心查找逻辑:findLocalTime(处理夏令时)](#5. 核心查找逻辑:findLocalTime(处理夏令时))

[6. 核心接口实现:toLocalTime/fromLocalTime](#6. 核心接口实现:toLocalTime/fromLocalTime)

[7. 常用时区构造:UTC ()/China ()](#7. 常用时区构造:UTC ()/China ())

总结


一、 TimeZone.h

先贴出完整代码,再逐部分解释:

cpp 复制代码
// 本源代码的使用受 BSD 风格许可证约束
// 该许可证可在 License 文件中查阅。
//
// 作者:陈硕 (chenshuo at chenshuo dot com)

#ifndef MUDUO_BASE_TIMEZONE_H
#define MUDUO_BASE_TIMEZONE_H

#include "muduo/base/copyable.h"  // 可拷贝基类(语义标记)
#include "muduo/base/Types.h"     // 基础类型定义
#include <memory>                 // 智能指针(unique_ptr/shared_ptr)
#include <time.h>                 // 时间相关结构体(tm)

namespace muduo
{

// 未指定时区的本地时间结构体
// 约束:1分钟固定为60秒,不考虑闰秒(简化时间计算)
struct DateTime
{
  // 默认构造函数(值初始化为0)
  DateTime() {}
  // 从 tm 结构体构造 DateTime
  explicit DateTime(const struct tm&);
  // 显式构造函数:指定年/月/日/时/分/秒
  DateTime(int _year, int _month, int _day, int _hour, int _minute, int _second)
      : year(_year), month(_month), day(_day), hour(_hour), minute(_minute), second(_second)
  {
  }

  // 转换为 ISO 格式字符串(如:"2011-12-31 12:34:56")
  string toIsoString() const;

  int year = 0;     // 年份范围 [1900, 2500]
  int month = 0;    // 月份范围 [1, 12](1=1月,12=12月)
  int day = 0;      // 日期范围 [1, 31](根据月份自动适配)
  int hour = 0;     // 小时范围 [0, 23](24小时制)
  int minute = 0;   // 分钟范围 [0, 59]
  int second = 0;   // 秒数范围 [0, 59](不考虑闰秒)
};

// 时区类(支持 1970~2100 年的时区转换)
class TimeZone : public muduo::copyable
{
 public:
  // 默认构造函数:创建一个**无效的**时区对象
  TimeZone() = default;
  // 构造固定偏移的时区(无夏令时)
  // @param eastOfUtc 相对于 UTC 的东偏移量(单位:秒,如 GMT+8 为 8*3600=28800)
  // @param tzname 时区名称(如 "CST")
  TimeZone(int eastOfUtc, const char* tzname);

  // 获取 UTC 时区对象(偏移量 0)
  static TimeZone UTC();
  // 获取中国时区对象(固定 GMT+8,无夏令时 DST)
  static TimeZone China();
  // 从时区文件加载时区规则(支持夏令时等动态规则)
  // @param zonefile 时区文件路径(如 /usr/share/zoneinfo/Asia/Shanghai)
  static TimeZone loadZoneFile(const char* zonefile);

  // 默认的拷贝构造/赋值/析构函数均适用(无需自定义)
  // 注:data_ 是 shared_ptr,拷贝时仅增加引用计数,效率高

  // 判断时区对象是否有效
  // 注:C++11 中可通过 'explicit operator bool() const' 实现隐式布尔转换
  bool valid() const
  {
    return static_cast<bool>(data_);
  }

  // 将 UTC 时间戳(从 epoch 开始的秒数)转换为当前时区的本地时间
  // @param secondsSinceEpoch UTC 时间戳(秒级)
  // @param utcOffset [输出参数] 可选,返回当前时区相对于 UTC 的偏移量(秒)
  // @return 转换后的本地时间(DateTime 结构体)
  struct DateTime toLocalTime(int64_t secondsSinceEpoch, int* utcOffset = nullptr) const;
  
  // 将当前时区的本地时间转换为 UTC 时间戳(从 epoch 开始的秒数)
  // @param dt 本地时间(DateTime 结构体)
  // @param postTransition 夏令时切换时,是否使用切换后的规则(默认 false)
  // @return 转换后的 UTC 时间戳(秒级)
  int64_t fromLocalTime(const struct DateTime&, bool postTransition = false) const;

  // 静态方法:将 UTC 时间戳转换为 UTC 时间(等价于 gmtime(3) 功能)
  // @param secondsSinceEpoch UTC 时间戳(秒级)
  // @return UTC 时间(DateTime 结构体)
  static struct DateTime toUtcTime(int64_t secondsSinceEpoch);
  
  // 静态方法:将 UTC 时间转换为 UTC 时间戳(等价于 timegm(3) 功能)
  // @param dt UTC 时间(DateTime 结构体)
  // @return UTC 时间戳(秒级)
  static int64_t fromUtcTime(const struct DateTime&);

  // 前向声明:时区核心数据结构(具体实现隐藏在 cpp 文件中,封装细节)
  struct Data;

 private:
  // 私有构造函数:从智能指针创建时区对象(仅内部/友元使用)
  explicit TimeZone(std::unique_ptr<Data> data);

  // 时区核心数据(共享指针,支持高效拷贝)
  std::shared_ptr<Data> data_;

  // 测试友元类:允许单元测试访问私有成员
  friend class TimeZoneTestPeer;
};

}  // namespace muduo

#endif  // MUDUO_BASE_TIMEZONE_H
1. TimeZone.h 整体定位

TimeZone 类是 Muduo 时间处理体系的时区层,核心解决「UTC 时间 ↔ 本地时间(不同时区)」的转换问题:

  • 弥补了 Timestamp(纯 UTC 微秒时间戳)和 Date(纯 UTC 日期)的「时区缺失」问题;
  • 支持两种时区模式:固定偏移时区 (如中国 GMT+8,无夏令时)和时区文件加载(如欧美含夏令时的复杂时区);
  • 限定时间范围 1970~2100 年,覆盖绝大多数业务场景,简化时区规则的实现。
2. 逐模块拆解代码

(1)DateTime 结构体:本地时间容器

cpp 复制代码
// 无特定时区的本地时间结构体(仅存年月日时分秒,无时区/微秒)
// 注意:分钟固定为60秒,不处理闰秒(符合日常业务场景)
struct DateTime
{
  // 空构造:初始值全0(无效时间)
  DateTime() {}
  // 从系统tm结构体构造(对接系统时间接口)
  explicit DateTime(const struct tm&);
  // 显式构造:指定年月日时分秒(符合人类习惯)
  DateTime(int _year, int _month, int _day, int _hour, int _minute, int _second)
      : year(_year), month(_month), day(_day), hour(_hour), minute(_minute), second(_second)
  {
  }

  // 转为ISO标准字符串:"2011-12-31 12:34:56"(日志/展示常用)
  string toIsoString() const;

  // 成员变量:标注合法范围,避免无效值
  int year = 0;     // [1900, 2500]
  int month = 0;    // [1, 12]
  int day = 0;      // [1, 31]
  int hour = 0;     // [0, 23]
  int minute = 0;   // [0, 59]
  int second = 0;   // [0, 59]
};

核心解释

  • DateTime vs Date/Timestamp

    类型 核心特征 时区属性
    Date 仅年月日(UTC) 仅 UTC
    Timestamp 微秒级时间戳(UTC) 仅 UTC
    DateTime 年月日时分秒(本地时间) 无(需结合 TimeZone)
  • 无闰秒设计:注释明确 "a minute is always 60 seconds",因为闰秒在业务场景中极少用到,简化实现且避免复杂度;

  • ISO 字符串格式:toIsoString() 返回 YYYY-MM-DD HH:MM:SS,是日志、网络协议中最通用的时间格式。

(2)TimeZone 类的基础设计(Pimpl 模式 + 智能指针)

cpp 复制代码
class TimeZone : public muduo::copyable
{
 public:
  // 默认构造:无效时区(data_为空)
  TimeZone() = default;  
  // 构造1:固定偏移时区(如GMT+8 → eastOfUtc=8*3600),tzname为时区名(如"CST")
  TimeZone(int eastOfUtc, const char* tzname);  

  // 预定义常用时区:UTC(偏移0)、中国(GMT+8,无夏令时)
  static TimeZone UTC();
  static TimeZone China();  
  // 构造2:加载时区文件(如/etc/localtime),支持含夏令时的复杂时区
  static TimeZone loadZoneFile(const char* zonefile);

  // 判断时区是否有效:通过data_是否非空(C++11 explicit operator bool的兼容写法)
  bool valid() const { return static_cast<bool>(data_); }

  // 核心接口:UTC ↔ 本地时间转换
  struct DateTime toLocalTime(int64_t secondsSinceEpoch, int* utcOffset = nullptr) const;
  int64_t fromLocalTime(const struct DateTime&, bool postTransition = false) const;

  // 静态工具:UTC时间 ↔ DateTime转换(无需时区,对接gmtime/timegm)
  static struct DateTime toUtcTime(int64_t secondsSinceEpoch);
  static int64_t fromUtcTime(const struct DateTime&);

  // 前向声明:时区核心数据(Pimpl模式,隐藏实现细节)
  struct Data;

 private:
  // 私有构造:接收Data的unique_ptr,封装实现细节
  explicit TimeZone(std::unique_ptr<Data> data);

  // 核心成员:共享时区数据(copyable的同时避免深拷贝)
  std::shared_ptr<Data> data_;

  // 测试友元:方便单元测试访问私有成员
  friend class TimeZoneTestPeer;
};

核心设计细节解析

① Pimpl 模式(Pointer to Implementation)

  • struct Data 前向声明,具体实现放在 .cpp 文件中:
    • 降低头文件依赖:头文件无需暴露时区数据的具体结构(如夏令时规则、偏移量表),编译更快;
    • 隔离实现变更:修改时区数据结构时,只需重新编译 .cpp,无需重新编译所有包含 TimeZone.h 的文件。

std::shared_ptr<Data> 的选择(关键!)

  • TimeZone 继承 copyable(可拷贝),但时区数据(如偏移量表、夏令时规则)可能较大,若深拷贝会有性能开销;
  • shared_ptr<Data> 实现数据共享 :多个 TimeZone 对象拷贝时,仅拷贝指针,共享同一份时区数据,兼顾 copyable 语义和性能;
  • 对比 unique_ptr:私有构造用 unique_ptr(独占数据),成员用 shared_ptr(共享数据),分工明确。

③ 两种时区构造方式(覆盖不同场景)

构造方式 适用场景 特点
固定偏移(eastOfUtc) 简单时区(如中国 GMT+8、UTC),无夏令时 实现简单,效率高
加载时区文件 复杂时区(如欧美,含夏令时切换) 兼容完整时区规则,稍复杂

(3)核心接口:时区转换

接口 作用 关键参数 / 说明
toLocalTime UTC 秒数 → 本地时间(DateTime) utcOffset:可选输出当前 UTC 偏移量(秒),比如中国时区返回 28800(8*3600)
fromLocalTime 本地时间(DateTime)→ UTC 秒数 postTransition:处理夏令时切换时的歧义时间(比如凌晨 2 点回拨 1 小时,同一个本地时间对应两个 UTC 时间,该参数指定取切换后的值)
toUtcTime UTC 秒数 → UTC 时间(DateTime) 等价于 gmtime,无需时区,纯 UTC 转换
fromUtcTime UTC 时间(DateTime)→ UTC 秒数 等价于 timegm(反向 gmtime),Linux 下需手动实现(无标准库)
3. 核心使用场景示例
cpp 复制代码
#include "muduo/base/TimeZone.h"
#include "muduo/base/Timestamp.h"
#include <iostream>

int main() {
  // 1. 获取中国时区(GMT+8,无夏令时)
  muduo::TimeZone cst = muduo::TimeZone::China();
  if (!cst.valid()) {
    std::cerr << "无效时区" << std::endl;
    return 1;
  }

  // 2. 获取当前UTC秒数(从Timestamp转换)
  muduo::Timestamp now = muduo::Timestamp::now();
  int64_t utc_seconds = now.secondsSinceEpoch();

  // 3. UTC → 中国本地时间
  muduo::DateTime local = cst.toLocalTime(utc_seconds);
  std::cout << "中国本地时间:" << local.toIsoString() << std::endl;

  // 4. 中国本地时间 → UTC秒数
  int64_t utc_seconds2 = cst.fromLocalTime(local);
  std::cout << "转回UTC秒数:" << utc_seconds2 << "(和原数是否相等:" << (utc_seconds == utc_seconds2) << ")" << std::endl;

  // 5. UTC时间 ↔ DateTime(无需时区)
  muduo::DateTime utc_dt = muduo::TimeZone::toUtcTime(utc_seconds);
  std::cout << "UTC时间:" << utc_dt.toIsoString() << std::endl;

  return 0;
}

输出示例(假设 UTC 时间为 2025-01-20 12:00:00):

cpp 复制代码
中国本地时间:2025-01-20 20:00:00
转回UTC秒数:1737345600(和原数是否相等:1)
UTC时间:2025-01-20 12:00:00
4. 设计思想
  • 分层设计:将「时间存储(Timestamp/Date)」和「时区转换(TimeZone)」解耦,Timestamp/Date 专注 UTC 时间,TimeZone 专注时区转换,符合单一职责原则;
  • 性能与易用性平衡shared_ptr 实现数据共享,固定时区快速构造,时区文件兼容复杂场景;
  • Pimpl 模式:隔离实现细节,降低编译依赖,提升代码可维护性;
  • 业务友好:忽略闰秒、限定时间范围 1970~2100,聚焦主流业务场景,避免过度设计。
5. 总结
  • DateTime 是「本地时间容器」,封装年月日时分秒,无时区信息,用于和 TimeZone 配合完成时区转换;
  • TimeZone 采用 Pimpl 模式 + shared_ptr,实现「可拷贝、低拷贝开销、隐藏实现细节」,支持固定偏移和时区文件两种构造方式;
  • 核心功能是「UTC 秒数 ↔ 本地时间(DateTime)」的双向转换,补全了 Muduo 时间处理体系的时区能力,适配不同地区的时间展示 / 计算需求。

二、 TimeZone.cc

先贴出完整代码,再逐部分解释:

cpp 复制代码
// 本源代码的使用受 BSD 风格许可证约束
// 该许可证可在 License 文件中查阅。
//
// 作者:陈硕 (chenshuo at chenshuo dot com)

#include "muduo/base/TimeZone.h"
#include "muduo/base/noncopyable.h"  // 不可拷贝基类
#include "muduo/base/Date.h"         // 日期处理类

#include <algorithm>   // 提供 upper_bound 等算法
#include <memory>      // 智能指针
#include <stdexcept>   // 异常类(logic_error)
#include <string>      // 字符串
#include <vector>      // 动态数组

#include <assert.h>    // 断言
//#define _BSD_SOURCE   // 启用 BSD 扩展函数(如 be64toh)
#include <endian.h>    // 字节序转换函数(be64toh/be32toh)

#include <stdint.h>    // 固定宽度整数类型
#include <stdio.h>     // 文件操作(fopen/fread/fseek 等)

using namespace muduo;

// TimeZone 类的核心数据结构(封装时区规则,对外隐藏实现)
struct TimeZone::Data
{
  // 时区切换点结构体(如夏令时开始/结束的时间点)
  struct Transition
  {
    int64_t utctime;      // 切换点的 UTC 时间(从 epoch 开始的秒数)
    int64_t localtime;    // 切换点的本地时间(偏移后的 epoch 秒数)
    int localtimeIdx;     // 对应 LocalTime 数组的索引

    Transition(int64_t t, int64_t l, int localIdx)
        : utctime(t), localtime(l), localtimeIdx(localIdx)
    { }
  };

  // 本地时间规则结构体(描述某一时区规则的属性)
  struct LocalTime
  {
    int32_t utcOffset;  // 相对于 UTC 的东偏移量(秒,如 GMT+8 为 28800)
    bool isDst;         // 是否为夏令时(DST)模式
    int desigIdx;       // 时区缩写在 abbreviation 字符串中的索引

    LocalTime(int32_t offset, bool dst, int idx)
        : utcOffset(offset), isDst(dst), desigIdx(idx)
    { }
  };

  // 添加一条本地时间规则到数组
  void addLocalTime(int32_t utcOffset, bool isDst, int desigIdx)
  {
    localtimes.push_back(LocalTime(utcOffset, isDst, desigIdx));
  }

  // 添加一个时区切换点
  // @param utcTime 切换点的 UTC 时间(秒);localtimeIdx 对应 LocalTime 索引
  void addTransition(int64_t utcTime, int localtimeIdx)
  {
    LocalTime lt = localtimes.at(localtimeIdx); // 取出对应的本地时间规则
    // 切换点的本地时间 = UTC 时间 + 偏移量
    transitions.push_back(Transition(utcTime, utcTime + lt.utcOffset, localtimeIdx));
  }

  // 根据 UTC 时间查找对应的本地时间规则
  const LocalTime* findLocalTime(int64_t utcTime) const;
  // 根据本地时间查找对应的本地时间规则(处理夏令时切换的歧义)
  const LocalTime* findLocalTime(const struct DateTime& local, bool postTransition) const;

  // 比较器:按 UTC 时间升序排序 Transition
  struct CompareUtcTime
  {
    bool operator()(const Transition& lhs, const Transition& rhs) const
    {
      return lhs.utctime < rhs.utctime;
    }
  };

  // 比较器:按本地时间升序排序 Transition
  struct CompareLocalTime
  {
    bool operator()(const Transition& lhs, const Transition& rhs) const
    {
      return lhs.localtime < rhs.localtime;
    }
  };

  std::vector<Transition> transitions;  // 所有时区切换点(按 UTC 时间排序)
  std::vector<LocalTime> localtimes;    // 所有本地时间规则
  string abbreviation;                  // 时区缩写字符串(如 "CST/CDT")
  string tzstring;                      // 时区完整描述字符串
};

namespace muduo
{

// 常量:一天的秒数(24*60*60 = 86400)
const int kSecondsPerDay = 24*60*60;

namespace detail
{

// 文件操作封装类(不可拷贝,简化二进制文件读取)
class File : noncopyable
{
 public:
  // 构造函数:打开指定二进制文件
  File(const char* file)
    : fp_(::fopen(file, "rb"))  // "rb" 以二进制模式打开
  {
  }

  // 析构函数:关闭文件(自动释放资源)
  ~File()
  {
    if (fp_)
    {
      ::fclose(fp_);
    }
  }

  // 判断文件是否成功打开
  bool valid() const { return fp_; }

  // 读取指定字节数的二进制数据(失败抛异常)
  string readBytes(int n)
  {
    char buf[n];
    ssize_t nr = ::fread(buf, 1, n, fp_); // 每次读 1 字节,共读 n 次
    if (nr != n)
      throw std::logic_error("数据不足"); // 读取字节数不符,抛异常
    return string(buf, n);
  }

  // 读取文件剩余所有数据(直到 EOF)
  string readToEnd()
  {
    char buf[4096];  // 栈缓冲区,减少堆分配
    string result;
    ssize_t nr = 0;
    while ( (nr = ::fread(buf, 1, sizeof buf, fp_)) > 0)
    {
      result.append(buf, nr); // 追加读取到的数据
    }
    return result;
  }

  // 读取 64 位整数(网络字节序转主机字节序)
  int64_t readInt64()
  {
    int64_t x = 0;
    ssize_t nr = ::fread(&x, 1, sizeof(int64_t), fp_);
    if (nr != sizeof(int64_t))
      throw std::logic_error("无效的 int64_t 数据");
    return be64toh(x); // 大端(网络序)转主机序
  }

  // 读取 32 位整数(网络字节序转主机字节序)
  int32_t readInt32()
  {
    int32_t x = 0;
    ssize_t nr = ::fread(&x, 1, sizeof(int32_t), fp_);
    if (nr != sizeof(int32_t))
      throw std::logic_error("无效的 int32_t 数据");
    return be32toh(x); // 大端转主机序
  }

  // 读取 8 位无符号整数
  uint8_t readUInt8()
  {
    uint8_t x = 0;
    ssize_t nr = ::fread(&x, 1, sizeof(uint8_t), fp_);
    if (nr != sizeof(uint8_t))
      throw std::logic_error("无效的 uint8_t 数据");
    return x;
  }

  // 跳过指定字节数(移动文件指针)
  off_t skip(ssize_t bytes)
  {
    return ::fseek(fp_, bytes, SEEK_CUR); // 从当前位置偏移 bytes 字节
  }

 private:
  FILE* fp_;  // 文件指针(NULL 表示打开失败)
};

// 读取 TZif 格式的时区数据块(RFC 8536 标准:https://www.rfc-editor.org/rfc/rfc8536.html)
// @param f 已打开的文件对象;data 存储解析结果;v1 是否为 TZif v1 版本(v1 用 32 位时间,v2+ 用 64 位)
bool readDataBlock(File& f, struct TimeZone::Data* data, bool v1)
{
  const int time_size = v1 ? sizeof(int32_t) : sizeof(int64_t); // 时间字段的字节数
  const int32_t isutccnt = f.readInt32();  // UTC 标志数量
  const int32_t isstdcnt = f.readInt32();  // 标准时间标志数量
  const int32_t leapcnt = f.readInt32();   // 闰秒记录数量
  const int32_t timecnt = f.readInt32();   // 切换点时间数量
  const int32_t typecnt = f.readInt32();   // 本地时间规则数量
  const int32_t charcnt = f.readInt32();   // 时区缩写字符串长度

  // 简化处理:不支持闰秒、UTC/标准时间标志数量需与规则数一致
  if (leapcnt != 0)
    return false;
  if (isutccnt != 0 && isutccnt != typecnt)
    return false;
  if (isstdcnt != 0 && isstdcnt != typecnt)
    return false;

  // 读取所有切换点时间(v1 读 32 位,v2+ 读 64 位)
  std::vector<int64_t> trans;
  trans.reserve(timecnt); // 预分配空间,提升效率
  for (int i = 0; i < timecnt; ++i)
  {
    if (v1)
    {
      trans.push_back(f.readInt32());
    }
    else
    {
      trans.push_back(f.readInt64());
    }
  }

  // 读取每个切换点对应的本地时间规则索引
  std::vector<int> localtimes;
  localtimes.reserve(timecnt);
  for (int i = 0; i < timecnt; ++i)
  {
    uint8_t local = f.readUInt8();
    localtimes.push_back(local);
  }

  // 读取所有本地时间规则
  data->localtimes.reserve(typecnt);
  for (int i = 0; i < typecnt; ++i)
  {
    int32_t gmtoff = f.readInt32();  // UTC 偏移量(秒)
    uint8_t isdst = f.readUInt8();   // 是否夏令时
    uint8_t abbrind = f.readUInt8(); // 时区缩写索引

    data->addLocalTime(gmtoff, isdst, abbrind);
  }

  // 构建所有时区切换点
  for (int i = 0; i < timecnt; ++i)
  {
    int localIdx = localtimes[i];
    data->addTransition(trans[i], localIdx);
  }

  // 读取时区缩写字符串
  data->abbreviation = f.readBytes(charcnt);
  // 跳过闰秒数据、UTC/标准时间标志
  f.skip(leapcnt * (time_size + 4));
  f.skip(isstdcnt);
  f.skip(isutccnt);

  if (!v1)
  {
    // FIXME: 读取到下一个换行符(TZif v2+ 包含注释行)
    data->tzstring = f.readToEnd();
  }

  return true;
}

// 读取 TZif 格式的时区文件(如 /usr/share/zoneinfo/Asia/Shanghai)
// @param zonefile 时区文件路径;data 存储解析结果
bool readTimeZoneFile(const char* zonefile, struct TimeZone::Data* data)
{
  File f(zonefile); // 打开时区文件
  if (f.valid())
  {
    try
    {
      // 读取文件头(4 字节 "TZif")
      string head = f.readBytes(4);
      if (head != "TZif")
        throw std::logic_error("无效的文件头");
      string version = f.readBytes(1); // 版本号(空=v1,2=v2,3=v3)
      f.readBytes(15); // 跳过剩余 15 字节保留字段

      // 读取 v1 版本的计数器(isgmtcnt/isstdcnt/leapcnt/timecnt/typecnt/charcnt)
      const int32_t isgmtcnt = f.readInt32();
      const int32_t isstdcnt = f.readInt32();
      const int32_t leapcnt = f.readInt32();
      const int32_t timecnt = f.readInt32();
      const int32_t typecnt = f.readInt32();
      const int32_t charcnt = f.readInt32();

      if (version == "2") // TZif v2 版本(包含 64 位时间)
      {
        // 跳过 v1 版本的旧数据块
        size_t skip = sizeof(int32_t) * timecnt + timecnt + 6 * typecnt +
            charcnt +  8 * leapcnt + isstdcnt + isgmtcnt;
        f.skip(skip);

        // 读取 v2 版本的文件头
        head = f.readBytes(4);
        if (head != "TZif")
          throw std::logic_error("无效的 v2 文件头");
        f.skip(16); // 跳过版本号+保留字段
        return readDataBlock(f, data, false); // 解析 64 位时间的 v2 数据块
      }
      else // TZif v1 版本(32 位时间)
      {
        // TODO: 用真实的 v1 文件测试
        f.skip(-4 * 6); // 回退到计数器位置(重新读取)
        return readDataBlock(f, data, true); // 解析 32 位时间的 v1 数据块
      }
    }
    catch (std::logic_error& e) // 捕获解析异常,打印错误信息
    {
      fprintf(stderr, "%s\n", e.what());
    }
  }
  return false;
}

// 辅助函数:将一天内的秒数拆分为时/分/秒,填充到 DateTime 结构体
inline void fillHMS(unsigned seconds, struct DateTime* dt)
{
  dt->second = seconds % 60;          // 秒(0-59)
  unsigned minutes = seconds / 60;    // 总分钟数
  dt->minute = minutes % 60;          // 分(0-59)
  dt->hour = minutes / 60;            // 时(0-23)
}

// 将 epoch 秒数转换为 DateTime 结构体(UTC 时间)
// @param t 从 epoch 开始的秒数
DateTime BreakTime(int64_t t)
{
  struct DateTime dt;
  int seconds = static_cast<int>(t % kSecondsPerDay); // 当天的秒数
  int days = static_cast<int>(t / kSecondsPerDay);    // 从 epoch 开始的天数

  // C++11 中负数取模结果向零取整,需修正(如 -1 秒 → 86399 秒,天数-1)
  if (seconds < 0)
  {
    seconds += kSecondsPerDay;
    --days;
  }

  // 拆分时/分/秒
  detail::fillHMS(seconds, &dt);
  // 计算年月日(基于儒略日)
  Date date(days + Date::kJulianDayOf1970_01_01);
  Date::YearMonthDay ymd = date.yearMonthDay();
  dt.year = ymd.year;
  dt.month = ymd.month;
  dt.day = ymd.day;

  return dt;
}

}  // namespace detail

}  // namespace muduo

// 根据 UTC 时间查找对应的本地时间规则
const TimeZone::Data::LocalTime* TimeZone::Data::findLocalTime(int64_t utcTime) const
{
  const LocalTime* local = NULL;

  // 示例数据(中国曾短暂使用的夏令时切换点):
  // 行  UTC 时间              是否夏令时  偏移量  本地时间(中国)
  //  1  1989-09-16 17:00:00Z   0      8.0   1989-09-17 01:00:00
  //  2  1990-04-14 18:00:00Z   1      9.0   1990-04-15 03:00:00
  //  3  1990-09-15 17:00:00Z   0      8.0   1990-09-16 01:00:00
  //  4  1991-04-13 18:00:00Z   1      9.0   1991-04-14 03:00:00
  //  5  1991-09-14 17:00:00Z   0      8.0   1991-09-15 01:00:00

  // 输入 '1990-06-01 00:00:00Z',std::upper_bound 返回第 3 行,
  // 因此输入时间属于第 2 行的规则,偏移量 9 小时,本地时间为 1990-06-01 09:00:00

  // 无切换点 或 UTC 时间早于第一个切换点:使用第一条规则(非夏令时)
  if (transitions.empty() || utcTime < transitions.front().utctime)
  {
    // FIXME: 应使用第一条非夏令时规则
    local = &localtimes.front();
  }
  else
  {
    // 创建哨兵对象,用于二分查找
    Transition sentry(utcTime, 0, 0);
    // 二分查找第一个大于 utcTime 的切换点
    std::vector<Transition>::const_iterator transI =
        std::upper_bound(transitions.begin(), transitions.end(), sentry, CompareUtcTime());
    assert(transI != transitions.begin()); // 确保至少有一个切换点

    if (transI != transitions.end())
    {
      --transI; // 回退到小于等于 utcTime 的最后一个切换点
      local = &localtimes[transI->localtimeIdx]; // 取对应的本地时间规则
    }
    else
    {
      // UTC 时间晚于所有切换点:使用最后一条规则(FIXME: 可读取 TZ 环境变量)
      local = &localtimes[transitions.back().localtimeIdx];
    }
  }

  return local;
}

// 根据本地时间查找对应的本地时间规则(处理夏令时切换的歧义)
// @param lt 本地时间;postTransition 夏令时切换时是否使用切换后的规则
const TimeZone::Data::LocalTime* TimeZone::Data::findLocalTime(
    const struct DateTime& lt, bool postTransition) const
{
  // 将本地时间转换为 epoch 秒数(先假设为 UTC 时间)
  const int64_t localtime = fromUtcTime(lt);

  // 无切换点 或 本地时间早于第一个切换点:使用第一条规则
  if (transitions.empty() || localtime < transitions.front().localtime)
  {
    // FIXME: 应使用第一条非夏令时规则
    return &localtimes.front();
  }

  // 创建哨兵对象,按本地时间二分查找
  Transition sentry(0, localtime, 0);
  std::vector<Transition>::const_iterator transI =
      std::upper_bound(transitions.begin(), transitions.end(), sentry, CompareLocalTime());
  assert(transI != transitions.begin());

  if (transI == transitions.end())
  {
    // 本地时间晚于所有切换点:使用最后一条规则(FIXME: 可读取 TZ 环境变量)
    return &localtimes[transitions.back().localtimeIdx];
  }

  // 获取当前切换点的前一个切换点
  Transition prior_trans = *(transI - 1);
  // 计算前一个切换点结束前的最后一秒的本地时间
  int64_t prior_second = transI->utctime - 1 + localtimes[prior_trans.localtimeIdx].utcOffset;

  // 示例数据(含夏令时切换的时间跳变/重复):
  // 行  UTC 时间              是否夏令时  偏移量  本地时间(中国)     前一切换点最后一秒的本地时间
  //  1  1989-09-16 17:00:00Z   0      8.0   1989-09-17 01:00:00
  //  2  1990-04-14 18:00:00Z   1      9.0   1990-04-15 03:00:00  1990-04-15 01:59:59
  //  3  1990-09-15 17:00:00Z   0      8.0   1990-09-16 01:00:00  1990-09-16 01:59:59
  //  4  1991-04-13 18:00:00Z   1      9.0   1991-04-14 03:00:00  1991-04-14 01:59:59
  //  5  1991-09-14 17:00:00Z   0      8.0   1991-09-15 01:00:00

  // 场景1:时间跳变(Skip)------ 夏令时开始,本地时间跳过 1 小时
  // 输入 1991-04-14 02:30:00,找到第 4 行,
  // 前一切换点最后一秒是 1991-04-14 01:59:59 < 输入时间 → 属于跳变区间
  if (prior_second < localtime)
  {
    // 处于时间跳变区间(该本地时间不存在)
    // printf("SKIP: prev %ld local %ld start %ld\n", prior_second, localtime, transI->localtime);
    if (postTransition)
    {
      return &localtimes[transI->localtimeIdx]; // 使用切换后的规则
    }
    else
    {
      return &localtimes[prior_trans.localtimeIdx]; // 使用切换前的规则
    }
  }

  // 场景2:时间重复(Repeat)------ 夏令时结束,本地时间重复 1 小时
  // 输入 1990-09-16 01:30:00,找到第 4 行,回退到第 3 行
  --transI;
  if (transI != transitions.begin())
  {
    prior_trans = *(transI - 1);
    prior_second = transI->utctime - 1 + localtimes[prior_trans.localtimeIdx].utcOffset;
  }
  if (localtime <= prior_second)
  {
    // 处于时间重复区间(该本地时间对应两个 UTC 时间)
    // printf("REPEAT: prev %ld local %ld start %ld\n", prior_second, localtime, transI->localtime);
    if (postTransition)
    {
      return &localtimes[transI->localtimeIdx]; // 使用切换后的规则
    }
    else
    {
      return &localtimes[prior_trans.localtimeIdx]; // 使用切换前的规则
    }
  }

  // 场景3:无歧义 ------ 本地时间唯一对应一个 UTC 时间
  return &localtimes[transI->localtimeIdx];
}

// 静态方法:获取 UTC 时区对象(偏移量 0,无时区切换)
TimeZone TimeZone::UTC()
{
  return TimeZone(0, "UTC");
}

// 静态方法:从时区文件加载时区对象
TimeZone TimeZone::loadZoneFile(const char* zonefile)
{
  std::unique_ptr<Data> data(new Data); // 创建空的 Data 对象
  // 解析时区文件,失败则释放 Data
  if (!detail::readTimeZoneFile(zonefile, data.get()))
  {
    data.reset();
  }
  return TimeZone(std::move(data)); // 转移所有权,创建 TimeZone 对象
}

// 私有构造函数:从 Data 智能指针创建 TimeZone
TimeZone::TimeZone(std::unique_ptr<Data> data)
  : data_(std::move(data)) // unique_ptr 转移到 shared_ptr
{
}

// 构造固定偏移的时区对象(无夏令时)
// @param eastOfUtc 相对于 UTC 的东偏移量(秒);name 时区名称(如 "CST")
TimeZone::TimeZone(int eastOfUtc, const char* name)
  : data_(new TimeZone::Data) // 创建 Data 对象
{
  data_->addLocalTime(eastOfUtc, false, 0); // 添加固定规则(非夏令时)
  data_->abbreviation = name;               // 设置时区名称
}

// 将 UTC 时间戳转换为当前时区的本地时间
// @param seconds UTC 时间戳(秒);utcOffset [输出] 可选,返回时区偏移量(秒)
struct DateTime TimeZone::toLocalTime(int64_t seconds, int* utcOffset) const
{
  struct DateTime localTime;
  assert(data_ != NULL); // 确保时区对象有效

  // 查找对应的本地时间规则
  const Data::LocalTime* local = data_->findLocalTime(seconds);

  if (local)
  {
    // UTC 时间 + 偏移量 = 本地时间,拆分为 DateTime
    localTime = detail::BreakTime(seconds + local->utcOffset);
    if (utcOffset)
    {
      *utcOffset = local->utcOffset; // 输出偏移量
    }
  }

  return localTime;
}

// 将当前时区的本地时间转换为 UTC 时间戳
// @param localtime 本地时间;postTransition 处理夏令时歧义的标志
int64_t TimeZone::fromLocalTime(const struct DateTime& localtime, bool postTransition) const
{
  assert(data_ != NULL); // 确保时区对象有效
  // 查找对应的本地时间规则
  const Data::LocalTime* local = data_->findLocalTime(localtime, postTransition);
  // 将本地时间转换为 epoch 秒数(先假设为 UTC 时间)
  const int64_t localSeconds = fromUtcTime(localtime);
  if (local)
  {
    // 本地时间 - 偏移量 = UTC 时间
    return localSeconds - local->utcOffset;
  }
  // 降级处理:视为 UTC 时间
  return localSeconds;
}

// 静态方法:将 UTC 时间戳转换为 UTC 时间的 DateTime 结构体
DateTime TimeZone::toUtcTime(int64_t secondsSinceEpoch)
{
  return detail::BreakTime(secondsSinceEpoch);
}

// 静态方法:将 UTC 时间的 DateTime 结构体转换为 UTC 时间戳
int64_t TimeZone::fromUtcTime(const DateTime& dt)
{
  // 转换年月日为儒略日
  Date date(dt.year, dt.month, dt.day);
  // 计算当天的秒数
  int secondsInDay = dt.hour * 3600 + dt.minute * 60 + dt.second;
  // 总秒数 = (儒略日 - 1970-01-01 儒略日) * 一天秒数 + 当天秒数
  int64_t days = date.julianDayNumber() - Date::kJulianDayOf1970_01_01;
  return days * kSecondsPerDay + secondsInDay;
}

// 从 tm 结构体构造 DateTime(修正 tm 结构体的年/月偏移)
DateTime::DateTime(const struct tm& t)
  : year(t.tm_year + 1900), month(t.tm_mon + 1), day(t.tm_mday),
    hour(t.tm_hour), minute(t.tm_min), second(t.tm_sec)
{
}

// 转换为 ISO 格式字符串(如:"2011-12-31 12:34:56")
string DateTime::toIsoString() const
{
  char buf[64];
  snprintf(buf, sizeof buf, "%04d-%02d-%02d %02d:%02d:%02d",
           year, month, day, hour, minute, second);
  return buf;
}
1. 代码整体定位

这份代码是 TimeZone 类的底层实现,核心解决两个问题:

  • 解析标准 TZif 格式的时区文件(如 /etc/localtime),提取时区规则(偏移量、夏令时切换点);
  • 基于这些规则完成「UTC 秒数 ↔ 本地时间(DateTime)」的双向转换,尤其处理夏令时切换时的「跳过时间」「重复时间」等边缘场景;所有实现都遵循 RFC 8536 标准,兼顾兼容性 (支持 TZif v1/v2 版本)和正确性(处理夏令时歧义)。
2. 核心数据结构解析(Pimpl 模式的 Data 结构体)

TimeZone::Data 是时区规则的核心载体,所有时区转换逻辑都基于它展开:

cpp 复制代码
struct TimeZone::Data
{
  // 时区切换点:比如夏令时开始/结束的UTC时间点
  struct Transition
  {
    int64_t utctime;      // 切换发生的UTC秒数(Epoch以来)
    int64_t localtime;    // 切换点对应的本地时间(UTC+偏移量)
    int localtimeIdx;     // 对应LocalTime的索引
  };

  // 本地时间规则:某一时间段内的时区属性
  struct LocalTime
  {
    int32_t utcOffset;    // UTC东偏移量(秒):比如GMT+8=28800秒
    bool isDst;           // 是否夏令时(Daylight Saving Time)
    int desigIdx;         // 时区缩写索引(如CST/CDT)
  };

  // 辅助方法:添加本地时间规则/切换点
  void addLocalTime(int32_t utcOffset, bool isDst, int desigIdx);
  void addTransition(int64_t utcTime, int localtimeIdx);

  // 核心查找方法:根据UTC/本地时间找对应的LocalTime规则
  const LocalTime* findLocalTime(int64_t utcTime) const;
  const LocalTime* findLocalTime(const struct DateTime& local, bool postTransition) const;

  // 比较器:用于排序/查找Transition
  struct CompareUtcTime { /* 按utcTime排序 */ };
  struct CompareLocalTime { /* 按localtime排序 */ };

  std::vector<Transition> transitions;  // 所有时区切换点(按UTC时间排序)
  std::vector<LocalTime> localtimes;    // 所有本地时间规则
  string abbreviation;                  // 时区缩写(如CST)
  string tzstring;                      // 时区完整字符串(如"GMT+8")
};

核心解释

  • Transition:记录「什么时候切换时区规则」------ 比如某年某月某日 UTC 18:00 开始夏令时(偏移从 8h 变 9h);
  • LocalTime:记录「切换后的时区规则」------ 比如偏移量、是否夏令时、时区名;
  • 两者结合:transitions 是切换点列表,localtimes 是规则列表,通过 localtimeIdx 关联,就能覆盖所有时间段的时区规则。
3. 辅助工具模块(detail 命名空间)

(1)File 类:封装二进制文件读写(线程安全 + 大端转小端)

cpp 复制代码
class File : noncopyable
{
public:
  File(const char* file) : fp_(::fopen(file, "rb")) {}
  ~File() { if (fp_) ::fclose(fp_); }

  // 核心读写方法:处理大端字节序(TZif文件用大端存储)
  int64_t readInt64() { int64_t x; ::fread(&x, 1, 8, fp_); return be64toh(x); }
  int32_t readInt32() { int32_t x; ::fread(&x, 1, 4, fp_); return be32toh(x); }
  // ... 其他读写方法
private:
  FILE* fp_;
};

关键细节

  • be64toh/be32toh:将 TZif 文件中的大端字节序转换为本地字节序(x86 是小端),保证跨平台兼容;
  • 二进制读取("rb" 模式):TZif 是二进制格式,必须用二进制模式打开,避免文本模式的换行符转换。

(2)时间拆分工具:fillHMS/BreakTime

cpp 复制代码
// 秒数(0~86399)拆分为时/分/秒
inline void fillHMS(unsigned seconds, struct DateTime* dt)
{
  dt->second = seconds % 60;
  unsigned minutes = seconds / 60;
  dt->minute = minutes % 60;
  dt->hour = minutes / 60;
}

// Epoch秒数 → DateTime(UTC)
DateTime BreakTime(int64_t t)
{
  DateTime dt;
  int seconds = static_cast<int>(t % kSecondsPerDay); // 当天秒数
  int days = static_cast<int>(t / kSecondsPerDay);    // 天数
  if (seconds < 0) { seconds += kSecondsPerDay; --days; } // 处理负数

  fillHMS(seconds, &dt);
  // 天数转年月日(复用Date类的儒略日逻辑)
  Date date(days + Date::kJulianDayOf1970_01_01);
  Date::YearMonthDay ymd = date.yearMonthDay();
  dt.year = ymd.year; dt.month = ymd.month; dt.day = ymd.day;
  return dt;
}

核心作用

  • 把「Epoch 秒数」这种机器友好的格式,转换为人类可读的「年月日时分秒」(DateTime),是时区转换的基础工具。
4. 核心逻辑:时区文件解析(readTimeZoneFile/readDataBlock)

TZif 是操作系统标准的时区文件格式(如 /etc/localtime),readTimeZoneFile 解析该文件并填充 Data 结构体:

cpp 复制代码
bool readTimeZoneFile(const char* zonefile, struct TimeZone::Data* data)
{
  File f(zonefile);
  if (f.valid())
  {
    try
    {
      string head = f.readBytes(4);
      if (head != "TZif") throw std::logic_error("bad head"); // 验证文件头
      string version = f.readBytes(1); // v1/v2版本
      // ... 跳过固定长度的头部

      if (version == "2") { /* 跳过v1数据,读取v2数据 */ }
      else { /* 读取v1数据 */ }
      return readDataBlock(f, data, version!="2"); // 解析核心数据块
    }
    catch (std::logic_error& e) { fprintf(stderr, "%s\n", e.what()); }
  }
  return false;
}

readDataBlock 核心步骤

  1. 读取文件中的计数器(切换点数量、规则数量等);
  2. 读取所有切换点的 UTC 时间(trans 数组);
  3. 读取每个切换点对应的规则索引(localtimes 数组);
  4. 读取所有本地时间规则(偏移量、是否夏令时、时区名索引);
  5. 填充 Datatransitionslocaltimes 数组;
  6. 读取时区缩写和描述字符串。
5. 核心查找逻辑:findLocalTime(处理夏令时)

这是时区转换的核心算法,分两个重载:

(1)UTC 时间 → 本地时间规则(findLocalTime (int64_t utcTime))

cpp 复制代码
const TimeZone::Data::LocalTime* TimeZone::Data::findLocalTime(int64_t utcTime) const
{
  if (transitions.empty() || utcTime < transitions.front().utctime)
  {
    return &localtimes.front(); // 无切换点/早于第一个切换点:用默认规则
  }
  else
  {
    // 用upper_bound找第一个大于utcTime的切换点,取前一个就是当前规则
    Transition sentry(utcTime, 0, 0);
    auto transI = std::upper_bound(transitions.begin(), transitions.end(), sentry, CompareUtcTime());
    --transI;
    return &localtimes[transI->localtimeIdx];
  }
}

例子

  • 切换点列表:[1989-09-16 17:00:00Z(GMT+8), 1990-04-14 18:00:00Z(GMT+9)];
  • 输入 UTC 时间 1990-06-01 00:00:00Z → upper_bound 找到 1990-09-15 17:00:00Z,取前一个(1990-04-14 18:00:00Z)→ 用 GMT+9 规则。

(2)本地时间 → UTC 时间规则(处理夏令时歧义)

夏令时切换时会出现「重复时间」(比如凌晨 2 点回拨 1 小时,2:30 出现两次)或「跳过时间」(比如凌晨 2 点拨快 1 小时,2:30 不存在),这个重载通过 postTransition 参数处理:

cpp 复制代码
const TimeZone::Data::LocalTime* TimeZone::Data::findLocalTime(
    const struct DateTime& lt, bool postTransition) const
{
  // 1. 本地时间转Epoch秒数(假设UTC)
  const int64_t localtime = fromUtcTime(lt);
  // 2. 找本地时间对应的切换点
  Transition sentry(0, localtime, 0);
  auto transI = std::upper_bound(transitions.begin(), transitions.end(), sentry, CompareLocalTime());
  // 3. 判断是「跳过时间」还是「重复时间」,根据postTransition返回对应规则
  // ... 核心逻辑:略
}

关键参数

  • postTransition=true:取切换后的规则(比如重复时间取第二次出现的);
  • postTransition=false:取切换前的规则(默认)。
6. 核心接口实现:toLocalTime/fromLocalTime
cpp 复制代码
// UTC秒数 → 本地时间(DateTime)
struct DateTime TimeZone::toLocalTime(int64_t seconds, int* utcOffset) const
{
  const Data::LocalTime* local = data_->findLocalTime(seconds);
  DateTime localTime = detail::BreakTime(seconds + local->utcOffset); // UTC+偏移=本地时间
  if (utcOffset) *utcOffset = local->utcOffset;
  return localTime;
}

// 本地时间 → UTC秒数
int64_t TimeZone::fromLocalTime(const struct DateTime& localtime, bool postTransition) const
{
  const Data::LocalTime* local = data_->findLocalTime(localtime, postTransition);
  const int64_t localSeconds = fromUtcTime(localtime);
  return localSeconds - local->utcOffset; // 本地时间-偏移=UTC时间
}

核心逻辑

  • toLocalTime:UTC 秒数 + 偏移量 = 本地秒数 → 拆分为 DateTime;
  • fromLocalTime:本地秒数 - 偏移量 = UTC 秒数(关键是找到正确的偏移量)。
7. 常用时区构造:UTC ()/China ()
cpp 复制代码
TimeZone TimeZone::UTC() { return TimeZone(0, "UTC"); }

TimeZone::TimeZone(int eastOfUtc, const char* name)
  : data_(new TimeZone::Data)
{
  data_->addLocalTime(eastOfUtc, false, 0); // 固定偏移,无夏令时
  data_->abbreviation = name;
}

中国时区TimeZone::China() 本质是 TimeZone(8*3600, "CST")(GMT+8,无夏令时),无需解析时区文件,直接用固定偏移,效率更高。

总结
  1. 数据结构设计 :用 Transition(切换点)+ LocalTime(规则)封装时区规则,兼顾简单时区(固定偏移)和复杂时区(夏令时);
  2. 文件解析:遵循 RFC 8536 解析 TZif 格式,处理大端字节序,兼容 v1/v2 版本;
  3. 核心算法 :用 upper_bound 高效查找切换点,处理夏令时的「跳过 / 重复时间」歧义;
  4. 性能优化:固定偏移时区(如中国)无需解析文件,直接用内存规则,效率拉满;
  5. 复用设计 :复用 Date 类的儒略日逻辑完成「秒数→年月日」转换,避免重复造轮子。
相关推荐
刘叨叨趣味运维2 小时前
Linux发行版选择指南:找到你的最佳拍档
linux
爱吃生蚝的于勒2 小时前
【Linux】零基础学习命名管道-共享内存
android·linux·运维·服务器·c语言·c++·学习
陳10302 小时前
C++:继承
开发语言·c++
txinyu的博客2 小时前
解析muduo源码之 atomic.h
服务器·c++
数智工坊2 小时前
【操作系统-处理器调度】
java·linux·服务器·windows·ubuntu
济6172 小时前
Linux内核---vmlinux、zImage、uImage区别
linux·运维·服务器
阿拉伯柠檬2 小时前
网络层协议IP(二)
linux·网络·网络协议·tcp/ip·面试
静谧空间2 小时前
Linux自动备份Mysql数据
linux·运维·mysql
技术摆渡人2 小时前
专题一:【BSP 核心实战】Linux 系统死机与 DDR 稳定性“法医级”排查全书
linux·驱动开发·车载系统