之前有介紹過 std::chrono 在 C++20 新增的日曆功能了,接下來這篇,則是來講一下時區(time zone)的部分。
首先,chrono 提供了 std::chrono::tzdb 這個結構,作為時區資料庫;他基本上是系統內提供的,需要透過 chrono 提供的函式取得,不允許使用者自行建立。
下面就是一個簡單的範例:
#include <chrono> #include <iostream> int main() { std::cout << "TZDB List:\n"; std::chrono::tzdb_list& listDB = std::chrono::get_tzdb_list(); for (auto& rDB : listDB) std::cout << " - " << rDB.version << "\n"; std::cout << "\nDefault TZDB\n"; const std::chrono::tzdb& db = std::chrono::get_tzdb(); std::cout << " Version: " << db.version << ", with " << db.zones.size() << " zones, " << db.links.size() << " zone links\n"; std::cout << " Links:\n"; for (auto& link : db.links) std::cout << " - " << link.name() << ":" << link.target() << "\n"; std::cout << " Zones:\n"; for (auto& zone : db.zones) std::cout << " - " << zone.name() << "\n"; }
這邊首先是透過 std::chrono::get_tzdb_list() 來取得 chrono 能取得的時區資料庫的列表,在這邊就只有一個;在設計上,他會在使用者第一次執行這個函式的時候去建立這個列表,之後都會用同一份。(參考文件)
而實際上一般要使用,應該沒必要去取得列表、而是透過 std::chrono::get_tzdb() 來取得預設的時區資料庫來用。
在 tzdb 這個結構(文件)裡面,紀錄了版本、可以用的時區(zones)以及時區的替代名稱(alternative name、比較像是縮寫、links)。
下面就是在 MSVC 執行的結果:
TZDB List: - 2021a.27 Default TZDB Version: 2021a.27, with 477 zones, 155 zone links Links: - ACT:Australia/Darwin - AET:Australia/Sydney - AGT:America/Buenos_Aires ... Zones: - Africa/Abidjan - Africa/Accra - Africa/Addis_Ababa ...
這邊只有一個時區列表,它提供了 447 個時區、155 個 zone link。
在裡面如果去找台北的話,則可以看到 zone link 是「ROC:Asia/Taipei」,也就是說他會把 ROC 對應到 Asia/Taipei 這個時區。
不過實際上一般要取得系統的時區的話,可以直接透過 std::chrono::current_zone() 這個函式(文件)來取得,它等同去去呼叫 std::chrono::get_tzdb().current_zone()。
在取得 time_zone 的物件後,就可以透過他提供的 to_local() 這個函式,來把時間轉換成當地的時區了~下面就是一個例子:
// std::chrono::system_clock::time_point auto tpSys = std::chrono::system_clock::now(); std::cout << "System time: " << tpSys << "\n"; // std::chrono::local_time<std::chrono::system_clock::duration> auto tpLoc = std::chrono::current_zone()->to_local(tpSys); std::cout << "Local time: " << tpLoc << "\n";
它的結果會是:
System time: 2022-11-18 03:50:57.7499958 Local time: 2022-11-18 11:50:57.7499958
也就是說,直接透過 std::chrono::system_clock::now() 來取得當下的時間的話,其實他會是 UTC+0、而不是系統的時區時間!
老實說,Heresy 之前都還不知道這點呢…不過由於之前也都只是在計算時間差,所以沒有影響;但是如果要和別的來源的時間做比較的話,這就會變成問題了。
比較麻煩的,是他這邊取得的時間型別又是另一種型別(嚴格來說是 template 參數不一樣),沒辦法直接拿來和不同的時間點做處理(計算、轉換)…
此外,其實 chrono 也還有另外提供一個 zoned_time 的類別(文件),裡面會去記錄時區的資訊;下面就是簡單的例子:
std::chrono::zoned_time tpSysZone(tpSys); std::cout << "System zone time: " << tpSysZone << "\n"; std::chrono::zoned_time tpZone(std::chrono::current_zone(), tpSys); std::cout << "Zone time: " << tpZone << "\n";
他輸出的結果大概會是下面的形式:
System zone time: 2022-11-21 05:53:25.1089381 UTC Zone time: 2022-11-21 13:53:25.1089381 GMT+8
和前面的結果相比,最大的差別就是在輸出的時候會在後面加上時區的資訊了!
而透過相關的 get 函式,也可以取得對應的 time_zone、或是系統時間與區域時間。
像是如果是要取得時差的數值的話,則可以透過下面的形式來取得:
tpZone.get_info().offset
他的型別是 std::chrono::seconds,此外也可透過 save 取得日光節約時間。
如果是要轉換到特定的時區的話,則可以寫成:
auto tpJST = std::chrono::zoned_time("JST", tpSys); std::cout << "JST time: " << tpJST << "\n"; auto tpTW = std::chrono::zoned_time("Asia/Taipei", tpJST); std::cout << "TW time: " << tpTW << "\n";
這邊的「JST」字串可以參考前面的資料庫的輸出結果,也可以寫成「Asia/Tokyo」;其結果會是:
JST time: 2022-11-21 16:59:09.5816398 GMT+9 TW time: 2022-11-21 15:59:09.5816398 GMT+8
而如果要進行計算、比較的話,這邊比較合適的作法,大概都是轉換成系統時鐘會比較合適了~下面就是一個例子:
if (tpJST != tpTW) std::cout << "Not the same\n"; if(tpJST.get_sys_time() == tpTW.get_sys_time()) std::cout << "The same\n";
如果直接拿 zoned_time 來比較的話,會是不一樣的;但是轉成 system_clock 的 time_point 的話,就會變成一樣了。
不過這邊要注意的,如果第二個參數是 local_time 的話,他就單純是附加時區資訊上去,不會去做額外的計算。
另外,chrono 也有提供閏秒(leap second、維基百科)的資訊,不過這東西個人覺得沒什麼用,而且也確實快被廢掉了,所以這邊就不提了。