在嵌入式軟件開(kāi)發(fā)中,有時(shí)會(huì)用到對(duì)日期時(shí)間的判斷與處理,比如記錄某個(gè)事件發(fā)生的時(shí)間,比較某個(gè)時(shí)刻已過(guò)去的時(shí)間等等。
記錄時(shí)間,可以使用ISO8601國(guó)際標(biāo)準(zhǔn)格式的時(shí)間,便于與其它軟件交互時(shí)做到統(tǒng)一。
本篇就來(lái)介紹ISO8601格式時(shí)間的生成以及兩個(gè)ISO8601格式的時(shí)間間隔的計(jì)算。
1 與時(shí)間相關(guān)的定義
在介紹具體的編程實(shí)現(xiàn)之前,需要先了解需要用到的一些與時(shí)間相關(guān)的類(lèi)型定義與函數(shù)接口
1.1 類(lèi)型與結(jié)構(gòu)體
1.1.1 timeval
存儲(chǔ)秒和微秒
struct timeval
{
long tv_sec; /*秒*/
long tv_usec; /*微秒*/
};
1.1.2 tm
表示日歷時(shí)間格式的時(shí)間
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] 小時(shí)*/
int tm_mday; /* day of the month - [1,31] 日*/
int tm_mon; /* months since January - [0,11],月,使用時(shí)一般會(huì)加1的偏移量 */
int tm_year; /* years since 1900,年,使用時(shí)一般會(huì)加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 函數(shù)
1.2.1 time
time_t time (time_t *time);
參數(shù)可以為空,用于獲取時(shí)間戳。
將自1970年1月1日以來(lái)經(jīng)過(guò)的秒數(shù)存儲(chǔ)在時(shí)間戳指針time指向的位置(time為空則不做此處理),并返回相等值的臨時(shí)time變量;
1.2.2 gettimeofday
int gettimeofday(struct timeval *tv, struct timezone *tz);
參數(shù)tv不可為空,tz通常不寫(xiě)默認(rèn)為空,用于獲取系統(tǒng)時(shí)間結(jié)構(gòu)(struct tm)。
將自1970年1月1日以來(lái)經(jīng)過(guò)的精度為微秒的時(shí)間存儲(chǔ)于tv結(jié)構(gòu)。獲取時(shí)間成功返回0,失敗返回-1。
1.2.3 localtime
struct tm *localtime (const time_t *time);
參數(shù)time不可為空。將時(shí)間戳time轉(zhuǎn)換為tm結(jié)構(gòu);
1.2.4 localtime_r
struct tm *localtime_r(const time_t *timer, struct tm *buf);
將傳入?yún)?shù)timer表示的秒數(shù)轉(zhuǎn)換為日歷時(shí)間格式,保存結(jié)果在buf,同時(shí)也會(huì)保存結(jié)果一個(gè)全局靜態(tài)變量中,返回這全局靜態(tài)變量的指針。
注:
localtime_r函數(shù)通過(guò)傳入tm參數(shù)指針保存轉(zhuǎn)換結(jié)果,使localtime_r函數(shù)線程安全。如果使用localtime_r函數(shù)返回值表示日歷,仍然是線程不安全的,通常僅通過(guò)返回值是否為空,判斷l(xiāng)ocaltime_r函數(shù)轉(zhuǎn)換時(shí)間是否成功。
2 獲取ISO8601格式的時(shí)間
2.1 ISO8601時(shí)間格式介紹
國(guó)際標(biāo)準(zhǔn)化組織的國(guó)際標(biāo)準(zhǔn)ISO 8601是日期和時(shí)間的表示方法,全稱(chēng)為《數(shù)據(jù)存儲(chǔ)和交換形式·信息交換·日期和時(shí)間的表示方法》。
最新為ISO8601:2019 ,第一版為ISO8601:1988,第二版為ISO8601:2000。
根據(jù)ISO8601標(biāo)準(zhǔn),北京時(shí)間2024年11月3日16點(diǎn)37分可以表示為:
2024-11-03T16:37:00+08:00
2.1.1 日期格式
標(biāo)準(zhǔn)日期格式為 YYYY-MM-DD,其中:
- YYYY 代表四位數(shù)的年份,如2024;MM 代表兩位數(shù)的月份,范圍01~12;DD 代表兩位數(shù)的日,范圍01~31。
2.1.2 時(shí)間格式
完整時(shí)間表示為:HH:MM:SS
- HH表示兩位數(shù)的小時(shí),24小時(shí)制;MM表示兩位數(shù)的分鐘;SS表示兩位數(shù)的秒;
可進(jìn)一步精確到毫秒,表示為:HH:MM:SS.sss
2.1.3 日期時(shí)間格式
日期和時(shí)間的組合表示為:YYYY-MM-DDTHH:MM:SS
- T是日期和時(shí)間之間的分隔符
注:
“T” 是一個(gè)全球統(tǒng)一且不常見(jiàn)的字符,使用 “T” 可以清楚地區(qū)分日期和時(shí)間這兩個(gè)不同的概念,避免混淆。
2.1.4 時(shí)區(qū)表示
ISO8601支持對(duì)時(shí)區(qū)的標(biāo)準(zhǔn)化表示,使用Z表示協(xié)調(diào)世界時(shí)(UTC),或者使用±hh:mm格式表示與UTC的偏移,例如:
- Z表示 UTC 時(shí)間;+08:00表示比 UTC 快8小時(shí)的時(shí)區(qū);-05:00表示比 UTC 慢5小時(shí)的時(shí)區(qū);
例如:
- 2024-03-19T15:26:00Z表示UTC時(shí)間下午3點(diǎn)26分0秒;2024-03-19T15:26:00+08:00表示北京時(shí)間下午3點(diǎn)26分0秒;
2.2 編程實(shí)現(xiàn)ISO8601時(shí)間的獲取
代碼思路如下:
- 獲取自1970年1月1日以來(lái)經(jīng)過(guò)的秒和微秒,存儲(chǔ)在timeval中將秒數(shù)通過(guò)localtime_r轉(zhuǎn)換為日歷時(shí)間格式結(jié)合日歷時(shí)間和微妙數(shù),格式化為ISO8601格式的時(shí)間
std::string GetISO8601NowTime()
{
timeval tv{}; //存儲(chǔ)自1970年1月1日以來(lái)經(jīng)過(guò)的秒和微秒
gettimeofday(&tv, nullptr); //獲取自1970年1月1日以來(lái)經(jīng)過(guò)的秒和微秒
tm stTM{}; //存儲(chǔ)日歷時(shí)間格式的時(shí)間
localtime_r(&tv.tv_sec, &stTM); //將傳入?yún)?shù)的秒數(shù)轉(zhuǎn)換為日歷時(shí)間格式
char sTmp[64]{}; //格式化為ISO8601格式的時(shí)間
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"; //這里時(shí)區(qū)暫使用固定的東八區(qū)
}
3 計(jì)算兩個(gè)時(shí)間的間隔
前面實(shí)現(xiàn)了ISO8601時(shí)間的獲取,如果有兩個(gè)ISO8601格式的時(shí)間,如何計(jì)算這兩個(gè)時(shí)間的間隔呢。
在實(shí)現(xiàn)該功能前,需要再來(lái)介紹需要用到的兩個(gè)函數(shù)。
3.1 函數(shù)
3.1.1 strptime
string parse time。parse,解析,用于將string格式的時(shí)間解析為tm格式
extern char *strptime (__const char *__restrict __s,
__const char *__restrict __fmt,
struct tm *__tp);
- 參數(shù)1: 輸入一個(gè)char 的指針,可通過(guò)c_str()兼容參數(shù)2: 統(tǒng)一為一個(gè)char的指針, 用于格式控制的字符串指針,可通過(guò)c_str()兼容參數(shù)3: 分解時(shí)間的存儲(chǔ),struct tm類(lèi)型的指針,可定義一個(gè)struct tm類(lèi)型,然后&實(shí)現(xiàn)
strftime:string format time。format,格式。把 time 格式化為 string
3.1.2 mktime
time_t mktime(struct tm *timeptr);
用于將結(jié)構(gòu)體 struct tm 表示的日歷時(shí)間轉(zhuǎn)換為對(duì)應(yīng)的秒數(shù)時(shí)間戳。
3.2 編程實(shí)現(xiàn)
代碼思路如下:
- 將string格式的時(shí)間解析為tm格式的日歷時(shí)間再將日歷時(shí)間轉(zhuǎn)換為對(duì)應(yīng)的秒數(shù)時(shí)間戳比較兩個(gè)時(shí)間戳 的差值即可
time_t ISO8601ToTimeT(std::string &dateTime)
{
tm stTM{};
//%F是一個(gè)代表完整日期的標(biāo)記,等同于%Y-%m-%d; %T是一個(gè)代表完整時(shí)間的標(biāo)記,等同于%H:%M:%S
strptime(dateTime.c_str(), "%FT%T", &stTM); //將string格式的時(shí)間解析為tm格式
time_t t = mktime(&stTM); //將日歷時(shí)間轉(zhuǎn)換為對(duì)應(yīng)的秒數(shù)時(shí)間戳
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 測(cè)試代碼
來(lái)編寫(xiě)一個(gè)測(cè)試代碼來(lái)驗(yàn)證剛才實(shí)現(xiàn)的功能。
- 先定義一個(gè)ISO8601格式的已過(guò)去的時(shí)間,作為測(cè)試時(shí)間間隔的old數(shù)據(jù)調(diào)用編寫(xiě)的GetISO8601NowTime獲取當(dāng)前的ISO8601格式的時(shí)間調(diào)用TimeDurationSec來(lái)計(jì)算兩個(gè)時(shí)間的差值,間隔的秒數(shù)以天、小時(shí)、分鐘、秒的形式打印出來(lái)過(guò)去的時(shí)間間隔
#include <stdio.h>
#include <ctime>
#include <sys/time.h>
#include <string>
#include <chrono>
//函數(shù)實(shí)現(xiàn)參考前面代碼
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;
}
運(yùn)行結(jié)果如下:
4 總結(jié)
本篇介紹了ISO8601格式時(shí)間的生成以及兩個(gè)ISO8601格式的時(shí)間間隔的計(jì)算。首先介紹需要用到的一些函數(shù),然后介紹編程實(shí)現(xiàn)的思路,編寫(xiě)代碼,實(shí)現(xiàn)所需的功能,最后進(jìn)行編譯運(yùn)行測(cè)試。