Skip to content

C++ 日期和时间

现代 C++ 通过 <chrono> 头文件提供了强大的日期和时间操作工具,该头文件在 C++11 中引入并在 C++20 中得到了显著增强。虽然 C++ 仍然支持旧的 C 风格 <ctime> 库以保持兼容性,但 <chrono> 提供了一种类型安全、灵活且更具表达力的方法。

  • 时钟(Clocks):表示时间点来源。常用的时钟包括 std::chrono::system_clock(墙上时钟时间,可能可调整)、std::chrono::steady_clock(单调时钟,适用于测量时间间隔)和 std::chrono::high_resolution_clock(可用精度最高的时钟)。
  • 时间点(Time Points):表示相对于时钟纪元(起始点)的特定时间点。std::chrono::time_point 通常从时钟的 now() 函数获得。
  • 时长(Durations):表示一段时间跨度,例如“5 秒”或“200 毫秒”。std::chrono::duration 是一个模板类,存储一个计数和一个时间单位(例如,std::chrono::seconds、std::chrono::milliseconds、std::chrono::nanoseconds)。

要获取当前的墙上时钟时间,请使用 std::chrono::system_clock::now():

#include <iostream>
#include <chrono>
int main() {
// Get the current time point from the system clock // 从系统时钟获取当前时间点
std::chrono::time_point<std::chrono::system_clock> now = std::chrono::system_clock::now();
// A time_point itself isn't directly printable in a human-readable format easily pre-C++20.
// 在 C++20 之前,time_point 本身不容易直接以人类可读的格式打印。
// We often convert it for output or calculations.
// 我们通常将其转换为用于输出或计算。
// Example: Get time since epoch (useful for some APIs) // 示例:获取自纪元以来经过的时间(对某些 API 有用)
auto duration_since_epoch = now.time_since_epoch();
// Convert duration to seconds (as a double for fractional part) // 将时长转换为秒(使用 double 以包含小数部分)
std::chrono::duration<double> seconds_since_epoch = duration_since_epoch;
std::cout << "Seconds since epoch (Jan 1, 1970 UTC): "
<< seconds_since_epoch.count() << std::endl; // 自纪元(1970 年 1 月 1 日 UTC)以来的秒数:
return 0;
}

格式化日期和时间(C++20 之前的方法)

Section titled “格式化日期和时间(C++20 之前的方法)”

在 C++20 向 <chrono> 添加格式化功能之前,获取人类可读输出的常用方法是将 system_clock::time_point 转换为旧的 C 类型 time_t,然后可以将其与 std::localtime(或用于 UTC 的 std::gmtime)和 std::put_time(来自 <iomanip>)等函数一起用于格式化输出。

注意:std::localtime 在所有平台上都不是线程安全的,因为它通常使用静态内部缓冲区。存在更安全的替代方法,如 localtime_s(Windows)或 localtime_r(POSIX),但它们是平台特定的。对于跨平台、线程安全的格式化,建议使用 C++20 的 <chrono> 或外部库,如 fmt 或 Boost.Locale。

以下是使用 std::put_time 的示例:

#include <iostream>
#include <chrono>
#include <ctime> // For time_t, tm, localtime // 用于 time_t, tm, localtime
#include <iomanip> // For std::put_time // 用于 std::put_time
#include <sstream> // For formatting to string (optional) // 用于格式化到字符串(可选)
int main() {
// Get the current time point // 获取当前时间点
auto now = std::chrono::system_clock::now();
// Convert time_point to time_t // 将 time_point 转换为 time_t
std::time_t now_c = std::chrono::system_clock::to_time_t(now);
// Convert time_t to tm struct for local time (potentially not thread-safe)
// 将 time_t 转换为 tm 结构体以获取本地时间(可能不是线程安全的)
// Note: std::localtime returns a pointer to a statically allocated struct.
// 注意:std::localtime 返回指向静态分配的结构体的指针。
std::tm now_tm = *std::localtime(&now_c);
// Format the time using std::put_time // 使用 std::put_time 格式化时间
std::cout << "Current local time: "; // 当前本地时间:
std::cout << std::put_time(&now_tm, "%Y-%m-%d %H:%M:%S %Z") << std::endl; // ISO 8601-like format + timezone // 类似 ISO 8601 格式 + 时区
// Example: Format date only // 示例:仅格式化日期
std::cout << "Current date: "; // 当前日期:
std::cout << std::put_time(&now_tm, "%A, %B %d, %Y") << std::endl; // e.g., Tuesday, August 22, 2023 // 例如,星期二,2023 年 8 月 22 日
// For UTC time, use std::gmtime instead of std::localtime (also potentially not thread-safe)
// 对于 UTC 时间,使用 std::gmtime 代替 std::localtime(也可能不是线程安全的)
std::tm utc_tm = *std::gmtime(&now_c);
std::cout << "Current UTC time: "; // 当前 UTC 时间:
std::cout << std::put_time(&utc_tm, "%Y-%m-%d %H:%M:%S UTC") << std::endl;
return 0;
}

std::put_time 中使用的格式代码(如 %Y、%m、%d)与 C 函数 strftime 使用的格式代码相同。常用的代码包括:

  • %Y:带世纪的年份(例如,2023)
  • %m:作为零填充十进制数字的月份(01-12)
  • %d:作为零填充十进制数字的月中的日期(01-31)
  • %H:作为零填充十进制数字的小时(24 小时制)(00-23)
  • %M:作为零填充十进制数字的分钟(00-59)
  • %S:作为零填充十进制数字的秒(00-60)
  • %Z:时区名称或缩写(如果可用)
  • %A:完整的星期几名称(例如,Sunday)
  • %B:完整的月份名称(例如,January)

C++20 通过 std::format(需要 <format> 头文件)添加了日历、时区和直接格式化功能,显著增强了 <chrono>。这避免了 time_t 转换及其相关的线程安全问题。

使用 C++20 std::format 的示例(需要符合 C++20 标准的编译器和标准库):

// Requires C++20 features // 需要 C++20 功能
#include <iostream>
#include <chrono>
#include <format> // C++20 header // C++20 头文件
int main() {
auto now = std::chrono.system_clock.now();
// Direct formatting using std::format // 使用 std::format 直接格式化
std::cout << std::format("Current local time: {:%Y-%m-%d %H:%M:%S %Z}\n", now); // 当前本地时间:
std::cout << std::format("Current UTC time: {:%Y-%m-%d %H:%M:%S UTC}\n", now); // 当前 UTC 时间:
// Formatting with specific time zones (if supported by implementation)
// 使用特定时区格式化(如果实现支持)
try {
auto zt = std::chrono::zoned_time{ "America/New_York", now };
std::cout << std::format("New York time: {:%Y-%m-%d %H:%M:%S %Z}\n", zt); // 纽约时间:
} catch (const std::chrono::nonexistent_local_time& e) {
std::cerr << "Time zone error: " << e.what() << '\n'; // 时区错误:
} catch (const std::chrono::ambiguous_local_time& e) {
std::cerr << "Time zone error: " << e.what() << '\n'; // 时区错误:
}
return 0;
}

<chrono> 使时长处理类型安全且直观。

#include <iostream>
#include <chrono>
#include <thread> // For std::this_thread::sleep_for // 用于 std::this_thread::sleep_for
int main() {
using namespace std::chrono_literals; // Enable convenient literals like 10s, 200ms // 启用方便的字面量,如 10s, 200ms
auto start = std::chrono::high_resolution_clock::now();
// Simulate some work // 模拟一些工作
std::this_thread::sleep_for(1500ms); // Sleep for 1.5 seconds // 休眠 1.5 秒
auto end = std::chrono::high_resolution_clock::now();
// Calculate the duration // 计算时长
std::chrono::duration<double> elapsed_seconds = end - start;
auto elapsed_milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
std::cout << "Elapsed time: " << elapsed_seconds.count() << " s\n"; // 经过时间:
std::cout << "Elapsed time: " << elapsed_milliseconds.count() << " ms\n"; // 经过时间:
return 0;
}

<chrono> 库是现代 C++ 处理日期和时间的标准。它提供类型安全的时钟、时间点和时长。虽然在 C++20 之前格式化需要转换为 C 类型,但现代 C++ 提供了直接格式化功能,使得日期/时间操作更安全、更方便。