C语言实现时间戳转换_避免2038年时间溢出问题

  • 我的博客:https://blog.csdn.net/qq_37388044
  • 我的知乎:https://www.zhihu.com/people/bbtganmin
  • 联系方式:知乎私信

转载或者引用本文内容请注明来源及原作者!



一、前言

  最近在开发esp32时遇到一个错误,经排查发现mktime()返回了-1。原来是向服务器获取的时间出错,服务器返回年份大于2038年,导致转换时间戳溢出。那么问题来了,难道产品使用到了2038年就要作废了吗?


二、什么是Unix时间戳?

Unix时间戳(英文为Unix epoch, Unix time, POSIX time 或 Unix
timestamp)是从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数,不考虑闰秒。
UNIX时间戳的0按照ISO 8601规范为 :1970-01-01T00:00:00Z.
一个小时表示为UNIX时间戳格式为:3600秒;一天表示为UNIX时间戳为86400秒,闰秒不计算。
在大多数的UNIX系统中UNIX时间戳存储为32位,这样会引发2038年问题或Y2038。


三、时间戳为什么会溢出?

在32位系统中,time_t是长度为32位的,有符号整数(signed int)类型。首个二进制位是符号位,用来储存正负。正数则为1970/1/1以后的时间,负数反之;其余的31位用来记数。当时间到达2038年1月19日3时14分08秒(北京时间2038年1月19日11时14分08秒)时,数值位全部向前进1,导致符号位被置1,其余31位为0。介时,将出现“时间回归”的情况,系统时间变为1901年12月13日20时45分52秒,系统将会出现错误。


四、避免方法

  • 用无符号整数(unsigned int)类型来保存和使用时间戳。
  • 方法:定义一个无符号整型来保存本地时间戳实际时间戳差值。设置时间时,只更新时间戳差值。获取时间时,获取本地时间戳和差值的和。
  • 整个过程不去设置系统本地时间戳。
typedef unsigned int uint32_t;

// 定义时间戳差值
static volatile uint32_t u32TimeD = 0;

// 获取时间
uint32_t bbtGetTime(void)
{
	return ((uint32_t)time(NULL) + u32TimeD);
}
// 设置时间
void bbtSetTime(uint32_t u32Time)
{
	u32TimeD = u32Time - (uint32_t)time(NULL);
}


五、C语言实现 时间戳 与 年月日时分秒 的互换

typedef struct bbtTM {
	int tm_sec; /* 秒 – 取值区间为[0,59] */
	int tm_min; /* 分 - 取值区间为[0,59] */
	int tm_hour; /* 时 - 取值区间为[0,23] */
	int tm_mday; /* 一个月中的日期 - 取值区间为[1,31] */
	int tm_mon; /* 月份(从一月开始,0代表一月) - 取值区间为[0,11] */
	int tm_year; /* 年份,其值等于实际年份 */
} bbtTM_S;

const char Days[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
const uint32_t mon_yday[2][12] = {
	{0,31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334},
	{0,31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335},
};


// 时间戳转年月日时分秒
void bbt_localtime(uint32_t time, bbtTM_S *t)
{
	uint32_t Pass4year;
	int hours_per_year;

	//取秒时间
	t->tm_sec=(int)(time % 60);
	time /= 60;
	//取分钟时间
	t->tm_min=(int)(time % 60);
	time /= 60;
	//取过去多少个四年,每四年有 1461*24 小时
	Pass4year = time / (1461L * 24L);
	//计算年份
	t->tm_year=(Pass4year << 2) + 1970;
	//四年中剩下的小时数
	time %= 1461L * 24L;
	//校正闰年影响的年份,计算一年中剩下的小时数
	while(1)
	{
		//一年的小时数
		hours_per_year = 365 * 24;
		//判断闰年,是闰年,一年则多24小时,即一天
		if ((t->tm_year & 3) == 0) hours_per_year += 24;

		if (time < hours_per_year) break;

		t->tm_year++;
		time -= hours_per_year;
	}
	//小时数
	t->tm_hour=(int)(time % 24);
	//一年中剩下的天数
	time /= 24;
	//假定为闰年
	time++;
	//校正闰年的误差,计算月份,日期
	if((t->tm_year & 3) == 0) {
		if (time > 60) {
			time--;
		} else {
			if (time == 60) {
				t->tm_mon = 1;
				t->tm_mday = 29;
				return ;
			}
		}
	}
	//计算月日
	for (t->tm_mon = 0; Days[t->tm_mon] < time;t->tm_mon++)
	{
		time -= Days[t->tm_mon];
	}

	t->tm_mday = (int)(time);

	return;
}


int bbtIsLeap(int year)
{
	return (year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0);
}

// 年月日时分秒转时间戳
uint32_t bbt_mktime(bbtTM_S dt)
{
	uint32_t ret;
	int i =0;
	// 以平年时间计算的秒数
	ret = (dt.tm_year - 1970) * 365 * 24 * 3600;
	ret += (mon_yday[bbtIsLeap(dt.tm_year)][dt.tm_mon] + dt.tm_mday - 1) * 24 * 3600;
	ret += dt.tm_hour * 3600 + dt.tm_min * 60 + dt.tm_sec;
	// 加上闰年的秒数
	for(i=1970; i < dt.tm_year; i++)
	{
		if(bbtIsLeap(i)) {
			ret += 24 * 3600;
		}
	}
	if (ret > 4107715199) { //2100-02-29 23:59:59
		ret += 24 * 3600;
	}
	return(ret);
}


  • 经过测试,这段代码可以使用到 2106年2月6日,nice!
  • 如果各位大哥和小姐姐们发现这算法有问题,请帮忙指出,感谢。

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值