周末在做数据结构大作业的时候遇到了一个关于ctime/time.h时间戳的bug,debug了好长时间。
作业的大致内容是构建一个模拟医院预约时间安排系统,我们在其中采用了C++的ctime库,通过递增time_t型时间戳进行时间模拟。其中设定模拟开始的时间代码如下:
tm start_time;
start_time.tm_year = 2022 -1900;
start_time.tm_mon = 4 -1;
start_time.tm_mday = 1;
start_time.tm_hour = 0;
start_time.tm_min = 0;
start_time.tm_sec = 0;
time_t clock;
tm clock_tm = start_time; //tm struct of start_time.
clock = mktime(&start_time); //Used for time-processing simulation.
time_t start_time_ts = clock; //Store a copy of the the timestamp of the start time.
这里假设模拟开始时间是2022年4月1日,time_t型变量clock(及其对应的tm结构体clock_tm)用于后续递增时间,start_time和start_time_ts都是为了存储开始时间。
之后在while循环中的时间递增语句为:
clock += 3600; //One iteration means one hour
clock_tm = *localtime(&clock);
另外,由于我们假设每半天(12小时)处理一次预约,将预约记录从队列转入斐波那契堆中,我们每半天输出一次时间作为提示。
if (clock_tm.tm_hour==0 || clock_tm.tm_hour==12)
{
cout << "The time now is " << clock_tm.tm_year + 1900 << "-"
<< clock_tm.tm_mon + 1 << "-" << clock_tm.tm_mday << " "
<< clock_tm.tm_hour << ":" << clock_tm.tm_min << ":"
<< clock_tm.tm_sec << endl;
//后面处理注册信息的部分省略
}
首先是在Visual Studio 2022里用CMake项目调试,在windows环境下可以正常运行。没有出现过问题。
但是在我们移植到Linux系统后用cmake运行时(做移植是因为教授要求要在Linux环境下能运行),却发现:开始时时间为2022年4月1日00:00:00没错,但在第二次输出时间时,输出的时间又回到了1970年1月1日12:59:59!之后我们多次尝试重新cmake,发现只有在很少时候会正常输出2022年4月1日12:00:00,大部分时间都是输出1970年1月1日12:59:59。
一开始我们以为是Linux用的库是time.h而不是ctime,于是修改了库名称,但是却无济于事。然后我们以为是终端缓存造成溢出,即终端需要清理缓存,但是同样的问题依然未解决。
在查阅一些资料之后,我们了解到在tm结构体转换成时间戳time_t时使用的mktime函数,在转换失败时会返回-1,而由于0对应的时间为UTC标准时1970年1月1日00:00:00(即时间戳time_t的计时起点),换算成北京时间(东八区)就是1970年1月1日08:00:00。所以在我们运行时,mktime生成失败返回的-1被存到了clock_tm中变成了1970年1月1日07:59:59,于是才有了在第二次输出时间时输出在其12小时之后的1970年1月1日12:59:59的情况。
而第一次输出正确是因为当时存在clock_tm中的数据是我们赋的原始数据,没有经过mktime和localtime的转换,输出的时候直接读出我们赋的值,于是仍是2022年4月1日00:00:00。
由于之后的时间递增不涉及mktime,于是之后时间递增正常。
所以,出现这一问题的根本原因是mktime在将tm结构体转为时间戳时转换失败。
那么为何会转换失败呢?我们应该如何才能让这一转换成功呢?
又经过一番查阅资料以及调试,我们发现转换失败的原因是tm结构体不完整,它的tm_isdst参数(是否为夏令时)没有设置,导致mktime无法确定转换后的时间戳,于是转换失败,返回-1.
至于夏令时是什么玩意,我也不清楚,貌似是美国人弄出来的,中国不使用,所以我们将这个参数设为0即可。
以下是修改后的代码:
tm start_time;
start_time.tm_year = 2022 -1900;
start_time.tm_mon = 4 -1;
start_time.tm_mday = 1;
start_time.tm_hour = 0;
start_time.tm_min = 0;
start_time.tm_sec = 0;
start_time.tm_isdst = 0; //这里
time_t clock;
tm clock_tm = start_time; //tm struct of start_time.
clock = mktime(&start_time); // used for time-processing simulation.
time_t start_time_ts = clock; //Store a copy of the the timestamp of the start time.
这样修改之后,我们又将程序中其他有类似转换的地方都加上了设置tm_isdst为0的语句,然后时间模拟就正常了,所以建议在使用tm结构体设置时间的时候,不仅要将年月日时分秒设置好,也要把这个夏令时参数(tm_isdst)设置好。
当然还有几个尚未解决的问题,期待大佬指点:
1. 为何同样的代码在windows环境下的visual studio 2022 中就不会报错?
2. 为何有些时候(少数情况下)在Linux环境下又可以正常运行?
这里把debug经历分享给大家,希望大家以后用ctime/time.h的tm结构体时少走些弯路。警钟长鸣。