C++程序从Windows移植到Linux时, ctime/time.h中tm结构体在mktime时遇到的转换失败问题

周末在做数据结构大作业的时候遇到了一个关于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结构体时少走些弯路。警钟长鸣。

  • 5
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值