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