Unix时间戳和北京时间的相互转换(C语言实现 )

    xiaoxiao2022-07-07  290

    一、问题背景

    最近物联网项目中需要上传包含时间戳的设备数据到服务器平台。原本想把“年”,“月”,“日”,“时”,“分”, “秒”分别用一个 uint8_t 的数据类型去存储,即占用6个字节。但是在平台配置协议时,只有一种叫“Unix时间戳”的数据类型。Unix时间戳只占用4个字节,而且Unix时间戳在服务器端更加通用,但是在单片机上没有像 Linux 环境下现成的 time(),localtime(),mktime() 等库函数调用。所以考虑自己实现 Unix时间戳 和北京时间的相互转换。

    二、Unix时间戳简介

    Unix时间戳: 是从1970年1月1日00:00:00开始到当前时刻经过的秒数。 例如:一个小时表示为Unix时间戳格式为:3600秒;一天表示为Unix时间戳为86400秒。  当然由于时区的关系,北京时间在算出来的秒数后面需要加上8个小时(8*3600秒)。 比如在64位linux中,我们获取Unix时间戳可以用:

    typedef long time_t; /* time value */ time_t time(time_t * timer)

    调用后会返回一个 time_t 类型的值(即 long)。但在以前大部分的32位操作系统上,此 “time_t” 定义为有正负号的32位整数(signed int32)。即最大秒数为:2^31,大约在 2038年 就会存在秒数溢出的问题(时间重新变为1970年)。所以后面的设备都改用64位去存储,当然这不是本文探讨的地方。

    为什么使用Unix时间戳? 在服务器端使用Unix时间戳更加通用。

    三、算法转换思路

    北京时间转Unix时间戳:

    这个转换比较简单,用当前的时间的年月日时分秒,依次减去1970/1/1 00:00:00即可。只要注意闰年的情况就行,最后注意需要加上北京时区的8个小时。

    Unix时间戳转北京时间:

    不严谨的说每隔4年就有一个闰年(此处暂不考虑2100年这样的非闰年,因为time_t限制,可取的范围只有1970~2038),所以可以将4年看做一个周期(即365+365+365+366=1461天)。通过总天数除以1461得到周期的个数,然后1970加上周期的个数乘以4就是年份。总天数对1461取余就是这个周期内的天数,然后根据平闰年去判断年月日时分秒。

    特殊说明:我们这是使用无符号的 uint32_t 去保存时间戳,相比使用有符号的 int32_t,因此溢出错误会被延后到 2106 年。但本程序暂未处理 2100 这类非闰年,所以只保证 1970~2100 这时间段内的计算。

    四、具体代码

    /****************************************************************** //版本记录: // v1.初始化版本。 --19.05.23 // v2.1.经网友报错,改正一处逻辑错误。 --20.11.08 ******************************************************************/ #include <stdio.h> #include <string.h> #include "stdint.h" #define FOURYEARDAY (365+365+365+366) //4年一个周期内的总天数(1970~2038不存在2100这类年份,故暂不优化) #define TIMEZONE (8) //北京时区调整 typedef struct rtc_time_struct { uint16_t ui8Year; // 1970~2038 uint8_t ui8Month; // 1~12 uint8_t ui8DayOfMonth; // 1~31 uint8_t ui8Week; uint8_t ui8Hour; // 0~23 uint8_t ui8Minute; // 0~59 uint8_t ui8Second; // 0~59 }rtc_time_t; static uint8_t month_day[12]={31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; //平年 static uint8_t Leap_month_day[12]={31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; //闰年 //const uint16_t dayPerYear[4] = {365, 365, 365, 366}; // 判断是否是闰年 // year: 需要判断的年 // return:1:闰年 // 0: 平年 uint8_t isLeapYear(uint16_t year) { uint8_t res=0; if(year%4 == 0) // 能够被4整除 { if((year0 == 0) && (year@0 != 0)) //能够被100整除,但是不能够被400整除 { res = 0; } else { res =1; } } return res; } // 将Unix时间戳转换为北京时间 // unixTime: 需要判断的Unix时间戳 // *tempBeijing:返回的北京时间 // return:none // note:没对输入参数正确性做判断 void covUnixTimeStp2Beijing(uint32_t unixTime, rtc_time_t *tempBeijing) { uint32_t totleDayNum=0, totleSecNum=0; uint16_t remainDayofYear=0, tempDay=0, tempYear=0; uint8_t *pr=NULL; totleDayNum = unixTime/(24*60*60); //总天数(注意加括号) totleSecNum = unixTime%(24*60*60); //当天剩余的秒速 memset(tempBeijing, 0x00, sizeof(rtc_time_t)); // 1.先计算时间 HH:MM:SS tempBeijing->ui8Hour = totleSecNum/3600; tempBeijing->ui8Minute = (totleSecNum600)/60; //error:变量搞错 tempBeijing->ui8Second = (totleSecNum600)`; // 2.对时间进行时区调整(注意:这里可能造成日期 +1) tempBeijing->ui8Hour +=TIMEZONE; if(tempBeijing->ui8Hour>23){ //printf("modify day..\n"); tempBeijing->ui8Hour -= 24; remainDayofYear++; // 日期+1 } // 3.计算哪一年 tempBeijing->ui8Year = 1970 + (totleDayNum / FOURYEARDAY) * 4; // 4年为一个周期 remainDayofYear += totleDayNum % FOURYEARDAY; //printf("year:%d, day:%d.\n", tempBeijing->ui8Year, remainDayofYear); tempYear = isLeapYear(tempBeijing->ui8Year)?366:365; while(remainDayofYear >= tempYear) // 计算4年整数倍外的年。 { tempBeijing->ui8Year++; remainDayofYear -= tempYear; tempYear = isLeapYear(tempBeijing->ui8Year)?366:365; } // 4.计算哪一月的哪一天 pr = isLeapYear(tempBeijing->ui8Year)?Leap_month_day:month_day; remainDayofYear++; // 这里开始计算具体日期。remainDayofYear为 0 时其实是 1 号,所以这里要 +1 while(remainDayofYear > *(pr+tempBeijing->ui8Month)) { remainDayofYear -= *(pr+tempBeijing->ui8Month); tempBeijing->ui8Month++; } //printf("year:%d, day:%d.\n", tempBeijing->ui8Year, remainDayofYear); tempBeijing->ui8Month++; //month tempBeijing->ui8DayOfMonth = remainDayofYear; //day //printf("year:%d, day:%d.\n", tempBeijing->ui8Year, tempBeijing->ui8DayOfMonth); } // 将北京时间转换为Unix时间戳 // year: 需要判断的年 // return:Unix时间戳(从1970/1/1 00:00:00 到现在的秒数) // note:没对输入参数正确性做判断 uint32_t covBeijing2UnixTimeStp(rtc_time_t *beijingTime) { uint32_t daynum=0, SecNum=0; //保存北京时间到起始时间的天数 uint16_t tempYear=1970, tempMonth=0; //1.年的天数 while(tempYear < beijingTime->ui8Year) { if(isLeapYear(tempYear)){ daynum += 366; } else{ daynum += 365; } tempYear++; } //2.月的天数 while(tempMonth < beijingTime->ui8Month-1) { if(isLeapYear(beijingTime->ui8Year)){ //闰年 daynum += Leap_month_day[tempMonth]; } else{ daynum += month_day[tempMonth]; } tempMonth++; } //3.天数 daynum += (beijingTime->ui8DayOfMonth-1); //4.时分秒 SecNum = daynum*24*60*60; //s SecNum += beijingTime->ui8Hour*60*60; SecNum += beijingTime->ui8Minute*60; SecNum += beijingTime->ui8Second; //5.时区调整 SecNum -= TIMEZONE*60*60; return SecNum; } //测试主函数 int main() { rtc_time_t testTime; uint32_t UnixTimsStamp=0; // 测试用例:平/闰年,闰月,8点前等 // 使用时,修改这里就可以 testTime.ui8Year = 2016; testTime.ui8Month = 02; testTime.ui8DayOfMonth = 29; testTime.ui8Hour = 05; testTime.ui8Minute = 20; testTime.ui8Second = 00; UnixTimsStamp = covBeijing2UnixTimeStp(&testTime); printf("%d/d/d-d:d:d convert is: %u\n\n", \ testTime.ui8Year, testTime.ui8Month, testTime.ui8DayOfMonth, \ testTime.ui8Hour, testTime.ui8Minute, testTime.ui8Second, UnixTimsStamp); covUnixTimeStp2Beijing(UnixTimsStamp, &testTime); printf("%u convert is: %d/d/d-d:d:d\n", UnixTimsStamp, testTime.ui8Year, testTime.ui8Month, testTime.ui8DayOfMonth, \ testTime.ui8Hour, testTime.ui8Minute, testTime.ui8Second); }

    运行结果如下:

    五、结果验证

    通过站长工具验证,测试ok:

    最新回复(0)