C++日期和時間編程小結(jié)
C++11
的日期和時間編程內(nèi)容在 C++ Primer(第五版)這本書并沒有介紹,目前網(wǎng)上的文章又大多質(zhì)量堪憂或者不成系統(tǒng),故寫下這篇文章用作自己的技術(shù)沉淀和技術(shù)分享,大部分內(nèi)容來自網(wǎng)上資料,文末也給出了參考鏈接。
日期和時間庫是每個編程語言都會提供的內(nèi)部庫,其可以用打印模塊耗時,從而方便做性能分析,也可以用作打印運行時間點。本文的內(nèi)容著重于 C++11-C++17的內(nèi)容,C++20的日期和時鐘庫雖然使用更方便也更強大,但是考慮到版本兼容和程序移植問題,故不做深入探討。
一,概述
C++ 中可以使用的日期時間 API 分為兩類:
C-style
日期時間庫,位于 頭文件中。這是原先 <time.h> 頭文件的 C++ 版本。 chrono
庫:C++ 11 中新增API,增加了時間點,時長和時鐘等相關(guān)接口(使用較為復(fù)雜)。
在 C++11 之前,C++ 編程只能使用 C-style 日期時間庫,其精度只有秒級別,這對于有高精度要求的程序來說,是不夠的。但這個問題在C++11 中得到了解決,C++11 中不僅擴展了對于精度的要求,也為不同系統(tǒng)的時間要求提供了支持。另一方面,對于只能使用 C-style 日期時間庫的程序來說,C++17 中也增加了 timespec 將精度提升到了納秒級別。
二,C-style 日期和時間庫
#include <ctime>
該頭文件包含了獲取和操作日期和時間的函數(shù)和相關(guān)數(shù)據(jù)類型定義。
2.1,數(shù)據(jù)類型
名稱 | 說明 |
---|---|
time_t | 能夠表示時間的基本算術(shù)類型的別名,能夠表示函數(shù) time 返回的時間,單位為秒級別。 |
clock_t | 能夠表示時鐘滴答計數(shù)的基本算術(shù)類型的別名(可用作進程運行時間) |
size_t | sizeof 運算符返回的無符號整數(shù)類型。 |
struct tm | 包含日歷日期和時間的結(jié)構(gòu)體類型 |
timespec* | 以秒和納秒表示的時間 |
2.2,函數(shù)
C-style
日期時間庫中包含的時間操作函數(shù)如下:
函數(shù) | 說明 |
---|---|
std::clock_t clock() | 返回自程序啟動時起的處理器時鐘時間 |
double difftime(std::time_t time_end, std::time_t time_beg) | 計算開始和結(jié)束之間的秒數(shù)差 |
std::time_t time (time_t* timer) | 返回自紀元起計的系統(tǒng)當(dāng)前時間, 函數(shù)可以為空指針 |
std::time_t mktime (struct tm * timeptr) | 將 tm 格式的時間轉(zhuǎn)換成 time_t 表示的時間 |
時間轉(zhuǎn)換函數(shù)如下:
函數(shù) | 說明 |
---|---|
char* asctime(const struct tm* timeptr) | 將 tm 結(jié)構(gòu)體對象轉(zhuǎn)換為字符串的文本 |
char* ctime(const time_t* timer) | 將 time_t 對象轉(zhuǎn)換為 C 字符串,用于表示日歷時間 |
struct tm* gmtime(const time_t* time) | 將 time_t 轉(zhuǎn)換成 UTC 表示的時間 |
struct tm* localtime(const time_t* timer) | 將 time_t 轉(zhuǎn)換成本地時間 |
localtime
函數(shù)使用參數(shù) timer
指向的值來填充 tm
結(jié)構(gòu)體,其中的值表示對應(yīng)的時間,以本地時區(qū)表示。
strftime
和 wcsftime
函數(shù)一般不常用,故不做介紹。tm
結(jié)構(gòu)體的一般定義如下:
/* Used by other time functions. */ struct tm { int tm_sec; /* Seconds. [0-60] (1 leap second) */ int tm_min; /* Minutes. [0-59] */ int tm_hour; /* Hours. [0-23] */ int tm_mday; /* Day. [1-31] */ int tm_mon; /* Month. [0-11] */ int tm_year; /* Year - 1900. */ int tm_wday; /* Day of week. [0-6] */ int tm_yday; /* Days in year.[0-365] */ int tm_isdst; /* DST. [-1/0/1]*/ };
2.3,數(shù)據(jù)類型與函數(shù)關(guān)系梳理
時間和日期相關(guān)的函數(shù)及數(shù)據(jù)類型比較多,單純看表格和代碼不是很好記憶,第一個參考鏈接的作者給出了如下所示的思維導(dǎo)圖,方便記憶與理解上面所有函數(shù)及數(shù)據(jù)類型之間各自的聯(lián)系。
在這幅圖中,以數(shù)據(jù)類型為中心,帶方向的實線箭頭表示該函數(shù)能返回相應(yīng)類型的結(jié)果。
clock
函數(shù)是相對獨立的一個函數(shù),它返回進程運行的時間,具體描述見下文。 time_t
描述了紀元時間,通過 time
函數(shù)可以獲得它,但它只能精確到秒級別。 timespec
類型在 time_t
的基礎(chǔ)上,增加了納秒的精度,通過 timespec_get
獲取。這是 C++17
上新增的特性。 tm
是日歷類型,因為它其中包含了年月日等信息。通過 gmtime,localtime 和 mktime 函數(shù)可以將 time_t 和 tm 類型互相轉(zhuǎn)換。 考慮到時區(qū)的差異,因此存在 gmtime 和 localtime 兩個函數(shù)。 無論是 time_t
還是 tm
結(jié)構(gòu),都可以將其以字符串格式輸出。ctime 和 asctime 輸出的格式是固定的。如果需要自定義格式,需要使用 strftime 或者 wcsftime 函數(shù)。
2.4,時間類型
2.4.1,UTC 時間
協(xié)調(diào)世界時(Coordinated Universial Time,簡稱 UTC)是最主要的時間標(biāo)準(zhǔn),其以原子時秒長為基礎(chǔ),在時刻上盡量接近于格林威治標(biāo)準(zhǔn)時間。
協(xié)調(diào)世界時是世界上調(diào)節(jié)時鐘和時間的主要時間標(biāo)準(zhǔn),它與0度經(jīng)線的平太陽時相差不超過 1 秒。因此UTC時間+8即可獲得北京標(biāo)準(zhǔn)時間(UTC+8)。
2.4.2,本地時間
本地時間與當(dāng)?shù)氐臅r區(qū)相關(guān),例如中國當(dāng)?shù)貢r間采用了北京標(biāo)準(zhǔn)時間(UTC+8
)。
2.4.3,紀元時間
紀元時間(Epoch time)又叫做 Unix 時間或者 POSIX 時間。它表示自1970 年 1 月 1 日 00:00 UTC 以來所經(jīng)過的秒數(shù)(不考慮閏秒)。它在操作系統(tǒng)和文件格式中被廣泛使用。**** 頭文件中通過 time_t 以秒級別表示紀元時間。
紀元時間這個想法很簡單:以一個時間為起點加上一個偏移量便可以表達任何一個其他的時間。
為什么選這個時間作為起點,可以點擊這里:Why is 1/1/1970 the “epoch time”?。
通過 time
函數(shù)獲取當(dāng)前時刻的紀元時間示例代碼如下:
time_t epoch_time = time(nullptr); cout << "Epoch time: " << epoch_time << endl; // Epoch time: 1660039180 (日歷時間: Tue Aug 9 17:59:40 2022)
time
函數(shù)接受一個指針,指向要存儲時間的對象,通??梢詡鬟f一個空指針,然后通過返回值來接受結(jié)果。雖然標(biāo)準(zhǔn)中沒有給出定義,但time_t
通常使用整形值來實現(xiàn)。
2.5,輸出時間和日期
使用 ctime
函數(shù),可以將時間以固定格式的字符串的形式打印出來,格式為:Www Mmm dd hh:mm:ss yyyy\n。代碼示例如下:
// 以字符串形式輸出當(dāng)前時間和日期 time_t now = time(nullptr); cout << "Now is: " << ctime(&now); // Now is: Tue Aug 9 18:06:38 2022
2.6,綜合示例代碼
asctime()
和 difftime()
函數(shù)等sample
代碼如下(復(fù)制可直接運行):
/* asctime example */ #include <stdio.h> /* printf */ #include <time.h> /* time_t, struct tm, time, localtime, asctime */ #include <vector> #include <iostream> using namespace std; // 冒泡排序: 將數(shù)據(jù)從小到大排序 void bubbleSort(vector<int> &arr){ size_t number = arr.size(); if (number <= 1) return; int temp; for(int i = 0; i < number; i++){ for(int j = 0; j < number-i; j++){ if (temp > arr[j+1]){ temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; } } } } // difftime() 函數(shù): 計算時間差,單位為 s void difftime_test() { vector<int> input_array; for (int i = 90000; i > 0; i--) { input_array.emplace_back(i); } time_t time1 = time(nullptr); bubbleSort(input_array); time_t time2 = time(nullptr); double time_diff = difftime(time2, time1); cout << "input array size is " << input_array.size() << " after bubbleSort time_diff: " << time_diff << "s" << endl; } // astime() 函數(shù): 將本地時間 tm 結(jié)構(gòu)體對象轉(zhuǎn)換為字符串文本 void astime_test() { time_t raw_time = time(nullptr); // 獲取當(dāng)前時刻日歷時間 struct tm* local_timeinfo = localtime(&raw_time); printf ( "The current date/time is: %s", asctime (local_timeinfo) ); } int main() { difftime_test(); astime_test(); // 3, 輸出當(dāng)前紀元時間 time_t epoch_time = time(nullptr); cout << "Epoch time: " << epoch_time << endl; // 4,以字符串形式輸出當(dāng)前時間和日期 time_t now = time(nullptr); cout << "Now is: " << ctime(&now); }
g++ time_demo.cpp -std=c++11
編譯后,運行程序 ./a.out
后,輸出結(jié)果:
三,chrono 庫
“chrono” 是英文 chronology 的縮寫,其含義是“年表;年代學(xué)”。
chrono
既是頭文件名字也是子命名空間的名字,chrono
頭文件下的所有 elements
都是在 std::chrono
命名空間下定義的。
std::chrono
是 C++11 引入的日期時間處理庫,chrono
庫里包括三種主要類型:Clocks
,Time points
和 Durations
。
3.1,時鐘
C++11
chrono
庫中包含了三種的時鐘類:
名稱 說明 chrono::system_clock
系統(tǒng)時鐘(可以調(diào)整) chrono::steady_clock
單調(diào)遞增時鐘(不能調(diào)整) chrono::high_resolution_clock
擁有可用的最短嘀嗒周期的時鐘
system_clock
是當(dāng)前所在系統(tǒng)的時鐘。因為系統(tǒng)時鐘隨時都可能被調(diào)整,所以如果想要計算兩個時間點的時間差,是不推薦使用系統(tǒng)時鐘的。
steady_clock
會保證時間的單調(diào)遞增性,只會向前移動不會減少,所以最適合用來度量時間間隔。
high_resolution_clock
表示實現(xiàn)提供的擁有最小計次周期的時鐘。它可以是 system_clock 或 steady_clock 的別名,也可能是第三個獨立時鐘。在不同的標(biāo)準(zhǔn)庫中,high_resolution_clock 的實現(xiàn)不一致,所以官方不建議使用這個時鐘。
這三個時鐘類有一些共同的成員函數(shù)和數(shù)據(jù)類型,如下所示:
名稱 說明 now()
靜態(tài)成員函數(shù),返回當(dāng)前時間,類型為 clock::time_point time_point
成員類型,當(dāng)前時鐘的時間點類型,用于表示一個具體時間,詳情見下文“時間點” duration
成員類型,時鐘的時長類型,用于表示時間間隔(一段時間),詳情見下文“時長” rep
成員類型,時鐘的 tick 類型,等同于 clock::duration::rep period
成員類型,時鐘的單位,等同于 clock::duration::period is_steady
靜態(tài)成員類型:是否是穩(wěn)定時鐘,對于 steady_clock 來說該值一定是 true
每一個時鐘類都有一個 now()
靜態(tài)函數(shù)來獲取當(dāng)前時間,返回的類型由 time_*point 描述。std::chrono::time_point 是模板類,模版類實例如:std::chrono::time_pointstd::chrono::steady\_*clock,這樣寫比較長,慶幸的是在 C++11 中可以通過 auto
關(guān)鍵字來自動推導(dǎo)變量類型。
std::chrono::time_point<std::chrono::steady_clock> now1 = std::chrono::steady_clock::now();
auto now2 = std::chrono::steady_clock::now();
3.2,與C-style轉(zhuǎn)換
system_clock 與另外兩個 clock 不一樣的地方在于,它還提供了兩個靜態(tài)函數(shù)用來將 time_point 與 std::time_t 來回轉(zhuǎn)換。
名稱 說明 to_time_t 將系統(tǒng)時鐘時間點轉(zhuǎn)換為 time_t
from_time_t 將 time_t
轉(zhuǎn)換到系統(tǒng)時鐘時間點
第一篇參考鏈接的文章給出了下面這幅圖來描述 c 風(fēng)格和 c++11 的幾種時間類型的轉(zhuǎn)換:
3.3,時長 ratio
為了支持更高精度的系統(tǒng)時鐘,C++11
新增了一個新的頭文件 <ratio>
和類型,用于自定義時間單位。std::ratio
是一個模板類,提供了編譯期的比例計算功能,為 std::chrono::duration 提供基礎(chǔ)服務(wù)。其聲明如下:
template< std::intmax_t Num, std::intmax_t Denom = 1 > class ratio;
第一個模板參數(shù) Num
(numerator) 表示分子,第二個參數(shù) Denom
(denominator) 表示分母。typedef ratio<1, 1000> milli;
表示一千分之一,因為約定了基本計算單位是秒,所以 milli
表示一千分之一秒。所以通過 ratio
可以表示毫秒、微秒、納秒等。
typedef ratio<1,1000000000> nano; // 納秒單位 typedef ratio<1,1000000> micro; // 微秒單位 typedef ratio<1,1000> milli; // 毫秒單位 typedef ratio<1,1> s // 秒單位
ratio 能表達的數(shù)值不僅僅是以 10 為基底的,同時也可以表達任意的分數(shù)秒,例如:5/7秒,89/23409 秒等等對于一個具體的 ratio 來說,可以通過 den 獲取分母的值,num 獲取分子的值。不僅僅如此,頭文件還包含了:ratio_add,ratio_subtract,ratio_multiply,ratio_divide
來完成分數(shù)的加減乘除四則運算。例如,想要計算 5/7+59/1023,可以用以下代碼表示:
ratio_add<ratio<5, 7>, ratio<59, 1023>> result; double value = ((double) result.num) / result.den; cout << result.num << "/" << result.den << " = " << value << endl; // 代碼輸出結(jié)果是 5528/7161 = 0.771959
在C++中,如果分子和分母都是整形,則整形除法結(jié)果依然是整形,即小數(shù)點右邊部分會被拋棄,因此想要獲取 double
類型的結(jié)果,需要先將其轉(zhuǎn)換成 double
。
3.3.1,時長運算
時長對象之間可以進行相加或相減運算。chrono
提供了以下幾個常用時長運算的函數(shù):
函數(shù) 說明 duration_cast
進行時長的轉(zhuǎn)換 floor(C++17)
以向下取整的方式,將一個時長轉(zhuǎn)換為另一個時長 ceil(C++17)
以向上取整的方式,將一個時長轉(zhuǎn)換為另一個時長 round(C++17)
轉(zhuǎn)換時長到另一個時長,就近取整,偶數(shù)優(yōu)先 abs(C++17)
獲取時長的絕對值 3.4,時間間隔 duration
類模板 std::chrono::duration 表示時間間隔,其聲明如下:
template< class Rep, class Period = std::ratio<1> > class duration;
類成員類型描述:
member type definition notes rep The first template parameter (Rep
) Representation type used as the type for the internal count object. period The second template parameter (Period
) The ratio type that represents a period in seconds.
duration
由 Rep
類型的計次數(shù)和Period
類型的計次周期組成,其中計次周期是一個編譯期有理數(shù)常量,表示從一個計次到下一個的秒數(shù)。存儲于 duration 的數(shù)據(jù)僅有 Rep 類型的計次數(shù)。若 Rep 是浮點數(shù),則 duration 能表示小數(shù)的計次數(shù)。 Period 被包含為時長類型的一部分,且只在不同時長間轉(zhuǎn)換時使用。
Rep
表示一種數(shù)值類型,用來表示 Period 的數(shù)量,比如 int float double (count of ticks)。 Period
是 std::ratio 類型,用來表示【用秒表示的時間單位】比如 second milisecond (a tick period)。 成員函數(shù) count()
返回 Rep
類型的 Period
數(shù)量。
常用的 duration<Rep, Period>
已經(jīng)定義好了,在 std::chrono
頭文件中,常用時長單位的代碼如下:
/// nanoseconds typedef duration<int64_t, nano> nanoseconds; /// microseconds typedef duration<int64_t, micro> microseconds; /// milliseconds typedef duration<int64_t, milli> milliseconds; /// seconds typedef duration<int64_t> seconds; /// minutes typedef duration<int, ratio< 60>> minutes; /// hours typedef duration<int, ratio<3600>> hours;
類型 定義 std::chrono::nanoseconds
duration</*至少 64 位的有符號整數(shù)類型*/, std::nano> std::chrono::microseconds
duration</*至少 55 位的有符號整數(shù)類型*/, std::micro> std::chrono::milliseconds
duration</*至少 45 位的有符號整數(shù)類型*/, std::milli> std::chrono::seconds
duration</*至少 35 位的有符號整數(shù)類型*/> std::chrono::minutes
duration</*至少 29 位的有符號整數(shù)類型*/, std::ratio<60» std::chrono::hours
duration</*至少 23 位的有符號整數(shù)類型*/, std::ratio<3600»
duration
類的 count()
成員函數(shù)返回時間間隔的具體數(shù)值。
3.4.1,時間間隔轉(zhuǎn)換函數(shù) duration_cast
因為有各種 duration
表示不同的時長單位,所以 chrono 庫提供了 duration_cast
函數(shù)來換 duration
類型,其聲明如下:
template <class ToDuration, class Rep, class Period> constexpr ToDuration duration_cast(const duration<Rep,Period>& d);
其定義比較復(fù)雜,但是我們?nèi)粘J褂每梢灾苯邮褂?auto
推導(dǎo)函數(shù)返回對象類型,示例代碼如下:
#include <iostream> #include <chrono> #include <ratio> #include <thread> void f() { std::this_thread::sleep_for(std::chrono::seconds(1)); } int main() { auto t1 = std::chrono::high_resolution_clock::now(); f(); auto t2 = std::chrono::high_resolution_clock::now(); // 整數(shù)時長:要求 duration_cast auto int_ms = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1); // 小數(shù)時長:不要求 duration_cast std::chrono::duration<double, std::milli> fp_ms = t2 - t1; std::cout << "f() took " << fp_ms.count() << " ms, " << "or " << int_ms.count() << " whole milliseconds\n"; // 程序輸出結(jié)果: f() took 1000.23 ms, or 1000 whole milliseconds }
3.5,時間點 time_point
std::chrono::time_point
表示時間中的一個點(一個具體時間),如上個世紀80年代、你的生日、今天下午、火車出發(fā)時間等,只要它能用計算機時鐘表示。其包含了時鐘和時長兩個信息。它被實現(xiàn)成如同存儲一個 Duration
類型的自 Clock
的紀元起始開始的時間間隔的值。其聲明如下:
template< class Clock, class Duration = typename Clock::duration > class time_point;
時鐘的 now()
函數(shù)返回的值就是一個時間點。time_point 中的 time_since_epoch() 返回從其時鐘起點開始的時長??梢酝ㄟ^兩個時間點相減計算一個時間間隔,下面是代碼示例:
#include <stdio.h> /* printf */ #include <iostream> #include <chrono> #include <math.h> using namespace std; void time_point_test() { auto start = chrono::steady_clock::now(); double sum = 0; for(int i = 0; i < 100000000; i++) { sum += sqrt(i); } auto end = chrono::steady_clock::now(); // 通過兩個時間點相減計算一個時間間隔 auto time_diff = end - start; // 將時間間隔單位轉(zhuǎn)化為毫秒 auto duration = chrono::duration_cast<chrono::milliseconds>(time_diff); cout << "Sqrt Operation cost : " << duration.count() << "ms" << endl; } int main() { time_point_test(); // 程序輸出結(jié)果: Sqrt Operation cost : 838ms }
3.5.1,時間點運算
時間點有加法和減法操作,計算結(jié)果和常識一致:時間點 + 時長 = 時間點;時間點 - 時間點 = 時長。
參考資料
C++ 日期和時間編程 C++日期和時間工具 C++ 頭文件內(nèi)容官方英文版資料
到此這篇關(guān)于C++日期和時間編程小結(jié)的文章就介紹到這了,更多相關(guān)C++日期和時間編程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Matlab實現(xiàn)極坐標(biāo)堆疊柱狀圖的繪制
極坐標(biāo)堆疊圖也是風(fēng)玫瑰圖的常用形式,MATLAB的bar繪制的條形圖可以繪制成堆疊形式,但是并沒有一個自帶函數(shù)可以繪制極坐標(biāo)堆疊圖。本文將為大家提供Matlab繪制極坐標(biāo)堆疊柱狀圖的示例代碼,需要的可以參考一下2022-08-08C++?中如何結(jié)束?while?(cin>>str)?的輸入
這篇文章主要介紹了C++?中如何結(jié)束?while?(cin>>str)?的輸入,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-07-07C/C++的浮點數(shù)在內(nèi)存中的存儲方式分析及實例
這篇文章主要介紹了C/C++的浮點數(shù)在內(nèi)存中的存儲方式分析及實例的相關(guān)資料,需要的朋友可以參考下2016-11-11