一、问题背景
最近项目中需要上传包含时间戳的设备数据到服务器平台。原本想把“年”,“月”,“日”,“时”,“分”, “秒”分别用一个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秒)。
比如在linux中,我们获取Unix时间戳可以用:
-
typedef long time_t; /* time value */
-
time_t time(time_t * timer)
调用后会返回一个time_t类型的值(即long)。由于在大多数32位的设备上,long为4个字节有符号数,所以最大秒数为:2^23,大约2038年就会存在溢出的问题。所以后面的设备都用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取余就是这个周期内的天数,然后根据平闰年去判断年月日时分秒。
四、具体代码
-
#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((year%100 == 0) && (year%400 != 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, tempDay=0;
-
uint8_t *pr, tempYear=0;
-
totleDaynum = unixTime/(24*60*60); //总天数(注意加括号)
-
totleSecNum = unixTime%(24*60*60); //当天剩余的秒速
-
memset(tempBeijing, 0x00, sizeof(rtc_time_t));
-
//1.计算哪一年
-
tempBeijing->ui8Year = 1970 + (totleDaynum/FOURYEARDAY)*4;
-
remainDayofYear = totleDaynum%FOURYEARDAY+1;
-
while(remainDayofYear >= dayPerYear[tempYear]){
-
remainDayofYear -= dayPerYear[tempYear];
-
tempBeijing->ui8Year++;
-
tempYear++;
-
}
-
//2.计算哪一月的哪一天
-
pr = isLeapYear(tempBeijing->ui8Year)?Leap_month_day:month_day;
-
while(remainDayofYear > *(pr+tempBeijing->ui8Month))
-
{
-
remainDayofYear -= *(pr+tempBeijing->ui8Month);
-
tempBeijing->ui8Month++;
-
}
-
tempBeijing->ui8Month++; //month
-
tempBeijing->ui8DayOfMonth = remainDayofYear; //day
-
//3.计算当天时间
-
tempBeijing->ui8Hour = totleSecNum/3600;
-
tempBeijing->ui8Minute = (totleSecNum%3600)/60; //error:变量搞错
-
tempBeijing->ui8Second = (totleSecNum%3600)%60;
-
//4.时区调整
-
tempBeijing->ui8Hour +=TIMEZONE;
-
if(tempBeijing->ui8Hour>23){
-
tempBeijing->ui8Hour -= 24;
-
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/%02d/%02d-%02d:%02d:%02d 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/%02d/%02d-%02d:%02d:%02d\n", UnixTimsStamp,
-
testTime.ui8Year, testTime.ui8Month, testTime.ui8DayOfMonth, \
-
testTime.ui8Hour, testTime.ui8Minute, testTime.ui8Second);
-
}
运行结果如下:
五、结果验证
通过站长工具验证,测试ok: