1 32位的UNIX时间戳
我们之前的文章有介绍过UNIX操作系统是伴随着C语言一起诞生的,其诞生的时间大致是在20世纪70年代左右,当时的计算机都是以8位,16位的数据宽度去处理数据的,因此在当时那个年代,找一个32位的寄存器来存储时间,是一件比较奢侈的事情。
最早的UNIX时间戳就只是用一个32位的寄存器来存储的。而UNIX时间戳规定,UTC/GMT的1970年1月1日0时0分0秒为UNIX时间戳的起始计数时刻,即UNIX时间戳从这个时间点开始技术。由于使用的变量是一个32位的二进制数,因此这个计数器的最大计数长度为2^32=4,294,967,296s,所以在UNIX时间戳的诞生之初,它最大时间只能累积计数到2038年左右。
但是,这是不是说到了2038年这个计数器溢出之后会发生类似“千年虫”之类的危机呢?这个问题,大家无需杞人忧天,因为现在的桌面和移动处理器早已经跨入64位CPU时代了,这个UNIX时间戳计数器的长度也早已经被扩充到64位。而我们今天使用32位的UNIX时间戳计数器,完全是为了类似STM32这种处理器来讲述的。
2 UNIX时间戳转UTC时间
2.1 时分秒的转换
前面我们已经说过,UNIX时间戳是以UTC/GMT时间的1970年1月1日0时0分0秒为起始时刻进行计数的。考虑到这个UNIX时间戳的计数器的数据长度为32位,我们不妨先将这个UNIX时间戳计数器定义为一个变量,其名称为“u32UnixTimeStamp”。
首先,基于上述的阐述,我们思考下,当这个1970年1月1日0时0分0秒时刻过了1s,那么“u32UnixTimeStamp”变量的值也会自加1,变成1。
随着时间的流逝,“u32UnixTimeStamp”的值不断地自加,当“u32UnixTimeStamp”变成60的时候,我们回过头来再看看UTC时间,由于此时已经过了60s,因此UTC时间的秒数值将会溢出被清掉,分数值将会被置1。这个时候UTC的时间变成了1970年1月1日0时1分0秒.
再随着时间的流逝,当“u32UnixTimeStamp”的值变成3600的时候,此时1个小时已经悄然流逝,这个时候UTC的时间变成了1970年1月1日1时0分0秒。
86400秒流逝之后,“u32UnixTimeStamp”的值变成86400,此时正好24个小时过去,此时UTC时间变成了1970年1月2日0时0分0秒。
其实,当UNIX时间戳的计数值小于86400的时候,我们很容易就能写出转换成UTC时间的程序,因为小时数就是“u32UnixTimeStamp”对3200取模,分数就是将不能凑满小时的“u32UnixTimeStamp”对60取模,剩余不能凑满分数的“u32UnixTimeStamp”即为当前时间的秒数,具体代码如图2所示。
- #include <stdio.h>
- #include "timex_test1.h"
- utc_t UtcTime;
- int main(void)
- {
- int retVal;
- int u32UnixTimeStamp = 0;
- int hour, minute, sec;
- printf("input the UNIX time stamp:");
- scanf("%d", &u32UnixTimeStamp);
- if(u32UnixTimeStamp >= 86400)
- {
- printf("input out of range!\n");
- retVal = 1;
- }
- else
- {
- hour = u32UnixTimeStamp / 3600; //计算小时
- minute = (u32UnixTimeStamp - hour * 3600) / 60; //计算分
- sec = (u32UnixTimeStamp - hour * 3600 - minute * 60); //计算秒
- UtcTime.year = 1970;
- UtcTime.month = 1;
- UtcTime.date = 1;
- UtcTime.hour = 0 + hour;
- UtcTime.minute = 0 + minute;
- UtcTime.second = 0 + sec;
- printf("\nUTC time is : %dy - %dm - %dd\n", UtcTime.year, UtcTime.month, UtcTime.date);
- printf("\nUTC time is : %dh - %dm : %ds\n", UtcTime.hour, UtcTime.minute, UtcTime.second);
- retVal = 0;
- }
- return retVal;
- }
- #ifndef __TIMEX_H_
- #define __TIMEX_H_
- /*定义UTC时间结构体类型*/
- typedef struct
- {
- int year;
- int month;
- int date;
- int hour;
- int minute;
- int second;
- } utc_t;
- #endif
我们先来从最简单的情况来推导。如果一个“u32UnixTimeStamp”为86401,那么此时我们可以知道,这个UNIX时间戳其实就是86400 + 1,已知当“u32UnixTimeStamp”为86400时,UTC时间将会变成1970年1月2日0时0分0秒,那么此时86401即为1970年1月2日0时0分0秒 + 1秒,即1970年1月2日0时0分1秒。由这个简单的推导,我们又可以将大于86400的数值拉到0~86400的范围之内了,同时,还可以得到一个和日期相关的“原子”单位,即“日”。
2.2 年月日的转换
接下来,我们将要再对“日”的上一层单位进行讨论,即“月”数值,这也将是这个程序最为复杂的一部分内容。
这个复杂点主要体现在两个方面:
(1)每个月的天数不等。众所周知,一年中每个月的天数都是不同的,1,3,5,7,8,10,12为大月,一个月有31天;4,6,9,11为小月,一个月有30天。
(2)闰年平年的影响。由于公历的偏差,导致了一年中最为特殊的一个月份2月,当此年为闰年时,2月份有29天,此年为平年时,2月份有28天。
上面两个原因,导致了年月日计算的复杂性。
但是,困难只是表面上的,我们仔细思考下,就很容易得出规律。这个规律的突破口即为闰年出现的时间,因为闰年每四年出现一次,那么我们可以列出从1970年开始的几个年份。如图3所示。
由于闰年每四年出现一次,因此我们由图3中可以得出一个简单方法,即可以从1790年开始,每四年组成一个集合,每一个集合的都是由1年闰年加上3年平年组成的,它们的时间都是相等的,即126230400秒。
因此这个月数的求解步骤就可以变成:
(1)计算从1970年开始到当前的UNIX时间戳为止,一共过了多少个“集合年(平年+闰年)”;
(2)计算出当前的UNIX时间戳位于本“集合年“的哪一年,这样就可以判断当年年份是平年还是闰年;
(3)判断了当前年份是平年还是闰年之后,就可以推算出2月份有多少天,然后可以根据上述的递归法,求解出当前位于某一月,某一天。
首先,我们先来写代码,求出当前的年份。如图4所示。
图4 计算当前年份代码
得出当前年份之后,我们就可以很容易使用“能被4整除且不能被100整除,或者能被400整除的年份是闰年”这一条规则算出当年年份是闰年还是平年。
接着,我们可以直接将当前年份剩余的时间戳结合平年还是闰年,查表计算出当前的月份。
最后代码如图5所示。
图5 UNIX时间戳转UTC时间代码
- #include <stdio.h>
- #include "timex_test2.h"
- utc_t UtcTime;
- int main(void)
- {
- int retVal;
- int u32UnixTimeStamp = 0;
- int hour, minute, sec;
- int flat_year_month_day[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
- int leap_year_month_day[13] = {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
- int year_temp = 0;
- int day_temp = 0;
- unsigned int cnt_temp = 0;
- int is_leap_or_flat_year;
- int i;
- printf("input the UNIX time stamp:");
- scanf("%d", &u32UnixTimeStamp);
- cnt_temp = u32UnixTimeStamp;
- /*判断当前UNIX时间戳经过了多少个4年*/
- while(cnt_temp >= 126230400)
- {
- year_temp ++;
- cnt_temp = cnt_temp - 126230400;
- }
- /*计算出当前的4年周期起始年份*/
- UtcTime.year = UNIX_TIME_YEAR + (4 * year_temp);
- /*计算出当前的年份*/
- /*这部分代码可使用循环做精简,为了直观,我将其写开*/
- if(cnt_temp >= 31536000)
- {
- UtcTime.year ++;
- cnt_temp -= 31536000;
- /*Flat year*/
- if(cnt_temp >= 31536000)
- {
- UtcTime.year ++;
- cnt_temp -= 31536000;
- /*Leap year*/
- if(cnt_temp >= 31622400)
- {
- UtcTime.year ++;
- cnt_temp -= 31622400;
- /*Flat year*/
- if(cnt_temp >= 31536000)
- {
- UtcTime.year ++;
- cnt_temp -= 31536000;
- }
- }
- }
- }
- /*计算当前年份是平年还是闰年*/
- if((((UtcTime.year % 4) == 0) && ((UtcTime.year % 100) != 0)) || ((UtcTime.year % 400) == 0))
- {
- is_leap_or_flat_year = LEAP_YEAR;
- }
- else
- {
- is_leap_or_flat_year = FLAT_YEAR;
- }
- /*计算出不足一年剩余的天数*/
- day_temp = cnt_temp / 86400;
- /*剩余不足86400s的时间戳,计算出时间*/
- UtcTime.hour = (cnt_temp - day_temp * 86400) / 3600; //Calculate hours
- UtcTime.minute = (cnt_temp - day_temp * 86400 - UtcTime.hour * 3600) / 60; //Calculate minutes
- UtcTime.second = cnt_temp % 60;
- /*将天数结合平年还是闰年查表计算出当前的月份*/
- UtcTime.month = 1;
- for(i = 0; i < 12; i ++)
- {
- if(is_leap_or_flat_year == FLAT_YEAR)
- {
- if(day_temp >= flat_year_month_day[i + 1])
- {
- UtcTime.month ++;
- day_temp -= flat_year_month_day[i + 1];
- }
- }
- else if(is_leap_or_flat_year == LEAP_YEAR)
- {
- if(day_temp >= leap_year_month_day[i + 1])
- {
- UtcTime.month ++;
- day_temp -= leap_year_month_day[i + 1];
- }
- }
- }
- /*由于天数从1开始,因此需要加1*/
- UtcTime.date = day_temp + 1;
- printf("\nTime transform successfully\n");
- printf("++++++++++++++++++++++++++++++++++\n");
- printf("\nUTC time is : %dy - %dm - %dd\n", UtcTime.year, UtcTime.month, UtcTime.date);
- printf("\nUTC time is : %dh - %dm : %ds\n", UtcTime.hour, UtcTime.minute, UtcTime.second);
- printf("++++++++++++++++++++++++++++++++++\n");
- printf("\n");
- printf("\n");
- return 0;
- }
- #ifndef __TIMEX_H_
- #define __TIMEX_H_
- /*定义UTC时间结构体类型*/
- typedef struct
- {
- int year;
- int month;
- int date;
- int hour;
- int minute;
- int second;
- } utc_t;
- /*定义UNIX时间戳的起始UNIX时间*/
- #define UNIX_TIME_YEAR 1970
- #define UNIX_TIME_MONTH 1
- #define UNIX_TIME_DATE 1
- #define UNIX_TIME_HOUR 0
- #define UNIX_TIME_MINIUTE 0
- #define UNIX_TIME_SECOND 0
- #define LEAP_YEAR 1
- #define FLAT_YEAR 0
- #endif
最后,我们可以使用在线工具来验证转换结果是否正确,可以参考下列链接的UNIX时间戳在线转换工具,链接:https://tool.lu/timestamp/
假设我们同时输入UNIX时间戳1615906780,可以得出转换的北京时间为2021-03-16 22:59:40,而我们程序转换得出的时间为2021-03-16 14:59:40,这两个结果看似有出入,其实是一致的,我们知道北京时间是东8区的时间,而UTC时间是0时区的时间,因此相差8小时。如图6所示。
图6 转换结果
本文源自微信公众号:乐创客,不代表用户或本站观点,如有侵权,请联系nick.zong@aspencore.com 删除!