以太坊 bootstrap gwt vbscript cocos2d html5 vue的钩子函数 后台管理页面模板 spark项目 多店版微信商城 纯html网页模板 js事件绑定 大数据项目开发案例 cmd查看mysql版本 mysql错误代码1064 kali重启网卡 idea批量替换快捷键 python中open python文件写入 java实战 java的数据结构 java自学教程 jdbc连接mysql java集成开发环境 sql实例 linux格式化命令 tabletpc 销售单打印软件 iphone滚动截屏 lseek函数 pr转场特效下载 一件换肤 sim卡注册失败 ram容量是什么意思 js关闭当前页面 网络驱动 c语言从入门到精通 qq农场图标 studioone mmap文件怎么打开 三菱plc序列号
当前位置: 首页 > 学习教程  > 编程语言

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

2020/12/5 10:04:54 文章标签:

目录一、前言二、什么是Unix时间戳?三、时间戳为什么会溢出?四、避免方法五、C语言实现 时间戳 与 年月日时分秒 的互换一、前言 最近在开发esp32时遇到一个错误,经排查发现mktime()返回了-1。原来是向服务器获取的时间出错,服务器…

目录

  • 一、前言
  • 二、什么是Unix时间戳?
  • 三、时间戳为什么会溢出?
  • 四、避免方法
  • 五、C语言实现 时间戳 与 年月日时分秒 的互换


一、前言

  最近在开发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语言实现 时间戳 与 年月日时分秒 的互换

  • 解决了时间戳保存问题,接下来就需要解决使用的问题。时间戳改成了无符号整型,那么C库里的 localtime() 和 mktime() 就不管用了。所以我们要自己实现时间戳的转换。
  • 在网上可以找到很多实现方法:
    https://blog.csdn.net/yaxf999/article/details/8136712
    https://www.cnblogs.com/ysen/p/5782102.html
  • 参考上面的两篇文章,我改进了一下代码,使它兼容无符号整型的时间戳。
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; /* 年份,其值等于实际年份减去1900 */
} 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!
  • 如果各位大哥和小姐姐们发现这算法有问题,请帮忙指出,感谢。


本文链接: http://www.dtmao.cc/news_show_450136.shtml

附件下载

相关教程

    暂无相关的数据...

共有条评论 网友评论

验证码: 看不清楚?