std::get_time执行结果不一致问题排查求助
问题背景
需求说明
我正在开发一个小型Discord Bot,用于和朋友一起处理电竞比赛的友好赌注。近期尝试为比赛添加日期时间功能,以实现:
- 仅显示即将到来的比赛
- 禁止添加日期已过期的比赛
- 将比赛日期时间转换为字符串展示
实现代码
我实现了如下DateAndTime类:
class DateAndTime { public: explicit DateAndTime(const std::string& timeAsString) { std::istringstream timeAsStream{ timeAsString }; timeAsStream >> std::get_time(&m_Time, std::string{ DATE_TIME_FORMAT }.c_str()); if (timeAsStream.fail()) { throw InvalidDateFormat(timeAsString); } } [[nodiscard]] std::string ToString() const noexcept { std::stringstream resultAsStream; resultAsStream << std::put_time(&m_Time, std::string{DATE_TIME_FORMAT}.c_str()); return resultAsStream.str(); } [[nodiscard]] bool IsInFuture() noexcept { const std::time_t dateInSeconds = std::mktime(&m_Time); const std::chrono::time_point now = std::chrono::system_clock::now(); const std::time_t nowAsSeconds = std::chrono::system_clock::to_time_t(now); return dateInSeconds > nowAsSeconds; } private: static constexpr std::string_view DATE_TIME_FORMAT = "%d-%m-%Y %H:%M"; std::tm m_Time; };
问题现象
同一输入字符串在单元测试和Bot运行时得到不同结果:
单元测试代码:
TEST(DateAndTime_Tests, ToString) { const DateAndTime test{ "10-01-1995 18:00" }; EXPECT_EQ(test.ToString(), "10-01-1995 18:00"); const DateAndTime test2{ "10-01-1995 18:00:00" }; EXPECT_EQ(test2.ToString(), "10-01-1995 18:00"); const DateAndTime test3{ "01-01-2028 18:00" }; EXPECT_EQ(test3.ToString(), "01-01-2028 18:00"); }
所有单元测试均通过,表现正常。
但在Discord命令中输入日期"01-01-2028 18:00"时,却触发了过期日期错误:
User error: The given Date [12-10-2000 16:42] is in the past.
可见解析出的日期与输入完全不符。
调试情况
起初怀疑命令到DateAndTime构造函数之间的输入字符串被篡改,但在构造函数中打断点后确认输入字符串是正确的,目前无法定位问题原因。
问题分析与解决方案
根本原因
问题出在std::tm类型成员m_Time的未初始化上:
- 类成员
m_Time默认未初始化,其内部字段(如tm_isdst、tm_yday等)会是随机垃圾值 std::get_time只会填充格式字符串中匹配的字段(日、月、年、时、分),不会处理其他未匹配字段- 调用
std::mktime转换时间时,这些垃圾值会导致转换结果完全错误,不同运行环境的初始垃圾值不同,就出现了单元测试正常但Bot运行异常的情况
修复方案
在构造函数中显式初始化m_Time,确保所有字段都有合理初始值,有两种常见方式:
方式1:初始化为全0
explicit DateAndTime(const std::string& timeAsString) { // 用memset将tm结构体所有字段置0 std::memset(&m_Time, 0, sizeof(m_Time)); std::istringstream timeAsStream{ timeAsString }; timeAsStream >> std::get_time(&m_Time, std::string{ DATE_TIME_FORMAT }.c_str()); if (timeAsStream.fail()) { throw InvalidDateFormat(timeAsString); } }
方式2:基于当前时间初始化
如果需要保留时区、夏令时等系统默认设置,可以基于当前时间初始化:
explicit DateAndTime(const std::string& timeAsString) { std::time_t now = std::time(nullptr); // 复制当前时间的tm结构体,再覆盖需要的字段 m_Time = *std::localtime(&now); std::istringstream timeAsStream{ timeAsString }; timeAsStream >> std::get_time(&m_Time, std::string{ DATE_TIME_FORMAT }.c_str()); if (timeAsStream.fail()) { throw InvalidDateFormat(timeAsString); } }
额外建议
- 在
IsInFuture方法中检查std::mktime的返回值,mktime转换失败会返回-1,可以提前抛出错误避免逻辑异常 - 补充单元测试覆盖
IsInFuture方法,测试未来、过去、当前时间的场景,提前发现环境相关问题
内容的提问来源于stack exchange,提问作者Maeglix
相关产品推荐
相关产品推荐

