当前位置: 首页 > news >正文

【江协科技STM32】Unix时间戳(学习笔记)

Unix时间戳

  • Unix 时间戳(Unix Timestamp)定义为从UTC/GMT的1970年1月1日0时0分0秒开始所经过的秒数,不考虑闰秒
  • 时间戳存储在一个秒计数器中,秒计数器为32位/64位的整型变量
  • 世界上所有时区的秒计数器相同,不同时区通过添加偏移来得到当地时间

 (北京时间)推荐时间戳换算工具:时间戳(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(协调世界时)和 GMT(格林威治标准时间)都与时间计量有关。
  • GMT 是指位于英国伦敦郊区的皇家格林威治天文台的标准时间,它以地球自转为基础,是一种天然的时间计量系统。但由于地球自转并不均匀,GMT 在时间计量上存在一定局限性。
  • UTC(Universal Time Coordinated)协调世界时是一种以原子钟为基础的时间计量系统。它规定铯133原子基态的两个超精细能级间在零磁场下跃迁辐射9,192,631,770周所持续的时间为1秒。当原子钟计时一天的时间与地球自转一周的时间相差超过0.9秒时,UTC会执行闰秒来保证其计时与地球自转的协调一致
  • UTC 是一种更精准、更科学的时间计量系统。它以原子时为基础,通过闰秒的方式与地球自转时间保持协调。UTC 在全球范围内广泛应用,是互联网、航空、航海等众多领域的标准时间。比如在国际航班的时刻表制定中,就普遍采用 UTC 时间。
  • 虽然在日常生活中,两者常被混用,但严格来说,UTC 是更现代、更精确的时间标准,而 GMT 是基于天文观测的传统时间标准。

 为什么会有闰秒现象?

 戳一下了解

闰秒现象的出现,主要是由地球自转的不稳定性原子时的高度精确性之间的矛盾所导致的。

一、根本原因:两种时间标准的差异

  1. 太阳时(基于地球自转)
    地球自转一周的时间被定义为一天,但实际上地球的自转速度并非恒定不变。
  • 变慢因素:月球和太阳的潮汐力、地球内部的物质运动等,使得地球每天的时长大约会增加1.7 毫秒
  • 不规则性:地震、气候变化等情况,可能会让地球的自转速度突然加快或减慢。
  1. 原子时(基于原子振动)
    原子时(例如 UTC 的基础 TAI)以铯原子的振动频率为基准,其精度极高,达到了每 100 万年误差不超过 1 秒,是一种均匀流逝的时间标准。

二、闰秒的作用:协调两种时间

为了防止太阳时(反映昼夜交替)和原子时(用于科学计量)之间的差距不断扩大,国际计量局(BIPM)下属的国际地球自转服务组织(IERS)会通过添加或删除闰秒的方式,让 UTC 与太阳时(UT1)的差距保持在**±0.9 秒**以内。

  • 具体操作:当 UTC 比 UT1 快时,在 6 月 30 日或 12 月 31 日的最后一秒增加一个正闰秒(如 23:59:60);如果地球自转意外加快,UTC 可能会比 UT1 慢,此时则需要添加负闰秒(不过自 1972 年以来尚未出现过这种情况)。

三、闰秒的历史与现状

  • 起源:1972 年,国际电信联盟正式引入闰秒机制,以此取代此前的 GMT。
  • 实施情况:截至 2023 年,一共添加了27 次闰秒,最近一次是在 2016 年 12 月 31 日。
  • 争议:由于闰秒会对全球的计算机系统、卫星导航等造成影响,国际上对于是否取消闰秒存在争议。目前的计划是在 2035 年左右逐步废除闰秒,但这一方案需要各国达成一致。

四、闰秒的影响

  • 积极方面:保证了天文时间(如日出日落)与民用时间的同步,对航空、航天、天文观测等领域至关重要。
  • 挑战
    • 计算机系统需要调整时间,这可能会导致服务中断(例如 2012 年闰秒曾引发 Linux 系统大规模故障)。
    • 高频交易、卫星定位等对时间精度要求极高的场景,需要应对时间跳跃问题。

总结

闰秒是人类在自然时间(地球自转)和人工时间(原子钟)之间寻求平衡的一种妥协方案。随着技术的不断发展,未来或许会出现新的时间标准来替代闰秒,但目前它仍然是维持时间体系稳定的关键因素。

 时间戳转换

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;
}


http://www.mrgr.cn/news/95751.html

相关文章:

  • tortoiseSVN、source insignt、J-flash使用
  • python --face_recognition(人脸识别,检测,特征提取,绘制鼻子,眼睛,嘴巴,眉毛)/活体检测
  • 【MySQL】一篇讲懂什么是聚簇索引和非聚簇索引(二级索引)以及什么是回表?
  • 基于PySide6的CATIA自动化工具开发实战——空几何体批量清理系统
  • 矩阵补充,最近邻查找
  • 流程控制语句
  • 【渗透测试】Fastjson 反序列化漏洞原理(一)
  • 算法训练营第二十三天 | 贪心算法(一)
  • GithubPages+自定义域名+Cloudfare加速+浏览器收录(2025最新排坑)
  • 内核中的互斥量
  • UE4学习笔记 FPS游戏制作17 让机器人持枪 销毁机器人时也销毁机器人的枪 让机器人射击
  • Linux修改SSH端口号
  • 研究生入学前文献翻译训练
  • 揭秘大数据 | 12、大数据的五大问题 之 大数据管理与大数据分析
  • V8引擎源码编译踩坑实录
  • 测试:测试中的概念
  • 【LeetCode 题解】算法:3. 无重复字符最长子串问题
  • Flink 自定义数据源:从理论到实践的全方位指南
  • Android RemoteViews:跨进程 UI 更新的奥秘与实践
  • 【性能优化点滴】odygrd/quill 中一个简单的标记位作用--降低 IO 次数