广告

C++ 时间处理必备:time_t 与 tm 结构体互转,以及 mktime、localtime 的用法详解

1. time_t 与 tm 结构体的基础与互转

时间类型与字段概览

在 C++ 的时间处理领域,time_t 通常表示自 UNIX 纪元(1970-01-01 00:00:00 UTC)以来的秒数,并且在不同平台上实现细节略有差异,但总体上是一种时间戳。为了将时间戳转化为易读的日历时间,可以借助 localtimegmtimetime_t 分解为日历信息,反之则需要使用 mktime 将日历时间重新合成为 time_t。这些核心互转 API 构成 C++ 时间处理的基石。

要点在于理解 tm 结构体 的字段:tm_year(从1900起算的年份)、tm_mon(0-11,1 月为 0)、tm_mdaytm_hourtm_mintm_sec,以及 tm_wdaytm_ydaytm_isdst 等辅助信息。

要注意的是,localtime 返回指向静态缓冲区的指针,在多线程场景下不安全,若要线程安全,优先使用 localtime_r(POSIX)或 localtime_s(Windows),同理,gmtime 也有对应的线程安全版本。

#include <ctime>
#include <iostream>
#include <iomanip>int main() {// 获取当前时间的 time_tstd::time_t t = std::time(nullptr);// 将 time_t 转换成本地日历时间std::tm tm_local;#if defined(_WIN32)localtime_s(&tm_local, &t);#elselocaltime_r(&t, &tm_local);#endif// 以人类可读的格式输出std::cout << std::put_time(&tm_local, "%F %T") << std::endl;return 0;
}

通过上述示例,可以看到 time_ttm 之间的互转需要借助系统提供的 API;其中,%F %T 的格式化输出对应了 年-月-日 小时:分:秒 的常见呈现形式。

tm 结构体字段的取值规则

在进行互转之前,应明确 tm_yeartm_montm_mday 这几个字段的含义及范围:tm_year 代表自1900年起的年份,因此要显示为实际年份需要将其加上 1900;tm_mon 的取值范围是 0-11,实际月份需加 1;其他字段如 tm_hourtm_mintm_sec 也遵循常规范围。

此外,tm_isdst 表示夏令时状态,常见取值为 0(非夏时制)、1(夏时制)或 -1(未确定)。在调用 mktime 时,系统可能会基于当前时区和夏时制规则自动更新这些字段。

2. time_t 与 tm 结构体互转的核心细节

从 tm 到 time_t:mktime 的使用要点

把一个分解后的日历时间重新组装成时间戳,核心在于使用 mktime。它接收一个指向 tm 的指针并返回对应的 time_t。在调用前,确保 tm_yeartm_montm_mday 等字段已经正确填充;调用后,tm_wdaytm_ydaytm_isdst 等字段也可能被归一化更新。

需要注意:tm_mon 的范围仍然是 0-11,tm_year 是自 1900 起的偏移值,tm_mday 从 1 开始。调用成功后,返回值 time_t 指向该日历时间的时间戳。

#include <ctime>
#include <iostream>int main() {std::tm tm_input = {};tm_input.tm_year = 2025 - 1900; // 2025 年tm_input.tm_mon  = 0;           // 1 月tm_input.tm_mday = 15;tm_input.tm_hour = 12;tm_input.tm_min  = 30;tm_input.tm_sec  = 0;// 归一化后得到 time_tstd::time_t t = std::mktime(&tm_input);std::cout << "time_t: " << t << std::endl;return 0;
}

若输入的日期超出范围,mktime 可能返回 -1,表示无法表示该时间。并且,当 tm_isdst 设置为 -1 时,mktime 会尝试自动推断是否处于夏时制,从而影响最终的时间戳。

从 time_t 到 tm:localtime 与 gmtime 的应用

将时间戳转换成人类时间,通常使用 localtime(本地时区)或 gmtime(UTC/格林威治时区)。这两者都返回 tm 的指针,但返回的是指向静态缓冲区的指针,因此同一时刻只能有一个 ta 复用这块内存,导致多线程场景不安全。若要线程安全,需要使用 localtime_rlocaltime_s

下面示例演示如何从 time_t 获取本地时间的 tm 结构体,以及如何进行格式化输出。

#include <ctime>
#include <iostream>
#include <iomanip>int main() {std::time_t now = std::time(nullptr);std::tm tm_now;#if defined(_WIN32)localtime_s(&tm_now, &now);#elselocaltime_r(&now, &tm_now);#endif// 将 tm_now 格式化为字符串std::cout << std::put_time(&tm_now, "%F %T") << std::endl;return 0;
}

如上所示,localtime 家族 API 能将时间戳迅速转为本地日历信息,配合 strftime 可以实现任意格式的文本输出。

3. localtime 获取本地时间的安全性与格式化输出

线程安全的替代方案与使用范例

在多线程环境中,直接使用 localtime 会产生数据竞争,因为返回的是静态缓冲区的指针。为了避免这种风险,应使用线程安全版本:在 POSIX 系统上选择 localtime_r,在 Windows 上选择 localtime_s。下面展示一个线程安全的示例。

C++ 时间处理必备:time_t 与 tm 结构体互转,以及 mktime、localtime 的用法详解

#include <ctime>
#include <iostream>
#include <iomanip>int main() {std::time_t t = std::time(nullptr);std::tm tm_buf;#if defined(_WIN32)localtime_s(&tm_buf, &t);#elselocaltime_r(&t, &tm_buf);#endifchar out[64];std::strftime(out, sizeof(out), "%Y-%m-%d %H:%M:%S", &tm_buf);std::cout << "Local time: " << out << std::endl;return 0;
}

对于格式化输出,strftime 提供了强大的格式化能力,能够将 tm 结构体中的字段映射为你需要的文本格式,如 %Y%m%d%H%M%S 等占位符。

如何把 tm 格式化成字符串输出

如果你只需要一个简单的文本表示,结合 strftime 可以快速实现:将一个已经填充好的 tm 传入 strftime,并指定你想要的格式字符串。这样做的好处是格式可控、跨平台一致性强。

#include <ctime>
#include <iostream>int main() {std::time_t t = std::time(nullptr);std::tm tm_local;#if defined(_WIN32)localtime_s(&tm_local, &t);#elselocaltime_r(&t, &tm_local);#endifchar buf[64];std::strftime(buf, sizeof(buf), "%A, %Y-%m-%d %H:%M:%S", &tm_local);std::cout << buf << std::endl;return 0;
}

4. 实践示例:日期字符串解析、时区与转换

示例 1:固定日期的互转

下面演示如何把一个固定日期字符串解析为 tm,再通过 mktime 还原为 time_t,最后再将其格式化输出。本例演示一个完整的“文本输入 → time_t → 本地文本输出”流程。

#include <ctime>
#include <iostream>
#include <iomanip>
#include <sstream>int main() {std::string ds = "2025-03-14 09:26:53";std::tm tm_input = {};std::istringstream ss(ds);ss >> std::get_time(&tm_input, "%Y-%m-%d %H:%M:%S");if (ss.fail()) {std::cerr << "parse failed" << std::endl;return 1;}// 将文本日期转换为 time_tstd::time_t t = std::mktime(&tm_input);// 将 time_t 转换回本地时间并输出std::tm tm_out;#if defined(_WIN32)localtime_s(&tm_out, &t);#elselocaltime_r(&t, &tm_out);#endifchar buf[64];std::strftime(buf, sizeof(buf), "%F %T", &tm_out);std::cout << "Local: " << buf << std::endl;return 0;
}

示例 2:当前时间的时区感知输出

另一个常见场景是取得当前时间并输出带时区感知信息的文本。示例中先获取当前时间的 time_t,再通过 localtime(或它的线程安全版本)转换为本地时间,并使用自定义时区标签输出。

#include <ctime>
#include <iostream>
#include <iomanip>int main() {std::time_t now = std::time(nullptr);std::tm tm_now;#if defined(_WIN32)localtime_s(&tm_now, &now);#elselocaltime_r(&now, &tm_now);#endifchar buf[64];std::strftime(buf, sizeof(buf), "%F %T %Z", &tm_now); // %Z 输出时区名std::cout << "Now: " << buf << std::endl;return 0;
}
这份内容覆盖了 time_t 与 tm 结构体的互转、mktime、localtime 的用法以及在实际工程中的常见场景,包含了多处明确强调的要点与可直接执行的代码片段,便于在博客中提高可读性和 SEO 效果。

广告

后端开发标签