【江协科技STM32】Unix时间戳详解与学习笔记分享
Unix时间戳
(北京时间)推荐时间戳换算工具:时间戳(Unix timestamp)转换工具 – 在线工具
Unix 时间戳的优缺点
Unix 时间戳的优点主要有:其一,简单统一,全球通用,方便不同系统和地区间进行时间数据交互与处理,在分布式系统中优势明显。其二,存储和计算高效,以一个整数表示时间,在存储上占用空间小,计算时间差值等操作简单直接,利于提升程序性能。其三,便于排序,由于是数值型,按时间顺序排列时,数值大小顺序就对应时间先后顺序。
然而,它也存在一些缺点:一方面,可读性差,Unix 时间戳是一个数值,对于人来说,很难直观理解其代表的具体时间,需要转换为人类可读的日期时间格式。另一方面,存在溢出风险,随着时间推移,32 位系统下表示时间戳的整数可能会达到上限,导致溢出问题,不过 64 位系统大大延长了可表示的时间范围。此外,它依赖于特定的起始时间,若系统时间计算或起始时间设置有误,可能导致时间数据错误。
一些不用我们担心
框图说明我们这款STM32,时间戳是32位的数据类型,32位的时间戳,表示我们这款STM32芯片也会在2038年出现BUG吗?实际上并不会,根据up猪的研究,这个时间戳在STM32程序中定义的是无符号的32位。 无符号 32 位意味着它使用 32 个二进制位来表示数据,且这 32 位全部用于表示数值大小,不用于表示正负。其表示的范围是从 0 到 2 的 32 次方减 1 。具体计算为 2³² – 1 = 4294967295 。也就是说,无符号 32 位能够表示的最大值是 4294967295,最小值是 0。计算一下要到2106年才会溢出。
UTC/GMT
为什么会有闰秒现象?
戳一下了解
闰秒现象的出现,主要是由地球自转的不稳定性与原子时的高度精确性之间的矛盾所导致的。
一、根本原因:两种时间标准的差异
- 太阳时(基于地球自转)
地球自转一周的时间被定义为一天,但实际上地球的自转速度并非恒定不变。
- 原子时(基于原子振动)
原子时(例如 UTC 的基础 TAI)以铯原子的振动频率为基准,其精度极高,达到了每 100 万年误差不超过 1 秒,是一种均匀流逝的时间标准。
二、闰秒的作用:协调两种时间
为了防止太阳时(反映昼夜交替)和原子时(用于科学计量)之间的差距不断扩大,国际计量局(BIPM)下属的国际地球自转服务组织(IERS)会通过添加或删除闰秒的方式,让 UTC 与太阳时(UT1)的差距保持在**±0.9 秒**以内。
三、闰秒的历史与现状
四、闰秒的影响
总结
闰秒是人类在自然时间(地球自转)和人工时间(原子钟)之间寻求平衡的一种妥协方案。随着技术的不断发展,未来或许会出现新的时间标准来替代闰秒,但目前它仍然是维持时间体系稳定的关键因素。
时间戳转换
C语言的time.h模块提供了时间获取和时间戳转换的相关函数,可以方便地进行秒计数器、日期时间和字符串之间的转换
函数 |
作用 |
time_t time(time_t*); |
获取系统时钟 |
struct tm* gmtime(const time_t*); |
秒计数器转换为日期时间(格林尼治时间) |
struct tm* localtime(const time_t*); |
秒计数器转换为日期时间(当地时间) |
time_t mktime(struct tm*); |
日期时间转换为秒计数器(当地时间) |
char* ctime(const time_t*); |
秒计数器转换为字符串(默认格式) |
char* asctime(const struct tm*); |
日期时间转换为字符串(默认格式) |
size_t strftime(char*, size_t, const char*, const struct tm*); |
日期时间转换为字符串(自定义格式) |
自学菜鸟教程,有上面各个函数的讲解:C 标准库 – <time.h> | 菜鸟教程
获取系统时钟
函数转换关系:
#include <stdio.h>
#include <time.h>
time_t time_cnt; //秒计数器数据类型,,64位秒计数器,不用担心计数溢出
int main()
{
ime_cnt = time(NULL);
printf("%d\n",time_cnt);
return 0;
}
运行得到时间戳的秒数,复制到时间戳换算工具可以知道当前时间
通过输出参数获取:现象跟上面一样
#include <stdio.h>
#include <time.h>
time_t time_cnt; //秒计数器数据类型
int main()
{
//ime_cnt = time(NULL);
time(&time_cnt);
printf("%d\n",time_cnt);
return 0;
}
秒计数器转换为日期时间(格林尼治时间)
#include <stdio.h>
#include <time.h>
struct tm time_data; //日期时间数据类型
int main()
{
ime_cnt = 1672588795;
time_data = *gmtime(&time_cnt);
printf("%d\n",time_data.tm_year+1900);
printf("%d\n",time_data.tm_mon+1);
printf("%d\n",time_data.tm_mday);
printf("%d\n",time_data.tm_hour);
printf("%d\n",time_data.tm_min);
printf("%d\n",time_data.tm_sec);
printf("%d\n",time_data.tm_wday);
return 0;
}
运行结果时间跟PPT伦敦时间一样
秒计数器转换为日期时间(当地时间)
这个函数和上一个函数一样,此函数会根据时区的偏移自动增加小时的偏移,在程序中改一下函数名即可,函数内部会根据当前电脑设置,自动判断我们在哪个时区,然后把时间添加时区偏移后输出出来
日期时间转换为秒计数器(当地时间)
上面函数的逆过程,需要传输的是当地时间,不能是伦敦时间
#include <stdio.h>
#include <time.h>
time_t time_cnt; //秒计数器数据类型,64位秒计数器,不用担心计数溢出
struct tm time_data; //日期时间数据类型
int main()
{
time_cnt = 1672588795;
printf("%d\n",time_cnt);
time_data = *localtime(&time_cnt);
printf("%d\n",time_data.tm_year+1900); /* 自 1900 年起的年数 */
printf("%d\n",time_data.tm_mon+1); /* 月,范围从 0 到 11 */
printf("%d\n",time_data.tm_mday); /* 一月中的第几天,范围从 1 到 31 */
printf("%d\n",time_data.tm_hour); /* 小时,范围从 0 到 23 */
printf("%d\n",time_data.tm_min); /* 分,范围从 0 到 59 */
printf("%d\n",time_data.tm_sec); /* 秒,范围从 0 到 59 */
printf("%d\n",time_data.tm_wday); /* 一周中的第几天,范围从 0 到 6 */
time_cnt = mktime(&time_data); //返回对应秒计数器值,只能用于当地时间
printf("%d\n",time_cnt);
return 0;
}
秒数和最初一样
秒计数器转换为字符串(默认格式)
#include <stdio.h>
#include <time.h>
time_t time_cnt; //秒计数器数据类型,64位秒计数器,不用担心计数溢出
struct tm time_data; //日期时间数据类型
char *time_str; //字符串数据类型
int main()
{
time_cnt = 1672588795;
printf("%d\n",time_cnt);
time_data = *localtime(&time_cnt);
printf("%d\n",time_data.tm_year+1900); /* 自 1900 年起的年数 */
printf("%d\n",time_data.tm_mon+1); /* 月,范围从 0 到 11 */
printf("%d\n",time_data.tm_mday); /* 一月中的第几天,范围从 1 到 31 */
printf("%d\n",time_data.tm_hour); /* 小时,范围从 0 到 23 */
printf("%d\n",time_data.tm_min); /* 分,范围从 0 到 59 */
printf("%d\n",time_data.tm_sec); /* 秒,范围从 0 到 59 */
printf("%d\n",time_data.tm_wday); /* 一周中的第几天,范围从 0 到 6 */
time_cnt = mktime(&time_data);
printf("%d\n",time_cnt);
time_str = ctime(&time_cnt); //秒计数器转换为字符串
printf(time_str);
return 0;
}
日期时间转换为字符串(默认格式)
#include <stdio.h>
#include <time.h>
time_t time_cnt; //秒计数器数据类型,64位秒计数器,不用担心计数溢出
struct tm time_data; //日期时间数据类型
char *time_str; //字符串数据类型
int main()
{
time_cnt = 1742871974;
printf("%d\n",time_cnt);
time_data = *localStime(&time_cnt);
printf("%d\n",time_data.tm_year+1900); /* 自 1900 年起的年数 */
printf("%d\n",time_data.tm_mon+1); /* 月,范围从 0 到 11 */
printf("%d\n",time_data.tm_mday); /* 一月中的第几天,范围从 1 到 31 */
printf("%d\n",time_data.tm_hour); /* 小时,范围从 0 到 23 */
printf("%d\n",time_data.tm_min); /* 分,范围从 0 到 59 */
printf("%d\n",time_data.tm_sec); /* 秒,范围从 0 到 59 */
printf("%d\n",time_data.tm_wday); /* 一周中的第几天,范围从 0 到 6 */
time_cnt = mktime(&time_data);
printf("%d\n",time_cnt);
time_str = ctime(&time_cnt); //秒计数器转换为字符串
printf(time_str);
time_str = asctime(&time_data); //秒计数器转换为字符串
printf(time_str);
return 0;
}
上两个函数结果一样
日期时间转换为字符串(自定义格式)
这个函数作用和上面的一样,但是可以自定义格时,下面提供自己的实践代码,具体学习参考菜鸟教程
#include <stdio.h>
#include <time.h>
time_t time_cnt; //秒计数器数据类型,64位秒计数器,不用担心计数溢出
struct tm time_data; //日期时间数据类型
char *time_str; //字符串数据类型
int main()
{
time_cnt = 1742871974;
printf("%d\n",time_cnt);
time_data = *localtime(&time_cnt);
printf("%d\n",time_data.tm_year+1900); /* 自 1900 年起的年数 */
printf("%d\n",time_data.tm_mon+1); /* 月,范围从 0 到 11 */
printf("%d\n",time_data.tm_mday); /* 一月中的第几天,范围从 1 到 31 */
printf("%d\n",time_data.tm_hour); /* 小时,范围从 0 到 23 */
printf("%d\n",time_data.tm_min); /* 分,范围从 0 到 59 */
printf("%d\n",time_data.tm_sec); /* 秒,范围从 0 到 59 */
printf("%d\n",time_data.tm_wday); /* 一周中的第几天,范围从 0 到 6 */
time_cnt = mktime(&time_data);
printf("%d\n",time_cnt);
time_str = ctime(&time_cnt); //秒计数器转换为字符串
printf(time_str);
time_str = asctime(&time_data); //秒计数器转换为字符串
printf(time_str);
char buffer[60]; //定义一个数组
strftime(buffer,60,"%Y-%m-%d %H:%M:%S %A",&time_data);
printf(buffer);
return 0;
}
作者:傍晚冰川