C++日期和時間編程小結
C++11
的日期和時間編程內容在 C++ Primer(第五版)這本書並沒有介紹,目前網上的文章又大多質量堪憂或者不成系統,故寫下這篇文章用作自己的技術沉淀和技術分享,大部分內容來自網上資料,文末也給出瞭參考鏈接。
日期和時間庫是每個編程語言都會提供的內部庫,其可以用打印模塊耗時,從而方便做性能分析,也可以用作打印運行時間點。本文的內容著重於 C++11-C++17的內容,C++20的日期和時鐘庫雖然使用更方便也更強大,但是考慮到版本兼容和程序移植問題,故不做深入探討。
一,概述
C++ 中可以使用的日期時間 API 分為兩類:
C-style
日期時間庫,位於 頭文件中。這是原先 <time.h> 頭文件的 C++ 版本。 chrono
庫:C++ 11 中新增API,增加瞭時間點,時長和時鐘等相關接口(使用較為復雜)。
在 C++11 之前,C++ 編程隻能使用 C-style 日期時間庫,其精度隻有秒級別,這對於有高精度要求的程序來說,是不夠的。但這個問題在C++11 中得到瞭解決,C++11 中不僅擴展瞭對於精度的要求,也為不同系統的時間要求提供瞭支持。另一方面,對於隻能使用 C-style 日期時間庫的程序來說,C++17 中也增加瞭 timespec 將精度提升到瞭納秒級別。
二,C-style 日期和時間庫
#include <ctime>
該頭文件包含瞭獲取和操作日期和時間的函數和相關數據類型定義。
2.1,數據類型
名稱 | 說明 |
---|---|
time_t |
能夠表示時間的基本算術類型的別名,能夠表示函數 time 返回的時間,單位為秒級別。 |
clock_t |
能夠表示時鐘滴答計數的基本算術類型的別名(可用作進程運行時間) |
size_t |
sizeof 運算符返回的無符號整數類型。 |
struct tm |
包含日歷日期和時間的結構體類型 |
timespec* | 以秒和納秒表示的時間 |
2.2,函數
C-style
日期時間庫中包含的時間操作函數如下:
函數 | 說明 |
---|---|
std::clock_t clock() |
返回自程序啟動時起的處理器時鐘時間 |
double difftime(std::time_t time_end, std::time_t time_beg) |
計算開始和結束之間的秒數差 |
std::time_t time (time_t* timer) |
返回自紀元起計的系統當前時間, 函數可以為空指針 |
std::time_t mktime (struct tm * timeptr) |
將 tm 格式的時間轉換成 time_t 表示的時間 |
時間轉換函數如下:
函數 | 說明 |
---|---|
char* asctime(const struct tm* timeptr) |
將 tm 結構體對象轉換為字符串的文本 |
char* ctime(const time_t* timer) |
將 time_t 對象轉換為 C 字符串,用於表示日歷時間 |
struct tm* gmtime(const time_t* time) |
將 time_t 轉換成 UTC 表示的時間 |
struct tm* localtime(const time_t* timer) |
將 time_t 轉換成本地時間 |
localtime
函數使用參數 timer
指向的值來填充 tm
結構體,其中的值表示對應的時間,以本地時區表示。
strftime
和 wcsftime
函數一般不常用,故不做介紹。tm
結構體的一般定義如下:
/* 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,數據類型與函數關系梳理
時間和日期相關的函數及數據類型比較多,單純看表格和代碼不是很好記憶,第一個參考鏈接的作者給出瞭如下所示的思維導圖,方便記憶與理解上面所有函數及數據類型之間各自的聯系。
在這幅圖中,以數據類型為中心,帶方向的實線箭頭表示該函數能返回相應類型的結果。
clock
函數是相對獨立的一個函數,它返回進程運行的時間,具體描述見下文。 time_t
描述瞭紀元時間,通過 time
函數可以獲得它,但它隻能精確到秒級別。 timespec
類型在 time_t
的基礎上,增加瞭納秒的精度,通過 timespec_get
獲取。這是 C++17
上新增的特性。 tm
是日歷類型,因為它其中包含瞭年月日等信息。通過 gmtime,localtime 和 mktime 函數可以將 time_t 和 tm 類型互相轉換。 考慮到時區的差異,因此存在 gmtime 和 localtime 兩個函數。 無論是 time_t
還是 tm
結構,都可以將其以字符串格式輸出。ctime 和 asctime 輸出的格式是固定的。如果需要自定義格式,需要使用 strftime 或者 wcsftime 函數。
2.4,時間類型
2.4.1,UTC 時間
協調世界時(Coordinated Universial Time,簡稱 UTC)是最主要的時間標準,其以原子時秒長為基礎,在時刻上盡量接近於格林威治標準時間。
協調世界時是世界上調節時鐘和時間的主要時間標準,它與0度經線的平太陽時相差不超過 1 秒。因此UTC時間+8即可獲得北京標準時間(UTC+8)。
2.4.2,本地時間
本地時間與當地的時區相關,例如中國當地時間采用瞭北京標準時間(UTC+8
)。
2.4.3,紀元時間
紀元時間(Epoch time)又叫做 Unix 時間或者 POSIX 時間。它表示自1970 年 1 月 1 日 00:00 UTC 以來所經過的秒數(不考慮閏秒)。它在操作系統和文件格式中被廣泛使用。**** 頭文件中通過 time_t 以秒級別表示紀元時間。
紀元時間這個想法很簡單:以一個時間為起點加上一個偏移量便可以表達任何一個其他的時間。
為什麼選這個時間作為起點,可以點擊這裡:Why is 1/1/1970 the “epoch time”?。
通過 time
函數獲取當前時刻的紀元時間示例代碼如下:
time_t epoch_time = time(nullptr); cout << "Epoch time: " << epoch_time << endl; // Epoch time: 1660039180 (日歷時間: Tue Aug 9 17:59:40 2022)
time
函數接受一個指針,指向要存儲時間的對象,通常可以傳遞一個空指針,然後通過返回值來接受結果。雖然標準中沒有給出定義,但time_t
通常使用整形值來實現。
2.5,輸出時間和日期
使用 ctime
函數,可以將時間以固定格式的字符串的形式打印出來,格式為:Www Mmm dd hh:mm:ss yyyy\n。代碼示例如下:
// 以字符串形式輸出當前時間和日期 time_t now = time(nullptr); cout << "Now is: " << ctime(&now); // Now is: Tue Aug 9 18:06:38 2022
2.6,綜合示例代碼
asctime()
和 difftime()
函數等sample
代碼如下(復制可直接運行):
/* asctime example */ #include <stdio.h> /* printf */ #include <time.h> /* time_t, struct tm, time, localtime, asctime */ #include <vector> #include <iostream> using namespace std; // 冒泡排序: 將數據從小到大排序 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() 函數: 計算時間差,單位為 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() 函數: 將本地時間 tm 結構體對象轉換為字符串文本 void astime_test() { time_t raw_time = time(nullptr); // 獲取當前時刻日歷時間 struct tm* local_timeinfo = localtime(&raw_time); printf ( "The current date/time is: %s", asctime (local_timeinfo) ); } int main() { difftime_test(); astime_test(); // 3, 輸出當前紀元時間 time_t epoch_time = time(nullptr); cout << "Epoch time: " << epoch_time << endl; // 4,以字符串形式輸出當前時間和日期 time_t now = time(nullptr); cout << "Now is: " << ctime(&now); }
g++ time_demo.cpp -std=c++11
編譯後,運行程序 ./a.out
後,輸出結果:
三,chrono 庫
“chrono” 是英文 chronology 的縮寫,其含義是“年表;年代學”。
chrono
既是頭文件名字也是子命名空間的名字,chrono
頭文件下的所有 elements
都是在 std::chrono
命名空間下定義的。
std::chrono
是 C++11 引入的日期時間處理庫,chrono
庫裡包括三種主要類型:Clocks
,Time points
和 Durations
。
3.1,時鐘
C++11
chrono
庫中包含瞭三種的時鐘類:
名稱 說明 chrono::system_clock
系統時鐘(可以調整) chrono::steady_clock
單調遞增時鐘(不能調整) chrono::high_resolution_clock
擁有可用的最短嘀嗒周期的時鐘
system_clock
是當前所在系統的時鐘。因為系統時鐘隨時都可能被調整,所以如果想要計算兩個時間點的時間差,是不推薦使用系統時鐘的。
steady_clock
會保證時間的單調遞增性,隻會向前移動不會減少,所以最適合用來度量時間間隔。
high_resolution_clock
表示實現提供的擁有最小計次周期的時鐘。它可以是 system_clock 或 steady_clock 的別名,也可能是第三個獨立時鐘。在不同的標準庫中,high_resolution_clock 的實現不一致,所以官方不建議使用這個時鐘。
這三個時鐘類有一些共同的成員函數和數據類型,如下所示:
名稱 說明 now()
靜態成員函數,返回當前時間,類型為 clock::time_point time_point
成員類型,當前時鐘的時間點類型,用於表示一個具體時間,詳情見下文“時間點” duration
成員類型,時鐘的時長類型,用於表示時間間隔(一段時間),詳情見下文“時長” rep
成員類型,時鐘的 tick 類型,等同於 clock::duration::rep period
成員類型,時鐘的單位,等同於 clock::duration::period is_steady
靜態成員類型:是否是穩定時鐘,對於 steady_clock 來說該值一定是 true
每一個時鐘類都有一個 now()
靜態函數來獲取當前時間,返回的類型由 time_*point 描述。std::chrono::time_point 是模板類,模版類實例如:std::chrono::time_pointstd::chrono::steady\_*clock,這樣寫比較長,慶幸的是在 C++11 中可以通過 auto
關鍵字來自動推導變量類型。
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轉換
system_clock 與另外兩個 clock 不一樣的地方在於,它還提供瞭兩個靜態函數用來將 time_point 與 std::time_t 來回轉換。
名稱 說明 to_time_t 將系統時鐘時間點轉換為 time_t
from_time_t 將 time_t
轉換到系統時鐘時間點
第一篇參考鏈接的文章給出瞭下面這幅圖來描述 c 風格和 c++11 的幾種時間類型的轉換:
3.3,時長 ratio
為瞭支持更高精度的系統時鐘,C++11
新增瞭一個新的頭文件 <ratio>
和類型,用於自定義時間單位。std::ratio
是一個模板類,提供瞭編譯期的比例計算功能,為 std::chrono::duration 提供基礎服務。其聲明如下:
template< std::intmax_t Num, std::intmax_t Denom = 1 > class ratio;
第一個模板參數 Num
(numerator) 表示分子,第二個參數 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 能表達的數值不僅僅是以 10 為基底的,同時也可以表達任意的分數秒,例如:5/7秒,89/23409 秒等等對於一個具體的 ratio 來說,可以通過 den 獲取分母的值,num 獲取分子的值。不僅僅如此,頭文件還包含瞭:ratio_add,ratio_subtract,ratio_multiply,ratio_divide
來完成分數的加減乘除四則運算。例如,想要計算 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; // 代碼輸出結果是 5528/7161 = 0.771959
在C++中,如果分子和分母都是整形,則整形除法結果依然是整形,即小數點右邊部分會被拋棄,因此想要獲取 double
類型的結果,需要先將其轉換成 double
。
3.3.1,時長運算
時長對象之間可以進行相加或相減運算。chrono
提供瞭以下幾個常用時長運算的函數:
函數 說明 duration_cast
進行時長的轉換 floor(C++17)
以向下取整的方式,將一個時長轉換為另一個時長 ceil(C++17)
以向上取整的方式,將一個時長轉換為另一個時長 round(C++17)
轉換時長到另一個時長,就近取整,偶數優先 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
類型的計次數和Period
類型的計次周期組成,其中計次周期是一個編譯期有理數常量,表示從一個計次到下一個的秒數。存儲於 duration 的數據僅有 Rep 類型的計次數。若 Rep 是浮點數,則 duration 能表示小數的計次數。 Period 被包含為時長類型的一部分,且隻在不同時長間轉換時使用。
Rep
表示一種數值類型,用來表示 Period 的數量,比如 int float double (count of ticks)。 Period
是 std::ratio 類型,用來表示【用秒表示的時間單位】比如 second milisecond (a tick period)。 成員函數 count()
返回 Rep
類型的 Period
數量。
常用的 duration<Rep, Period>
已經定義好瞭,在 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 位的有符號整數類型*/, std::nano> std::chrono::microseconds
duration</*至少 55 位的有符號整數類型*/, std::micro> std::chrono::milliseconds
duration</*至少 45 位的有符號整數類型*/, std::milli> std::chrono::seconds
duration</*至少 35 位的有符號整數類型*/> std::chrono::minutes
duration</*至少 29 位的有符號整數類型*/, std::ratio<60» std::chrono::hours
duration</*至少 23 位的有符號整數類型*/, std::ratio<3600»
duration
類的 count()
成員函數返回時間間隔的具體數值。
3.4.1,時間間隔轉換函數 duration_cast
因為有各種 duration
表示不同的時長單位,所以 chrono 庫提供瞭 duration_cast
函數來換 duration
類型,其聲明如下:
template <class ToDuration, class Rep, class Period> constexpr ToDuration duration_cast(const duration<Rep,Period>& d);
其定義比較復雜,但是我們日常使用可以直接使用 auto
推導函數返回對象類型,示例代碼如下:
#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(); // 整數時長:要求 duration_cast auto int_ms = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1); // 小數時長:不要求 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"; // 程序輸出結果: f() took 1000.23 ms, or 1000 whole milliseconds }
3.5,時間點 time_point
std::chrono::time_point
表示時間中的一個點(一個具體時間),如上個世紀80年代、你的生日、今天下午、火車出發時間等,隻要它能用計算機時鐘表示。其包含瞭時鐘和時長兩個信息。它被實現成如同存儲一個 Duration
類型的自 Clock
的紀元起始開始的時間間隔的值。其聲明如下:
template< class Clock, class Duration = typename Clock::duration > class time_point;
時鐘的 now()
函數返回的值就是一個時間點。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; // 將時間間隔單位轉化為毫秒 auto duration = chrono::duration_cast<chrono::milliseconds>(time_diff); cout << "Sqrt Operation cost : " << duration.count() << "ms" << endl; } int main() { time_point_test(); // 程序輸出結果: Sqrt Operation cost : 838ms }
3.5.1,時間點運算
時間點有加法和減法操作,計算結果和常識一致:時間點 + 時長 = 時間點;時間點 – 時間點 = 時長。
參考資料
C++ 日期和時間編程 C++日期和時間工具 C++ 頭文件內容官方英文版資料
到此這篇關於C++日期和時間編程小結的文章就介紹到這瞭,更多相關C++日期和時間編程內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- c++11 chrono全面解析(最高可達納秒級別的精度)
- C++使用chrono庫處理日期和時間的實現方法
- 詳解C語言編程之thread多線程
- C++ 對多線程/並發的支持(上)
- 帶你瞭解C++初階之引用