1. 程式人生 > >C++標準庫之時間戳、時間段

C++標準庫之時間戳、時間段

以前的時間日期庫只能支援到秒、毫秒。並不能支援微妙納秒,C++11帶來了chrono,來提供高精度的時間日期庫。標頭檔案 < chrono >

chrono這個庫主要目的是為不同的系統提供高精度的時間和時鐘。為了不用每隔10年為一種時間型別重新解讀,這次chrono乾脆整出了兩個新的概念: 
duration:時間段 eg:2分鐘、120秒 
timepoint:時間點,時間點是由兩部分組成:時間段 + 起始時間。eg:2016年新年夜(解釋方式:1970/01/01 + xxx秒。 這是unix和posix系統的計時方式)

名稱空間:std::chrono

一個duration物件包含兩個部分:一個表示ticks的值,一個秒的單位。

std::ratio可以用來表示小數點後面的部分 
ratio的英文翻譯:比率、係數 
用x/y秒來描述時間的單位。eg:60s表示分鐘單位;1/1000s表示毫秒單位等

std::chrono::duration<int> d1(20);
std::chrono::duration<double, std::ratio<60>> d2(0.5);
std::chrono::duration<long, std::ratio<1, 1000>> d3(1);
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

分析一下上面的3行,duration是一個類模版,有兩個模版引數,第一個表示數值ticks,第二個表示單位(可選,預設是秒)。 
先分析一下第二個引數:std::ratio< 60 > 也有兩個引數,第二個引數是可選的,預設為1,第一個引數表示分子,第二個表示分母。結合duration來講,就可以任意指定秒的單位:60s(一分鐘)為單位,0.001s(1毫秒),100s(百秒,這個是自定義的)。正常生活中是年月日 時分秒 毫秒 微妙 納秒 皮秒(c++11可以支援到納秒)。

再看看上面的例子:第一個是20秒,第二個是半分鐘,第三個是1毫秒。 
實際場景中絕大多數都是時分秒 一直到納秒。c++11已經考慮到這些單位的常用性,為我們定義了6個常用的單位: 
std::chrono::hours 
std::chrono::minutes 
std::chrono::seconds 
std::chrono::milliseconds 
std::chrono::microseconds 
std::chrono::nanoseconds 
對應上面3個定義,可以用下面的代替

std::chrono::seconds d1(20);
std::chrono::minutes
d2(0.5); std::chrono::milliseconds(1);
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

下面說一下duration的算術操作: 
兩個durtion的加減乘除 
durtion和ticks的之間的加減 
比較

這些算術操作需要注意他們的單位。

std::chrono::seconds d1(1); // 1std::chrono::milliseconds d2(1); // 1毫秒
  • 1
  • 2
  • 1
  • 2

d1 - d2: 
結果是999 單位是 毫秒 
如果不用標準庫提供的單位:

std::chrono::durtion<int, std::ratio<1, 5>> d1(2); // 2/5std::chrono::durtion<int, std::ratio<1, 3>> d2(1); // 1/3
  • 1
  • 2
  • 1
  • 2

d1 + d2: 
結果是11 單位是1/15秒。15就是3和5的最大公約數

大部分算術操作和比較操作都適用於duration:eg:數值1和duration就沒法進行比較操作。

不同的秒單位都可以進行隱式轉換

duration的預設建構函式,只指定預設單位為秒,但是ticks值是未定的, 
拷貝建構函式可能發生單位的隱式轉換

對於列印duration 標準庫並沒有提供<<操作 
我們可以過載操作符<<來做到:

template <typename V, typename R>
std::ostream& operator << (std::ostream& s, const std::chrono::duration<V, R>& d)
{
  s << d.count() << " of " << R::num << "/" << R::den;

  return s;
}

// 呼叫
std::chrono::milliseconds d2(3);
std::cout << d2 << std::endl;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

需要注意的是:上面這種寫法,如果adl不工作,那麼上面的函式也不會工作 
關於adl,以後再說。

這上面說的單位轉換都是隱式的,但是有一個問題:向高精度轉換是沒問題的,向低精度轉換有可能會丟失資料。這時可以用顯示轉換來避免這種情況:

std::chrono::seconds d1(55); // 55std::chrono::minutes d2(d1); // error
std::chrono::minutes d3 = std::chrono::duration_cast<std::chrono::minutes>(d1); // ok
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

使用durtion_cast來進行顯示的單位轉換。 
下面是一種特殊的情況,來補充上面”向高精度轉換是沒問題的”這點。 
半分鐘(0.5) 轉換成30秒,秒單位是從低精度轉向了高精度(分鐘轉秒),但是ticks的值從double轉成了int,從高精度轉成了低精度,這時也會發生資料丟失,隱式轉換會發生錯誤,需要使用顯示轉換duration_cast。

顯示轉換另一個用的比較多的地方:取一個duration的時分秒,是截斷一部分資訊,常用在列印部分。

std::chrono::milliseconds ms(12345679);
std::chrono::hours hour = duration_cast<std::chrono::hours>(ms % std::chrono::hours(1));
  • 1
  • 2
  • 1
  • 2

利用%(取模操作)可以輕易進行取時取分取秒。

duration還有3個靜態成員函式: 
zeor() : 產生一個0秒的duration物件 
max() min() :產生一個duration物件,裡面的ticks儘可能是最大最小值。

下面說一下由duration + 起始點epoch =的時間點timepoint: 
起始點epoch可以由clock產生,每個clock產生的起始點都有可能不一樣,不過一般都是用1970.0.1.01這個起始點。

clock定義了一個epoch(起點)和一個period(時間段)。 
clock可以計算從1970.01.01到現在的毫秒數,也可以通過now介面產生一個當前時間物件。 
timepoint就是clock +/- 一個duration。

下面先講一下clock: 
c++標準庫定了了3中clock:

system_clock:系統時鐘,提供了to_time_t()from_time_t()介面來將timepoint和c系統時間time_t做轉換。
steady_clock:不變時鐘,並不是使用物理時間的增長來描述的,而是以一個固定的比率來增長。
high_resolution_clock:高精度時鐘。
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

為什麼一個叫系統時鐘一個加不變時鐘? 
系統時鐘就是電腦右下角的時鐘,我們可以任意設定,eg:現在是早上10點,我們可以把系統時鐘設定為下午3點,也可以設定9點。不變時鐘就是針對系統時鐘這種變化性設計出的另一種不可更改的時鐘,意思是我們無法去調整時鐘的值,只能看著她以一個不變的比率,一直增加。

c++新標準並沒有為這三種時鐘提供必須要的精度、起始時間、範圍。 
如果需要一個自定義的起始時間、或是時鐘並不覆蓋的一個時間點,這時,需要用到轉換函式。

namespace
{

template <typename C>
void print_clock()
{
  std::cout << " - precision: ";

  typedef typename C::period P;
  if ( std::ratio_less_equal<P, std::milli>::value )
  {
    typedef typename std::ratio_multiply<P, std::kilo> T;
    std::cout << std::fixed << double(T::num) / T::den << " milliseconds" << std::endl;
  }
  else
    std::cout << std::fixed << double(P::num) / P::den << " seconds" << std::endl;

  std::cout << " - is_steady: " << std::boolalpha << C::is_steady << std::endl << std::endl;
}

}


void test_cpp2()
{
  std::cout << "----------------- test clock----------------" << std::endl;

  std::cout << " system_clock/high_resolution_clock/steady_clock " << std::endl;
  print_clock<std::chrono::system_clock>();
  print_clock<std::chrono::high_resolution_clock>();
  print_clock<std::chrono::steady_clock>();

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

執行結果是:

----------------- test clock----------------
 system_clock/high_resolution_clock/steady_clock
 - precision: 0.000100 milliseconds
 - is_steady: false

 - precision: 0.000001 milliseconds
 - is_steady: true

 - precision: 0.000001 milliseconds
 - is_steady: true
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

上面的程式碼說明 
system_clock 、high_resolution_clock的精度都是100ns,steady_clock的精度是1ms。 
high_resolution_clock、steady_clock的時鐘都不可調整 
測試環境:win10 x64 + vs2015,不同的系統檢測出的可能有所不同(eg:高精度和系統時鐘又可能是一樣的)。

steady_clock主要用在處理兩個時鐘的比較和計算,如果是用system_clock來完成這件事,其中,如果時鐘被改變了,那麼基於system_clock的計算就會出現錯誤,同樣的,如果計算程式執行時間,如果使用system_clock,那麼得出來的結果,也算不了數,甚至執行時間為負。所以說,使用steady_clock來計算兩個時鐘的比較和計算是比較合適的。

上面已經說過了duration和clock,那麼基於duration和clock的timepoint在下面開始分析:

namespace std { namespace chrono {

template <typename Clock,
                  typename Durtion = typename Clock:Durtion>
class time_point;               

}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

說到時間點,有4個比較特殊的: 
epoch:可由任意時鐘的time_point預設構造產生 
current time:由任意時鐘的靜態成員函式now()產生 
minimum timepoint:有任意時鐘的time_point靜態成員函式min()產生 
maximum timepoint:有任意時鐘的time_point靜態成員函式max()產生

下面兩種寫法是一樣的意思:

std::chrono::system_clock::time_point t;
std::chrono::time_point<std::chrono::system_clock> t;
  • 1
  • 2
  • 1
  • 2

time_point 物件只有一個成員 duration,這個值可以被成員函式time_since_epoch()獲取到,同時,time_point物件之間的比較或是和duration之間的算術操作,類time_point都有提供。

只有當time_point 和 durtion組合起來,才能表達出更豐富的時間點,但這其中還需要考慮很多問題:秒單位轉換時的擷取和四捨五入;閏年和閏秒等等。

總結:chrono描述的更多的是chrono和duration而不是日期時間庫。

以上是c++新標準庫對時鐘和時間的描述,c標準和posix的介面,在c++程式中也是可用的,下面介紹一下time.h(在c++可寫成ctime)。 名稱空間是std

CLOCKS_PER_SEC : 表示1s內有多少個ticks,1個嘀嗒(ticks)佔的時間是1/CLOCKS_PER_SEC。

ctime() 將一個time_t轉換成一個日期字串, mktime()將struct tm轉換成time_t 
而c++標準則提供了frome_time_t(),to_time_t() 來將time_point和time_t進行轉換

接下來看看duration和timepoint在定時器和阻塞方面的知識: 
阻塞執行緒:由this_thread提供的sleep_for和sleep_until 
等待互斥量:try_lock_fro和try_lock_unitl 
等待條件變數:wait_for和wait_until 
所有以_for結尾的介面都是利用duration來實現阻塞;所有以_until都是利用timepoint來實現阻塞。

這裡有一個需要注意的地方:以_for結尾的介面用的是duration時間段來處理的,和時鐘無關;而以_until結尾的介面用的是time_point來處理,這裡如果將時鐘修改了,那麼會影響到執行的邏輯。eg:現在是下午3點,sleep_unitl(下午4點),如果此時將時間改為下午4點,那麼定時器會立馬結束。

但計算機的事情並沒有絕對,例如上面說的_for不會收到時鐘調整的影響,但是如果硬體平臺不提供steady_clock,那麼軟體就沒辦法在保證計時不會被影響,所以,在這種情況下,_for介面也會被時鐘調整所影響。