在C++程序开发中,将日期时间转换为指定格式的字符串是常见需求,比如生成带时间戳的日志、按日期命名的文件等场景都需要用到这个功能。C++标准库提供了两种主流的日期格式化方案,分别是C风格的strftime函数和C++11引入的put_time工具,两者都能实现日期到字符串的转换,但使用方式和适用场景有所不同。

strftime函数基础用法
strftime是C标准库中的时间格式化函数,定义在<ctime>头文件中,它可以将time_t类型的时间按照指定的格式转换为字符串。函数的核心参数包括目标字符串缓冲区、缓冲区大小、格式字符串和时间结构体指针。
strftime函数原型
函数的基本定义如下:
#include <ctime> #include <iostream> // 函数原型 // size_t strftime(char* str, size_t maxsize, const char* format, const struct tm* timeptr);
基础示例:获取当前时间并格式化
下面的代码演示了使用strftime获取当前系统时间,并格式化为常见的年月日时分秒字符串:
#include <ctime>
#include <iostream>
#include <vector>
int main() {
// 获取当前系统时间
time_t now = time(nullptr);
// 转换为本地时间结构体
struct tm local_time;
localtime_r(&now, &local_time); // Linux/macOS使用localtime_r,线程安全
// Windows下可使用localtime_s(&local_time, &now);
// 定义格式字符串:年-月-日 时:分:秒
const char* format = "%Y-%m-%d %H:%M:%S";
// 定义缓冲区,足够存储格式化后的字符串
char buffer[64];
// 调用strftime格式化
size_t ret = strftime(buffer, sizeof(buffer), format, &local_time);
if (ret > 0) {
std::cout << "格式化后的时间:" << buffer << std::endl;
} else {
std::cout << "时间格式化失败" << std::endl;
}
return 0;
}
常用格式说明符
strftime的格式字符串由普通字符和格式说明符组成,常用说明符如下:
- %Y:四位年份,比如2024
- %m:两位月份,01-12
- %d:两位日期,01-31
- %H:24小时制小时,00-23
- %M:分钟,00-59
- %S:秒,00-60(60用于闰秒)
- %y:两位年份,比如24
- %I:12小时制小时,01-12
- %p:AM/PM标识
- %A:完整的星期名称,比如Monday
- %a:简写的星期名称,比如Mon
- %B:完整的月份名称,比如January
- %b:简写的月份名称,比如Jan
put_time工具用法
put_time是C++11标准引入的I/O操纵器,定义在<iomanip>头文件中,它可以配合输出流(比如std::cout、std::ostringstream)直接输出格式化后的时间,使用起来更符合C++的面向对象风格。
put_time基本使用
put_time需要接收两个参数,第一个是tm结构体指针,第二个是格式字符串,格式说明符和strftime完全一致。
#include <iostream>
#include <ctime>
#include <iomanip>
#include <sstream>
int main() {
time_t now = time(nullptr);
struct tm local_time;
localtime_r(&now, &local_time);
// 直接输出到控制台
std::cout << "当前时间:" << std::put_time(&local_time, "%Y-%m-%d %H:%M:%S") << std::endl;
// 输出到字符串流,转换为字符串
std::ostringstream oss;
oss << std::put_time(&local_time, "%Y%m%d_%H%M%S");
std::string time_str = oss.str();
std::cout << "文件名时间戳:" << time_str << std::endl;
return 0;
}
strftime与put_time对比
两种方式都能实现日期格式化,具体差异如下:
| 对比项 | strftime | put_time |
|---|---|---|
| 所属标准 | C标准库,兼容C和C++ | C++11及以上标准 |
| 使用方式 | 函数调用,需要手动管理缓冲区 | I/O操纵器,配合流使用 |
| 返回值 | 返回格式化后的字符数,失败返回0 | 返回流对象,支持链式调用 |
| 适用场景 | 需要直接获取C风格字符串、兼容旧代码 | C++风格代码、需要配合流输出或转换字符串 |
| 线程安全 | 依赖传入的tm结构体,本身无状态 | 依赖流对象,流对象需保证线程安全 |
实战场景示例
场景1:生成日志时间戳
日志通常需要带精确到毫秒的时间戳,strftime本身不支持毫秒,需要额外处理:
#include <ctime>
#include <iostream>
#include <chrono>
#include <fmt/format.h> // 需要fmt库支持,若不用fmt可手动拼接毫秒
int main() {
// 获取当前时间,包含毫秒
auto now = std::chrono::system_clock::now();
time_t now_time = std::chrono::system_clock::to_time_t(now);
struct tm local_time;
localtime_r(&now_time, &local_time);
// 计算毫秒部分
auto duration = now.time_since_epoch();
auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count() % 1000;
// 格式化时间+毫秒
char buffer[64];
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &local_time);
char log_time[64];
snprintf(log_time, sizeof(log_time), "%s.%03ld", buffer, millis);
std::cout << "[ " << log_time << " ] 这是一条日志信息" << std::endl;
return 0;
}
场景2:处理自定义时间结构体
如果需要对自定义的时间对象进行格式化,可以先转换为tm结构体再使用两种方式处理:
#include <ctime>
#include <iostream>
#include <iomanip>
// 自定义时间结构体
struct MyTime {
int year; // 实际年份,比如2024
int month; // 1-12
int day; // 1-31
int hour; // 0-23
int minute;// 0-59
int second;// 0-59
};
int main() {
MyTime t = {2024, 5, 20, 14, 30, 45};
struct tm tm_time = {0};
// 填充tm结构体,注意tm_year是年份减去1900,tm_mon是月份减去1
tm_time.tm_year = t.year - 1900;
tm_time.tm_mon = t.month - 1;
tm_time.tm_mday = t.day;
tm_time.tm_hour = t.hour;
tm_time.tm_min = t.minute;
tm_time.tm_sec = t.second;
// 标记tm结构体已经填充完整
tm_time.tm_isdst = -1;
// 使用put_time格式化
std::cout << "自定义时间格式化:" << std::put_time(&tm_time, "%Y年%m月%d日 %H时%M分%S秒") << std::endl;
return 0;
}
注意事项
- 使用localtime函数时需要注意线程安全问题,尽量使用localtime_r(Linux/macOS)或localtime_s(Windows)替代非线程安全的localtime
- strftime的缓冲区大小要足够,避免格式化后的字符串过长导致缓冲区溢出
- put_time需要C++11及以上标准支持,编译时需要开启对应的标准,比如使用-std=c++11参数
- tm结构体的tm_year字段是年份减去1900,tm_mon字段是月份减去1,填充自定义时间时需要注意转换,否则会得到错误的结果