在嵌入式软件开发中,有时会用到对日期时间的判断与处理,比如记录某个事件发生的时间,比较某个时刻已过去的时间等等。
记录时间,可以使用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):%snt2(now):%sn", 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格式的时间间隔的计算。首先介绍需要用到的一些函数,然后介绍编程实现的思路,编写代码,实现所需的功能,最后进行编译运行测试。