- 一,概述
- 二,C-style 日期和時間庫
- 2.1,資料型別
- 2.2,函式
- 2.3,資料型別與函式關系梳理
- 2.4,時間型別
- 2.4.1,UTC 時間
- 2.4.2,本地時間
- 2.4.3,紀元時間
- 2.5,輸出時間和日期
- 2.6,綜合示例代碼
- 三,chrono 庫
- 3.1,時鐘
- 3.2,與C-style轉換
- 3.3,時長 ratio
- 3.3.1,時長運算
- 3.4,時間間隔 duration
- 3.4.1,時間間隔轉換函式 duration_cast
- 3.5,時間點 time_point
- 3.5.1,時間點運算
- 參考資料
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 以來所經過的秒數(不考慮閏秒),它在作業系統和檔案格式中被廣泛使用,
紀元時間這個想法很簡單:以一個時間為起點加上一個偏移量便可以表達任何一個其他的時間,
為什么選這個時間作為起點,可以點擊這里: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 = https://www.cnblogs.com/armcvai/archive/2022/12/05/((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++
頭檔案內容官方英文版資料
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/539234.html
標籤:其他
