在嵌入式软件开发中,有时会用到对日期时间的判断与处理,比如记录某个事件发生的时间,比较某个时刻已过去的时间等等。
记录时间,可以使用ISO8601国际标准格式的时间,便于与其它软件交互时做到统一。
本篇就来介绍ISO8601格式时间的生成以及两个ISO8601格式的时间间隔的计算。
1 与时间相关的定义
在介绍具体的编程实现之前,需要先了解需要用到的一些与时间相关的类型定义与函数接口
1.1 类型与结构体
1.1.1 timeval
存储秒和微秒
struct timeval
{
long tv_sec; /*秒*/
long tv_usec; /*微秒*/
};
1.1.2 tm
表示日历时间格式的时间
struct tm {
int tm_sec; /* seconds after the minute - [0,59] 秒*/
int tm_min; /* minutes after the hour - [0,59] 分钟*/
int tm_hour; /* hours since midnight - [0,23] 小时*/
int tm_mday; /* day of the month - [1,31] 日*/
int tm_mon; /* months since January - [0,11],月,使用时一般会加1的偏移量 */
int tm_year; /* years since 1900,年,使用时一般会加1900的偏移量 */
int tm_wday; /* days since Sunday - [0,6] 周*/
int tm_yday; /* days since January 1 - [0,365] */
int tm_isdst; /* daylight savings time flag */
};
1.2 函数
1.2.1 time
time_t time (time_t *time);
参数可以为空,用于获取时间戳。
将自1970年1月1日以来经过的秒数存储在时间戳指针time指向的位置(time为空则不做此处理),并返回相等值的临时time变量;
1.2.2 gettimeofday
int gettimeofday(struct timeval *tv, struct timezone *tz);
参数tv不可为空,tz通常不写默认为空,用于获取系统时间结构(struct tm)。
将自1970年1月1日以来经过的精度为微秒的时间存储于tv结构。获取时间成功返回0,失败返回-1。
1.2.3 localtime
struct tm *localtime (const time_t *time);
参数time不可为空。将时间戳time转换为tm结构;
1.2.4 localtime_r
struct tm *localtime_r(const time_t *timer, struct tm *buf);
将传入参数timer表示的秒数转换为日历时间格式,保存结果在buf,同时也会保存结果一个全局静态变量中,返回这全局静态变量的指针。
注:
localtime_r函数通过传入tm参数指针保存转换结果,使localtime_r函数线程安全。 如果使用localtime_r函数返回值表示日历,仍然是线程不安全的,通常仅通过返回值是否为空,判断localtime_r函数转换时间是否成功。
2 获取ISO8601格式的时间
2.1 ISO8601时间格式介绍
国际标准化组织的国际标准ISO 8601是日期和时间的表示方法,全称为《数据存储和交换形式·信息交换·日期和时间的表示方法》。
最新为ISO8601:2019 ,第一版为ISO8601:1988,第二版为ISO8601:2000。
根据ISO8601标准,北京时间2024年11月3日16点37分可以表示为:
2024-11-03T16:37:00+08:00
2.1.1 日期格式
标准日期格式为 YYYY-MM-DD,其中:
YYYY 代表四位数的年份,如2024; MM 代表两位数的月份,范围01~12; DD 代表两位数的日,范围01~31。
2.1.2 时间格式
完整时间表示为:HH:MM:SS
HH表示两位数的小时,24小时制; MM表示两位数的分钟; SS表示两位数的秒;
可进一步精确到毫秒,表示为:HH:MM:SS.sss
2.1.3 日期时间格式
日期和时间的组合表示为:YYYY-MM-DDTHH:MM:SS
T是日期和时间之间的分隔符
注:
“T” 是一个全球统一且不常见的字符,使用 “T” 可以清楚地区分日期和时间这两个不同的概念,避免混淆。
2.1.4 时区表示
ISO8601支持对时区的标准化表示,使用Z表示协调世界时(UTC),或者使用±hh:mm格式表示与UTC的偏移,例如:
Z表示 UTC 时间; +08:00表示比 UTC 快8小时的时区; -05:00表示比 UTC 慢5小时的时区;
例如:
2024-03-19T15:26:00Z表示UTC时间下午3点26分0秒; 2024-03-19T15:26:00+08:00表示北京时间下午3点26分0秒;
2.2 编程实现ISO8601时间的获取
代码思路如下:
获取自1970年1月1日以来经过的秒和微秒,存储在timeval中 将秒数通过localtime_r转换为日历时间格式 结合日历时间和微妙数,格式化为ISO8601格式的时间
std::string GetISO8601NowTime()
{
timeval tv{}; //存储自1970年1月1日以来经过的秒和微秒
gettimeofday(&tv, nullptr); //获取自1970年1月1日以来经过的秒和微秒
tm stTM{}; //存储日历时间格式的时间
localtime_r(&tv.tv_sec, &stTM); //将传入参数的秒数转换为日历时间格式
char sTmp[64]{}; //格式化为ISO8601格式的时间
sprintf(sTmp, "%04d-%02d-%02dT%02d:%02d:%02d.%03ld",
stTM.tm_year + 1900, stTM.tm_mon + 1, stTM.tm_mday,
stTM.tm_hour, stTM.tm_min, stTM.tm_sec, tv.tv_usec/1000);
return std::string(sTmp) + "+08:00"; //这里时区暂使用固定的东八区
}
3 计算两个时间的间隔
前面实现了ISO8601时间的获取,如果有两个ISO8601格式的时间,如何计算这两个时间的间隔呢。
在实现该功能前,需要再来介绍需要用到的两个函数。
3.1 函数
3.1.1 strptime
string parse time。parse,解析,用于将string格式的时间解析为tm格式
extern char *strptime (__const char *__restrict __s,
__const char *__restrict __fmt,
struct tm *__tp);
参数1: 输入一个char 的指针,可通过c_str()兼容 参数2: 统一为一个char的指针, 用于格式控制的字符串指针,可通过c_str()兼容 参数3: 分解时间的存储,struct tm类型的指针,可定义一个struct tm类型,然后&实现
strftime:string format time。format,格式。把 time 格式化为 string
3.1.2 mktime
time_t mktime(struct tm *timeptr);
用于将结构体 struct tm 表示的日历时间转换为对应的秒数时间戳。
3.2 编程实现
代码思路如下:
将string格式的时间解析为tm格式的日历时间 再将日历时间转换为对应的秒数时间戳 比较两个时间戳 的差值即可
time_t ISO8601ToTimeT(std::string &dateTime)
{
tm stTM{};
//%F是一个代表完整日期的标记,等同于%Y-%m-%d; %T是一个代表完整时间的标记,等同于%H:%M:%S
strptime(dateTime.c_str(), "%FT%T", &stTM); //将string格式的时间解析为tm格式
time_t t = mktime(&stTM); //将日历时间转换为对应的秒数时间戳
return t;
}
uint64_t TimeDurationSec(std::string &oldT, std::string &newT)
{
auto oldPoint = std::chrono::system_clock::from_time_t(ISO8601ToTimeT(oldT));
auto newPoint = std::chrono::system_clock::from_time_t(ISO8601ToTimeT(newT));
return std::chrono::duration_cast<std::chrono::seconds>(newPoint - oldPoint).count();
}
3 测试代码
来编写一个测试代码来验证刚才实现的功能。
先定义一个ISO8601格式的已过去的时间,作为测试时间间隔的old数据 调用编写的GetISO8601NowTime获取当前的ISO8601格式的时间 调用TimeDurationSec来计算两个时间的差值,间隔的秒数 以天、小时、分钟、秒的形式打印出来过去的时间间隔
#include <stdio.h>
#include <ctime>
#include <sys/time.h>
#include <string>
#include <chrono>
//函数实现参考前面代码
int main()
{
std::string t1 = "2024-11-01T17:31:09.000";
std::string t2 = GetISO8601NowTime();
printf("t1(old):%s\nt2(now):%s\n", t1.c_str(), t2.c_str());
uint64_t deltaTotalSec = TimeDurationSec(t1, t2);
uint64_t deltaDay = deltaTotalSec / (3600*24);
uint32_t deltaHour = deltaTotalSec % (3600*24) / 3600;
uint32_t deltaMin = deltaTotalSec % 3600 / 60;
uint32_t deltaSec = deltaTotalSec % 60;
printf("delta sec:%lu(%lu day, %u hour, %u min, %u sec)\n", deltaTotalSec, deltaDay, deltaHour, deltaMin, deltaSec);
return 0;
}
运行结果如下:
4 总结
本篇介绍了ISO8601格式时间的生成和两个ISO8601格式的时间间隔的计算。首先介绍需要用到的一些函数,然后介绍编程实现的思路,编写代码,实现所需的功能,最后进行编译运行测试。
END