C++20 Chrono 的時區功能

之前有介紹過 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_clocktime_point 的話,就會變成一樣了。

不過這邊要注意的,如果第二個參數是 local_time 的話,他就單純是附加時區資訊上去,不會去做額外的計算。


另外,chrono 也有提供閏秒(leap second、維基百科)的資訊,不過這東西個人覺得沒什麼用,而且也確實快被廢掉了,所以這邊就不提了。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *