文/Edward

前一篇文章《聊聊时间(1)UNIX时间戳和UTC时间》中,我们讲述了计算机中UNIX时间戳和UTC时间的基本概念,以及简要地阐述了诸如STM32F103之类芯片简单功能RTC的时间转换问题,这篇文章将会夹带慢慢的干货,详细地讨论UTC时间和Linux时间戳转换的算法,这个经典的算法必将使你有一天会受用。

132位的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这种处理器来讲述的。

图1 溢出

2UNIX时间戳转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所示。

图2 计算一天之内时分秒的代码#include #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”小于86400时当前UTC时间的时分秒代码。其实,细心的读者可能会发现,这个86400数值代表的即是一天之内的UTC时分秒,那么如果当前的“u32UnixTimeStamp”计数值大于86400,我们该怎么处理呢?

我们先来从最简单的情况来推导。如果一个“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 1970年开始的年份闰年平年分布

由于闰年每四年出现一次,因此我们由图3中可以得出一个简单方法,即可以从1790年开始,每四年组成一个集合,每一个集合的都是由1年闰年加上3年平年组成的,它们的时间都是相等的,即126230400秒。

因此这个月数的求解步骤就可以变成:

(1)计算从1970年开始到当前的UNIX时间戳为止,一共过了多少个“集合年(平年+闰年)”;

(2)计算出当前的UNIX时间戳位于本“集合年“的哪一年,这样就可以判断当年年份是平年还是闰年;

(3)判断了当前年份是平年还是闰年之后,就可以推算出2月份有多少天,然后可以根据上述的递归法,求解出当前位于某一月,某一天。

首先,我们先来写代码,求出当前的年份。如图4所示。

图4 计算当前年份代码

得出当前年份之后,我们就可以很容易使用“能被4整除且不能被100整除,或者能被400整除的年份是闰年”这一条规则算出当年年份是闰年还是平年。

接着,我们可以直接将当前年份剩余的时间戳结合平年还是闰年,查表计算出当前的月份。

最后代码如图5所示。

图5 UNIX时间戳转UTC时间代码#include #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 hoursUtcTime.minute = (cnt_temp - day_temp * 86400 - UtcTime.hour * 3600) / 60; //Calculate minutesUtcTime.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 转换结果

下一期,我们将会讲述UTC时间如何转换成UNIX时间戳,以及如何进行时区转换。

unix时间戳转换c语言算法,聊聊时间(2)硬核算法篇——UNIX时间戳转UTC时间相关推荐

  1. labuladong 的算法小抄_来自GitHub 68.8k star的硬核算法教程

    很多朋友害怕算法,其实大可不必,算法题无非就那几个套路,一旦掌握,就会觉得算法实在是太朴实无华且枯燥了! 本文选自硬核算法教程<labuladong的算法小抄>,带你学习套路,把握各类算法 ...

  2. 《时空幻境》的时间倒放玩法!用 Cocos Creator 实现时间回溯

    喜欢研究各种有趣效果的「Cocos Star Writer」Nowpaper,之前同我们分享了<饥荒>同款 2.5D 视角的实现和用 RenderTexture 实现小地图与传送门等等,这 ...

  3. linux时间戳转换c语言,Unix时间戳和北京时间的相互转换(C语言实现 )

    一.问题背景 最近项目中需要上传包含时间戳的设备数据到服务器平台.原本想把"年","月","日","时","分 ...

  4. catboost和xgboost_算法竞赛开挂神器:XGBoost、LightGBM和Catboost一决高低 | 程序员硬核算法评测...

    [导读]XGBoost.LightGBM 和 Catboost 是三个基于 GBDT(Gradient Boosting Decision Tree)代表性的算法实现,今天,我们将在三轮 Battle ...

  5. 算法之unix时间戳和UTC时间(年月日)互换

    算法之unix时间戳和UTC时间(年月日)互换 author:onceday date:2022年6月8日 文章目录 算法之unix时间戳和UTC时间(年月日)互换 1.c标准库时间结构定义 2.自定 ...

  6. utc秒数 linux时间戳,UTC时间与UNIX时间戳之间的转换

    将当前系统时间转换为UNIX时间戳 BASH 1 2 $date +%s 1263236513 +%s是date的格式控制,上面命令的意思其实就是将当前系统时间以UNIX时间戳的形式输出. 如果要将任 ...

  7. android 时间戳转换成日期_Matlab将Unix时间戳转为可读日期

    Unix时间戳有很多在线转换的工具,比如: Unix时间戳(Unix timestamp)转换工具 - 站长工具​tool.chinaz.com时间戳(Unix timestamp)转换工具​tool ...

  8. mysql 日期操作 增减天数、时间转换、时间戳(转换)

    http://hi.baidu.com/juntao_li/item/094d78c6ce1aa060f6c95d0b MySQL datediff(date1,date2):两个日期相减 date1 ...

  9. linux时间戳 gps,gps时间(gps时间戳转换成utc时间)

    GPS时间系统 GPS系统是测时测距系统,GPS定位要求有高度精确的.稳定的和连续的观测时间,因此时间系统对GPS定位具有重要意义.时间系统与坐标系统一样,应. 通俗的讲,所以存在的时间系统全部由两部 ...

最新文章

  1. IOS开发错误library not found for -lXXX
  2. P4281 [AHOI2008]紧急集合 / 聚会
  3. Scala变量和常量
  4. Bazel发布Beta版本,增加对Groovy、Rust和Scala语言的支持
  5. php连接mysql总结_php连接数据库的三种方式的总结
  6. Virtual Box上的Ubuntu与Win7共享文件夹
  7. Byte Cup 2018国际机器学习竞赛夺冠记
  8. linux更新命令yum,Linux中升级更新命令yum upgrade和yum update的区别
  9. cpu针脚测试软件,Sandsifter:一款专门针对X86处理器的模糊测试工具
  10. elementUI使用之table表格如何给行元素添加点击事件
  11. Python NetworkX – Python图形库
  12. sat数学逻辑推理题解题方法
  13. GTP 协议常用术语
  14. ShareSDK分享 和新浪登陆冲突解决方法
  15. 关于用C#实现宽带的连接
  16. 微软2008年7月「最有价值专家」(MVP)当选名单
  17. 我得意地笑: 搞定了, 哈哈 如何读取Thermo Scientific Nicolet Omnic *.spa二进制格式的谱图文件中的数据
  18. 把网页知乎的视频下载下来
  19. TOUGH2系列建模方法及在CO2地质封存、水文地球化学、地热、地下水污染等领域中的实践技术应用
  20. Smart3D集群建模步骤

热门文章

  1. 对百度输入法软件的评价
  2. 跨服务器备份文件,服务器异地数据备份方案,常见的几种异地数据备份方法
  3. scratch案例——幸运大转盘
  4. 科技感的全息宴会厅,成为更多年轻人的选择
  5. 【PHP】快递100API查询快递接口
  6. lims实验室管理系统如何提高工作效率
  7. turtle库 填充颜色函数组合
  8. 计算机全真模拟试题五,计算机一级考试MSOffice全真模拟试题(1)(5)
  9. 【JD算法题】定义一个数组的权值为,该数组最大值的出现次数。求长度为n且每个元素范围都在[1,n]的所有数组的权值之和。
  10. DXT and ETC Compression Texture