📅 为什么需要日期时间处理?

在日常编程中,我们经常需要处理与时间相关的任务:

  • 记录事件发生的时间(如日志记录)
  • 测量代码执行时间(性能分析)
  • 定时任务和调度
  • 处理用户输入的日期(如生日)
  • 计算时间间隔(如倒计时)
  • 生成时间戳

💡 C++提供了多种处理日期和时间的方法,从传统的C风格方法到现代的C++11/17/20特性。

🕰️ C风格日期时间处理

C++继承了C语言的日期时间处理函数,需要包含头文件 <ctime>

1. time_t 和 time() 函数

time_t 是表示时间的基本数据类型,通常是一个整数(从1970年1月1日UTC时间起经过的秒数)。

time() 函数获取当前时间:

#include <ctime>
#include <iostream>

int main() {
    time_t now = time(0); // 获取当前时间
    std::cout << "当前时间戳: " << now << "秒" << std::endl;
    return 0;
}

2. tm 结构体

tm 是一个结构体,包含日期和时间的各个组成部分:

struct tm {
    int tm_sec;   // 秒 [0-60]
    int tm_min;   // 分 [0-59]
    int tm_hour;  // 时 [0-23]
    int tm_mday;  // 月中的天数 [1-31]
    int tm_mon;   // 月份 [0-11] (0=一月)
    int tm_year;  // 年份(从1900开始)
    int tm_wday;  // 周中的天数 [0-6] (0=周日)
    int tm_yday;  // 年中的天数 [0-365]
    int tm_isdst; // 夏令时标志
};

3. 时间转换函数

  • localtime() - 将time_t转换为本地时间的tm结构
  • gmtime() - 将time_t转换为UTC时间的tm结构
  • mktime() - 将tm结构转换为time_t

示例:获取当前日期和时间

#include <ctime>
#include <iostream>

int main() {
    time_t now = time(0);
    tm *ltm = localtime(&now);
    
    std::cout << "年: " << 1900 + ltm->tm_year << std::endl;
    std::cout << "月: " << 1 + ltm->tm_mon << std::endl;
    std::cout << "日: " << ltm->tm_mday << std::endl;
    std::cout << "时间: " << ltm->tm_hour << ":";
    std::cout << ltm->tm_min << ":" << ltm->tm_sec << std::endl;
    
    return 0;
}

⏱️ C++11 chrono库

C++11引入了<chrono>库,提供了更现代、类型安全的时间处理方式。

1. 时间段 (duration)

表示一段时间长度,如秒、毫秒、微秒等。

#include <chrono>
#include <iostream>

using namespace std::chrono;

int main() {
    // 定义时间段
    milliseconds ms(1000); // 1000毫秒
    seconds sec = duration_cast<seconds>(ms); // 转换为秒
    
    std::cout << ms.count() << " 毫秒 = ";
    std::cout << sec.count() << " 秒" << std::endl;
    
    return 0;
}

2. 时间点 (time_point)

表示一个特定的时间点,通常相对于某个纪元(如1970年1月1日)。

// 获取当前时间点
auto now = system_clock::now();

// 转换为time_t(C风格时间)
time_t now_c = system_clock::to_time_t(now);

3. 时钟 (clock)

chrono库提供了几种时钟:

  • system_clock - 系统范围的实时时钟(可调整)
  • steady_clock - 稳定的时钟(不可调整),适用于测量时间间隔
  • high_resolution_clock - 最高精度的时钟

示例:测量代码执行时间

#include <chrono>
#include <iostream>
#include <thread> // 用于sleep

int main() {
    auto start = std::chrono::high_resolution_clock::now();
    
    // 模拟耗时操作
    std::this_thread::sleep_for(std::chrono::milliseconds(500));
    
    auto end = std::chrono::high_resolution_clock::now();
    
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    
    std::cout << "耗时: " << duration.count() << " 毫秒" << std::endl;
    
    return 0;
}

📆 C++20 日期库

C++20扩展了chrono库,添加了强大的日历和时区支持。

1. 日历类型

  • year - 表示年份
  • month - 表示月份(1-12)
  • day - 表示一个月中的某天(1-31)
  • year_month_day - 完整的日期类型

2. 创建日期

#include <chrono>
#include <iostream>

int main() {
    using namespace std::chrono;
    
    // 创建日期:2023年8月15日
    year_month_day date{2023y, August, 15d};
    
    // 检查日期是否有效
    if (date.ok()) {
        std::cout << "日期有效: ";
        std::cout << static_cast<int>(date.year()) << "-";
        std::cout << static_cast<unsigned>(date.month()) << "-";
        std::cout << static_cast<unsigned>(date.day()) << std::endl;
    }
    
    return 0;
}

3. 日期运算

auto tomorrow = sys_days{2023y/8/15} + days{1};
std::cout << "明天是: " << tomorrow << std::endl;

// 计算两个日期之间的天数
auto date1 = 2023y/8/15;
auto date2 = 2023y/9/1;
auto diff = sys_days{date2} - sys_days{date1};
std::cout << "相差天数: " << diff.count() << std::endl;

💡 C++20日期库提供了更直观、更安全的日期处理方式,避免了C风格函数中的很多陷阱。

🔄 时间类型转换

在实际编程中,经常需要在不同时间表示之间转换:

1. time_t 和 system_clock::time_point 互转

// time_point 转 time_t
auto now_tp = system_clock::now();
time_t now_tt = system_clock::to_time_t(now_tp);

// time_t 转 time_point
time_t tt = time(0);
system_clock::time_point tp = system_clock::from_time_t(tt);

2. tm 和 system_clock::time_point 互转

// time_point 转 tm
time_t tt = system_clock::to_time_t(tp);
tm* timeinfo = localtime(&tt);

// tm 转 time_point
tm timeinfo = {}; // 初始化为0
timeinfo.tm_year = 123; // 2023年(2023-1900)
timeinfo.tm_mon = 7;    // 8月(0-based)
timeinfo.tm_mday = 15;  // 15日
time_t tt = mktime(&timeinfo);
auto tp = system_clock::from_time_t(tt);

🖨️ 格式化输出

C++提供了多种方式格式化日期时间输出。

1. C风格格式化 (strftime)

char buffer[80];
time_t now = time(0);
tm *ltm = localtime(&now);

strftime(buffer, 80, "%Y-%m-%d %H:%M:%S", ltm);
std::cout << "格式化时间: " << buffer << std::endl;

2. C++20格式化

#include <chrono>
#include <format> // C++20
#include <iostream>

int main() {
    auto now = std::chrono::system_clock::now();
    std::cout << std::format("当前时间: {:%Y-%m-%d %H:%M:%S}", now) << std::endl;
    return 0;
}

常用格式化符号:

  • %Y - 四位年份(如2023)
  • %m - 两位月份(01-12)
  • %d - 两位日期(01-31)
  • %H - 24小时制小时(00-23)
  • %M - 分钟(00-59)
  • %S - 秒(00-60)
  • %F - 等价于 %Y-%m-%d
  • %T - 等价于 %H:%M:%S

⚖️ 新旧方法对比

传统C风格方法

优点:

  • 简单,易于理解
  • 所有C/C++环境都支持
  • 轻量级

缺点:

  • 类型不安全
  • 容易出错(如月份0-based)
  • 功能有限
  • 时区处理复杂

C++11/17/20 chrono库

优点:

  • 类型安全
  • 功能强大
  • 支持高精度计时
  • 更直观的API
  • C++20支持日历和时区

缺点:

  • 语法较复杂
  • 旧编译器支持有限
  • C++20部分功能较新

💡 建议: 新项目尽量使用C++11/17/20的chrono库,旧项目维护或兼容性要求高时使用C风格方法。

🔧 实际应用示例

1. 计算程序执行时间

#include <chrono>
#include <iostream>
#include <thread>

int main() {
    auto start = std::chrono::steady_clock::now();
    
    // 模拟耗时操作
    std::this_thread::sleep_for(std::chrono::seconds(2));
    
    auto end = std::chrono::steady_clock::now();
    auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    
    std::cout << "执行时间: " << elapsed.count() << " 毫秒" << std::endl;
    return 0;
}

2. 生成时间戳字符串

#include <chrono>
#include <iostream>
#include <ctime>
#include <sstream>
#include <iomanip>

std::string current_time() {
    auto now = std::chrono::system_clock::now();
    auto now_c = std::chrono::system_clock::to_time_t(now);
    std::tm now_tm = *std::localtime(&now_c);
    
    std::ostringstream oss;
    oss << std::put_time(&now_tm, "%Y%m%d_%H%M%S");
    return oss.str();
}

int main() {
    std::cout << "时间戳: " << current_time() << std::endl;
    return 0;
}

3. 计算两个日期之间的天数(C++20)

#include <chrono>
#include <iostream>

using namespace std::chrono;

int main() {
    auto date1 = 2023y/8/15; // 2023年8月15日
    auto date2 = 2023y/9/1;  // 2023年9月1日
    
    auto days = sys_days{date2} - sys_days{date1};
    
    std::cout << "天数差: " << days.count() << std::endl;
    return 0;
}