C++/STL/chrono
程式語言中,chrono
是標準模板庫(STL)中與時間有關的頭文件(自C++11開始)。該頭文件中所有函數與類模板均定義在std::chrono命名空間中。
概念
編輯- duration:時間長度,如1分鐘、2小時、10毫秒等。表示為類模板duration的對象,用一個count representation與一個period precision表示。例如,10毫秒的10為count representation,毫秒為period precision。
- time points:表示一個時間點。例如某人的生日、今天的日出時間等。表示為類模板time_point的對象。用相對於一個固定時間點epoch的duration來表示。
- clocks:時間點相對於真實物理時間的框架。C++11提供了3個clock,C++20還提供了4個clock:
- system_clock:可被管理員手工調整
- steady_clock:不受管理員人為影響,適用於時間流逝的計量
- high_resolution_clock
- utc_clock — UTC時間,包括閏秒
- tai_clock — 國際原子時間,不含閏秒
- gps_clock — GPS時間,不含閏秒
- file_clock — 文件系統時間
類模板
編輯時間長度duration
編輯表示time span。該類模板聲明為:
template <class Rep, class Period=ratio<1> > class duration;
第一個模板參數為存儲時間計數的數據類型,如整型、浮點型等。成員函數count()返回該計數。第二個模板參數表示計數的一個周期,一般是std::ratio(即有理數)類型,表示一個周期(即一個時間嘀嗒tick)是秒鐘的倍數或分數,在編譯時應為一個有理數常量。
duration模板類實例化typedef:
類型 | Rep | Period |
---|---|---|
years | signed integral至少17比特 | std::ratio<86400> |
months | signed integral至少20比特 | ratio<2629746,1> |
weeks | signed integral至少22比特 | ratio<604800,1> |
days | signed integral至少25比特 | ratio<86400,1> |
hours | signed integral至少23比特 | ratio<3600,1> |
minutes | signed integral至少29比特 | ratio<60,1> |
seconds | signed integral至少35比特 | ratio<1,1> |
miliseconds | signed integral至少45比特 | ratio<1,1000> |
microseconds | signed integral至少55比特 | ratio<1,1000000> |
nanoseconds | signed integral至少64比特 | ratio<1,1000000000> |
rep | 對應於模板的第1個參數 | |
period | 對應於模板的第2個參數 |
chrono提供的預定義的時間長度單位都是int64類型的內部存儲數據。
- duration模板類成員函數:
- 構造函數
- 析構函數
- count 返回時間嘀嗒計數
- zero: 靜態函數,返回0值實例
- min:靜態函數,表示最小可能值。例如
std::cout << (seconds::min)();
- max:靜態函數,表示最大可能值
- operator= 賦值運算符
- operator+ 酉運算符
- operator- 酉運算符
- operator++ 前綴++
- operator++(int) 後綴++
- operator-- 前綴--
- operator--(int) 後綴--
- operator+=
- operator-=
- operator*=
- operator/=
- operator%=
- 非成員函數
- std::common_type<std::chrono::duration> 是類模板std::common_type的特化版本。給出兩個duration類的共同類型,即分母是最小公倍數。
- operator+
- operator-
- operator*
- operator/
- operator%
- operator==
- operator!=
- operator<
- operator<=
- operator>
- operator>=
- duration_cast 把時間長度轉換到不同時間嘀嗒單位的另一個時間長度。存在截斷誤差時才需要這種顯式類型轉換。
- floor(std::chrono::duration) C++17 轉換時間長度向下近似
- ceil(std::chrono::duration) C++17 轉換時間長度向上近似
- round(std::chrono::duration) C++17 轉換時間長度向最近偶數近似
- abs(std::chrono::duration) C++17 時間長度的絕對值
- 幫助類Helper classes
- treat_as_floating_point 表示一個時間長度可轉換為不同的時間嘀嗒單位
- duration_values 構造給定類型的repr類型的零值zero、最小值min、最大值max 等3個constexpr函數。例如
auto iiii = std::chrono::duration_values<int>::min; auto jj = iiii();
均定義在std::literals::chrono_literals命名空間中。因此需要先聲明using namespace std::chrono_literals;
- operator""h
- operator""min
- operator""s
- operator""ms
- operator""us
- operator""ns
未定義大於小時的字面常量。
初始化
編輯duration初始化的形式:
- std::chrono::seconds v1{3}; //不允許用圓括號或者賦值號
- std::chrono::seconds v2{4min}; //相當於拷貝賦值
例子
編輯為了區分秒數和普通的數值之間的差異,seconds類在設計的時候就禁止了數值隱式轉換為seconds類
seconds s = 3; // 编译出错
seconds s1{3}; // 这样是可以的
s1 = 4; // 编译出错
std::cout << s << "\n"; // C++20可以,输出为 3s
std::cout << s.count() << "s\n"; // 这样是可以的
seconds s2 = 3s; // C++14支持字面常量
seconds s3 = s2 + s1; // 支持基本的算术操作,但是不支持seconds和一个普通数值进行算术操作
seconds::min(); // 获取秒类型可以表示的范围,
seconds::max();
minutes m1{2};
seconds s2 = m1; // m1隐式转换为seconds
seconds s3 = s2 + m1; // m1隐式转换为seconds,然后运算
//不同时间长度单位的隐式转换必须是向下转换,如minutes转seconds。如果向上转换需用duration_cast显式转换。因为这些转换是截断近似,而不是四舍五入。
seconds s2{1000};
minutes m3 = std::chrono::duration_cast<minutes>(s2);
時間點time_point
編輯表示一個時間點,即一個時刻的值。它存儲為從一個固定時刻(epoch)開始的一段時間的長度(duration)。C++20規定system clock採用的是Unix的時間戳即UTC 1970年1月1日 00:00。時間點的類模板形如:
template <class Clock,
class Duration = typename Clock::duration>
class time_point {
Duration d_;
public:
using clock = Clock;
using duration = Duration;
// ...
};
- 類型定義
- clock 使用的時鐘
- duration 一個std::chrono::duration類型,用於度量從固定時刻開始的時間的長度
- rep 算術類型,表示時間長度的嘀嗒(tick)數
- period 一個std::ratio類型,表示一個時間嘀嗒的單位
- 成員函數
- (constructor)
- time_since_epoch 返回從固定時刻開始的時間長度。
- operator+= 修改時間長度
- operator-= 修改時間長度
- min 靜態成員函數,最小可能的時間長度
- max 靜態成員函數,最大可能的時間長度
- 非成員函數
- std::common_type<std::chrono::time_point> 是類模板std::common_type的特化版本
- operator+ 修改時間長度
- operator- 修改時間長度
- operator== 比較時間點
- operator!= 比較時間點
- operator< 比較時間點
- operator<= 比較時間點
- operator> 比較時間點
- operator>= 比較時間點
- time_point_cast 函數模板,在同一時鐘下把一個時間點轉化為具有不同時鐘滴答長度的另一個時間點。所以第一個模板參數是目標的時鐘抵達長度類型。
- clock_cast C++20引入,可以轉換不同時鐘下的時間點。例如
utc_clock::time_point t = clock_cast<utc_clock>(file_clock::now());
- floor(std::chrono::time_point) C++17 函數模板 轉化時間點並向下近似
- ceil(std::chrono::time_point) C++17 函數模板 轉化時間點並向上近似
- round(std::chrono::time_point) C++17 函數模板 轉化時間點並近似到最近鄰的偶數
不同類型的時間點之間也可以相互轉換,可隱式的向下轉換;如果向上轉換就必須顯式的使用time_point_cast:
using namespace std::chrono;
template <class D> using sys_time = time_point<system_clock, D>;
sys_time<seconds> tp{5s}; // 5s
sys_time<milliseconds> tp2 = tp; // 5000ms 隐式的向下转换
tp = time_point_cast<seconds>(tp2); // 5s 向下转换,需要显示的使用time_point_cast
時鐘clock
編輯C++11預定義3個時鐘,形如:
struct some_clock
{
using duration = chrono::duration<int64_t, microseconds>;
using rep = duration::rep;
using period = duration::period;
using time_point = chrono::time_point<some_clock>;
static constexpr bool is_steady = false;
static time_point now() noexcept;
};
C++20之前,不同時鐘的時間點無法用標準庫的工具相互轉換:
system_clock::time_point tp = system_clock::now();
steady_clock::time_point tp2 = tp; // no viable conversion
C++20引入結構模板clock_time_conversion,可以轉換不同時鐘下的時間點。其()運算符可把一個時間點轉化為目標時鐘下的對應時間點。以system時鐘或者utc時鐘為轉換中介。例如:
auto sd = sys_days{ 2021y / July / 26 };
auto time = clock_time_conversion<utc_clock, system_clock>{}(sd);
std::cout << time << "\n";
Visual C++ 2015把steady_clock都用QueryPerformanceCounter實現;把high_resolution_clock定義為steady_clock的別名。
system_clock
編輯當前系統範圍(即對各進程都一致)的一個實時的日曆時鐘(wall clock)。但是這個值並不一定是單調的,因為作業系統可能會修改時間,導致system_clock返回的時間不單調。
- 成員類型:
- rep 表示時鐘滴答計數的算術類型
- period 表示時鐘滴答長度的std::ratio類型,以秒鐘為單位長度
- duration std::chrono::duration<rep, period> 微軟實現為 duration<long long, ratio<1, 10'000'000>即100奈秒。
- time_point std::chrono::time_point<std::chrono::high_resolution_clock> 。微軟實現epoch為1970-01-01 00:00:00 UTC
- 成員常量:
- constexpr bool is_steady (public static member constant) 表示該時鐘的每個時間嘀嗒單位是否為均勻(即長度相等) 。
- 成員函數
- now 靜態函數 返回時鐘的當前值,類型為std::chrono::time_point
- to_time_t 靜態函數 把系統時鐘的時間點轉化為C風格的std::time_t
- from_time_t 靜態函數 把C風格的std::time_t轉化為系統時鐘的時間點
C++20引入3個預定義的time_point特化類:
- sys_time
- sys_seconds
- sys_days
steady_clock
編輯當前系統實現的一個穩定時鐘。表示的是單調時鐘,隨著物理時間向前,這個時鐘的時間點不會減少,最適合進行間隔的測量。
- 成員類型:
- rep 表示時鐘滴答計數的算術類型
- period 表示時鐘滴答長度的std::ratio類型,以秒鐘為單位長度
- duration std::chrono::duration<rep, period> 微軟實現為duration<long long, nano>
- time_point std::chrono::time_point<std::chrono::high_resolution_clock>
- 成員常量:
- constexpr bool is_steady (public static member constant) 表示該時鐘的每個時間嘀嗒單位是否為均勻(即長度相等) 。該值總為true
- 成員函數
- now 靜態函數 返回時鐘的當前值,類型為std::chrono::time_point
注意,steady_clock的epoch不能轉化為system timepoint;steady_clock的時間點不能輸出輸入給stl的流。實質上這相當於steady_clock是一個計時器而不是時鐘,兩個時間點相減得到的duration是有意義的,不存在時間原點epoch。
high_resolution_clock
編輯當前系統實現的最高解析度的時鐘。微軟實現該時鐘為steady_clock的類型別名。
- 成員類型:
- rep 表示時鐘滴答計數的算術類型
- period 表示時鐘滴答長度的std::ratio類型,以秒鐘為單位長度
- duration std::chrono::duration<rep, period>
- time_point std::chrono::time_point<std::chrono::high_resolution_clock>
- 成員常量:
- constexpr bool is_steady (靜態) 表示該時鐘的每個時間嘀嗒單位是否為均勻(即長度相等)
- 成員函數
- now 靜態函數 返回時鐘的當前值,類型為std::chrono::time_point
utc_clock
編輯此時鐘自1970-01-01 00:00:00 UTC開始計時。此時鐘計算閏秒,是世界各地民用時間的基礎。微軟實現它是system_clock的別名類型
3個靜態成員函數:
- now
- to_sys
- from_sys
非成員函數:
- get_leap_second_info
C++20引入2個預定義的time_point特化類:
- utc_time
- utc_seconds
tai_clock
編輯epoch為1958-01-01 00:00:00.0000000UTC。
duration微軟實現為duration<long long, ratio<1, 10'000'000>,即100奈秒。
3個靜態成員函數:
- from_utc
- now
- to_utc
C++20引入2個預定義的time_point特化類:
- tai_time
- tai_seconds
gps_clock
編輯epoch為1980-01-06 00:00:00.0000000UTC
duration微軟實現為duration<long long, ratio<1, 10'000'000>,即100奈秒。
3個靜態成員函數:
- from_utc
- now
- to_utc
C++20引入2個預定義的time_point特化類:
- gps_time
- gps_seconds
file_clock
編輯為std::filesystem::file_time_type的別名類型。不是steady時鐘,即有閏秒插入。
epoch微軟實現為1601-01-01 00:00:00.0000000UTC
duration微軟實現為duration<long long, ratio<1, 10'000'000>,即100奈秒。
靜態成員函數:
- now
- to_utc
- from_utc
- to_sys
- from_sys
由於C++20要求編譯器實現to_utc/from_utc或to_sys/from_sys中的一套,因此建議使用clock_cast,例如clock_cast<file_clock>(utc_clock::now())
C++20引入2個預定義的time_point特化類:file_time
local_t
編輯C++20引入,表示本地時間的偽時鐘。用於std::chrono::time_point的構造函數的第一個參數,實際上表示還沒有跟具體哪個時鐘相結合的時間點。
以此特化了3個std::chrono::time_point模板實例類:
- template<class Duration> using local_time = std::chrono::time_point<std::chrono::local_t, Duration>;
- using local_seconds = local_time<std::chrono::seconds>;
- using local_days = local_time<std::chrono::days>;
一天之內的時間
編輯C++20用類模板std::chrono::hh_mm_ss把時間滴答長度表示為小時、分鐘、秒鐘、小數秒。主要用于格式化。成員函數有is_negative、hours、minutes、seconds、subseconds等。例如: cout << std::chrono::hh_mm_ss{ chrono::milliseconds{1003} } << '\n';
對於std::chrono::hours,std::chrono包含函數is_am、is_pm、make12、make24用於判斷是否為上午[0h, 11h],否則為下午;轉換為12小時/24小時表示。
日期
編輯C++20在std::chrono中增加:
- last_spec:標記類用於定義一個類實例last,可表示月度最後一天,月度最後一個星期幾。
- day 類,表示月度中的一天,範圍為[1,31],但也可[0, 255].
- month 類,表示月度。範圍為[1,12],但也可[0, 255]。預定義了12個月的英文名字作為類的實例如January值為1。
- year 類,表示年份。範圍為 [-32767, 32767]
- weekday 類,表示星期各天名稱。範圍為[0,6],但也可[0, 255]。預定義了7個星期內各天名字作為類的實例如Sunday值為0 .
- 重載了運算符[],下標如果為整型值i,表示未指定的月度內的第i個星期幾;如果下標值為std::chrono::last_spec,表示未指定月度內的最後一個星期幾。
- weekday_indexed 類,表示月度內的第幾個星期幾
- weekday_last 類,表示月度內的最後一個星期幾
- month_day 類,表示指定月的第幾天
- month_day_last類,表示月度的最後一天
- month_weekday類,表示指定月度的第幾個星期幾
- month_weekday_last類,表示制定月度的最後一個星期幾
- year_month類,表示指定年度的指定月度
- year_month_day類,表示指定的日期
- year_month_day_last類,表示指定年月的最後一天
- year_month_weekday類,表示指定年月的第幾個星期幾。
- year_month_weekday_last類,表示指定年月的最後一個星期幾
- operator/ 運算符重載,為方便創建格里高利曆日期
- 創建完整日期,允許3種順序:
- year/month/day
- month/day/year
- day/month/year.
- 其中允許day替換為:
- std::chrono::last, 月度最後一天
- weekday[i], 月度內第i個星期幾
- weekday[std::chrono::last], 月度內最後一個星期幾
- 純整數可被接手,如果不引起歧義,如2005y/4/5可以,但5/April/2005不行。
- 部分日期類型,如year_month、month_day等可以按上述3種順序,不使用第二個operator/
- 創建完整日期,允許3種順序:
時區
編輯- tzdb: 時區庫
- tzdb_list:tzdb的鍊表
- get_tzdb
- get_tzdb_list
- reload_tzdb
- remote_version
- locate_zone函數:按照名字返回一個time_zone的地址。
- current_zone 函數,返回當前的time_zone對象。其name如為「Asia/Shanghai」
- time_zone類:表示一個時區。const訪問。有成員函數name、get_info等。
- sys_info 底層類,一般不用於編程。
- local_info 類:represents information about a local time to UNIX time conversion
- choose 枚舉類,成員值為earliest,latest
- zoned_traits
- zoned_time 表示一個時區及時間點。也可做同一時間點不同時區的換算。例如:
std::chrono::zoned_time zt{ "Asia/Shanghai",std::chrono::local_days{18d / std::chrono::January / 2022} + 15h + 15min }; std::chrono::zoned_time zt2{ "America/New_York", zt };
- leap_second
- leap_second_info 類:給出一個UTC時間點的閏秒總數。
- get_leap_second_info 函數模板:給出一個UTC時間點返回leap_second_info實例。
- time_zone_link 類:一個時區的可選名字。
- nonexistent_local_time 異常類
- ambiguous_local_time 異常類
chrono I/O
編輯std::chrono::parse函數模板:從輸入流中分析一個chrono對象。
C++20的一個例子
編輯// C++20
#include <chrono>
#include <format>
#include <iostream>
int main()
{
using TwentyMins = std::chrono::duration<int, std::ratio<20*60>>;
std::chrono::time_zone const* myTimeZone = std::chrono::current_zone();
std::chrono::time_point p1 = std::chrono::system_clock::now();
std::chrono::time_point p2 = myTimeZone->to_local(p1);
std::chrono::time_point p3 = std::chrono::floor<TwentyMins>(p2);
std::cout << std::format("{:%Y-%m-%d %H:%M}\n", p3);
}
表示為字符串的示例
編輯#include <iostream>
#include <chrono>
#include <ratio>
#include <time.h>
#include <iomanip>
#include <sstream>
#include <string>
int main()
{
using namespace std::chrono;
auto now = system_clock::now();
time_t t = system_clock::to_time_t(now);
std::tm tm;
localtime_s(&tm, &t);
auto time = std::put_time(&tm, "%b %d %Y %H:%M:%S");
std::cout << time << std::endl;
std::stringstream ss;
ss << time;
std::string a{ ss.str() };
}
例子
編輯時間點的示例
編輯#include <iostream>
#include <iomanip>
#include <ctime>
#include <chrono>
int main()
{
std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
std::time_t now_c = std::chrono::system_clock::to_time_t(now - std::chrono::hours(24));
std::cout << "24 hours ago, the time was "
<< std::put_time(std::localtime(&now_c), "%F %T") << '\n';
std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
std::cout << "Hello World\n";
std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
std::cout << "Printing took "
<< std::chrono::duration_cast<std::chrono::microseconds>(end - start).count()
<< "us.\n";
}
時間長度的示例
編輯#include <iostream>
#include <chrono>
int main()
{
using shakes = std::chrono::duration<int, std::ratio<1, 100000000>>;
using jiffies = std::chrono::duration<int, std::centi>;
using microfortnights = std::chrono::duration<float, std::ratio<12096,10000>>;
using nanocenturies = std::chrono::duration<float, std::ratio<3155,1000>>;
std::chrono::seconds sec(1);
std::cout << "1 second is:\n";
std::cout << std::chrono::duration_cast<shakes>(sec).count()
<< " shakes\n";
std::cout << std::chrono::duration_cast<jiffies>(sec).count()
<< " jiffies\n";
std::cout << microfortnights(sec).count() << " microfortnights\n";
std::cout << nanocenturies(sec).count() << " nanocenturies\n";
}
綜合示例
編輯#include <iostream>
#include <chrono>
#include <ctime>
long fibonacci(unsigned n)
{
if (n < 2) return n;
return fibonacci(n-1) + fibonacci(n-2);
}
int main()
{
std::chrono::time_point<std::chrono::system_clock> start, end;
start = std::chrono::system_clock::now();
std::cout << "f(42) = " << fibonacci(42) << '\n';
end = std::chrono::system_clock::now();
std::chrono::duration<double> elapsed_seconds = end-start;
std::time_t end_time = std::chrono::system_clock::to_time_t(end);
std::cout << "finished computation at " << std::ctime(&end_time)
<< "elapsed time: " << elapsed_seconds.count() << "s\n";
}