基本概念

C++11的时间日期库在std::chrono中,包含头文件即可。

在C++11的日期时间库中,共有3个概念,分别是持续时间(Duration)、时间点(timepoint)、时钟(clock)。概念都比较好理解,下面分别对这几个概念做一个比较详细的说明。

Clocks

定义中的时钟主要有3个,分别是:

  1. system_clock,顾名思义,就是系统时钟,他一般是unix时间,即从1970年1月1日到现在的间隔。如果系统时间发生调整,那么调用该方法获取的时间值也会跟着变化。
  2. steady_clock,是单调时间,即每后一次调用都一定会比前一次调用的时间要晚,它并不反映真实的世界时间。在内部的实现中,有可能是系统开启以来的时间。通过该时钟很适合用来获取一段时间内的间隔,它不随系统时间改变而改变。
  3. high_resolution_clock,高精度时间,用来测量细微间隔的一段时间,它的内部实现中可能是system_clock或是steady_clock,也可能是其他独立的时间。

一般来说,我们使用的都是system_clock,如果有用到steady_clock,那么后面再补充。

system_clock的主要方法有3个,分别是:

  1. now,用来获取当前时间。
  2. to_time_t,用来将系统时间转变为std::time_t类型。
  3. from_time_t,用来将std::time_t类型转换为系统时间点。

下面是一些使用示例。

使用system_clock获取当前系统时间。

1
2
3
4
5
6
7
8
9
#include <iostream>
#include <chrono>
int main() {    
    auto time = std::chrono::system_clock::now();
    auto tt = std::chrono::system_clock::to_time_t(time);
    std::cout << std::put_time(std::localtime(&tt), "%Y-%m-%d %H:%M:%S") << std::endl;
    
    return 0;
}

统计函数的执行耗时,这种情况更适合使用steady_clock。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <iostream>
#include <chrono>

long fibonacci(unsigned n)
{
    if (n < 2) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

int main()
{
    auto start = std::chrono::steady_clock::now();
    std::cout << "f(42) = " << fibonacci(42) << '\n';
    auto end = std::chrono::steady_clock::now();
    std::chrono::duration<double> elapsed_seconds = end-start;
    std::cout << "elapsed time: " << elapsed_seconds.count() << "s\n";
}

持续时间(Duration)

持续时间表示一个时间段,比如1s、1min、1day等等,这些时间段是有单位的,因此持续时间也是一个模板变量,用来指定不同的单位。

它的定义如下:

1
2
3
// Rep表示单位的数量单位
// Period表示单位
template<class Rep,  class Period = std::ratio<1>> class duration;

Period用来表示时间的单位,比如秒、天、小时等等,而Rep则表示容纳某个数量时间的单位。比较绕口,举个例子,比如说10天,那么Period是单位day,那么其数量是10,而10的类型可以是int型,也可以short型,Rep就用来表示这到底是什么类型存储这个量,如果类型过小,如果时间太长的话将会导致溢出。

再来看下Period的定义,

1
2
3
// Num,表示分子
// Denom,表示分母
template<std::intmax_t Num, std::intmax_t Denom = 1> class ratio;

举个简单点的例子,Num为1,Denom也为1时,表示是1s,如果Num为1,Denom为1000,那么相当于是1/1000s,也即毫秒。如果Num为60,Denom为1,那么表示 60 / 1s,也即相当于1min。

下面是chrono头文件中的一些常见持续时间定义。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Type	Definition
std::chrono::nanoseconds	duration</*signed integer type of at least 64 bits*/, std::nano>
std::chrono::microseconds	duration</*signed integer type of at least 55 bits*/, std::micro>
std::chrono::milliseconds	duration</*signed integer type of at least 45 bits*/, std::milli>
std::chrono::seconds	duration</*signed integer type of at least 35 bits*/>
std::chrono::minutes	duration</*signed integer type of at least 29 bits*/, std::ratio<60>>
std::chrono::hours	duration</*signed integer type of at least 23 bits*/, std::ratio<3600>>
std::chrono::days (since C++20)	duration</*signed integer type of at least 25 bits*/, std::ratio<86400>>
std::chrono::weeks (since C++20)	duration</*signed integer type of at least 22 bits*/, std::ratio<604800>>
std::chrono::months (since C++20)	duration</*signed integer type of at least 20 bits*/, std::ratio<2629746>>
std::chrono::years (since C++20)	duration</*signed integer type of at least 17 bits*/, std::ratio<31556952>>

针对Duraion来说,一般有两种操作,

  1. 时间的增减。
  2. 不同持续时间单位之间的转换。

Duration重载了+/-运算符,可以直接进行+/-操作,类型之间的转换可以使用duration_cast方法。下面是一些使用实例。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <iostream>
#include <chrono>

int main() {
    std::chrono::milliseconds ms(3);
    printf("%lld\n", ms.count());

    using second = std::chrono::duration<double>;
    auto s = std::chrono::duration_cast<second>(ms);
    printf("%llf\n", s.count());

    std::chrono::seconds sec(10);
    auto du = sec + ms;
    printf("%lld\n", du.count());

    return 0;
}

对应的输出如下:

1
2
3
3
0.003000
10003

时间点(TimePoint)

从时钟拿到的时间是时间点,它代表了时间段上的一个点,比如2019年10月21日10:20:10。

时间点的定义如下。

1
template<class Clock, class Duration = typename Clock::duration> class time_point;

通过time_point的模板参数,我们可以看到,一个是时钟,另外一个是持续时间,实际上timepoint就是一个时钟的基准时间加上一个时间段。

对于时间点来说,下面这几个方法比较重要。

1
2

template <class ToDuration, class Clock, class Duration>time_point<Clock, ToDuration> time_point_cast(                                 const time_point<Clock, Duration> &t);
  1. time_since_epoch,获取以模板时钟的起始时间到现在的这个持续时间。
  2. time_point_cast,用来将时间点转换为基于同一个时钟,但不同类型持续时间的时间点。
  3. 时间点也重载了+/-运算符,用来进行两个时间点之间的相加减。

下面是一些使用示例。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <chrono>

int main() {
    std::chrono::time_point<std::chrono::system_clock> now = std::chrono::system_clock::now();
    std::chrono::minutes m(20);
    std::chrono::time_point<std::chrono::system_clock> then = now + m;
    std::time_t tt = std::chrono::system_clock::to_time_t(then);
    std::cout << "timestamp is : " << now.time_since_epoch().count() << std::endl;
    std::cout << std::put_time(std::localtime(&tt), "%F %T") << std::endl;

    // 降低时间精度,默认system_clock的duration精度是 1/10 micro sec
    auto then_min = std::chrono::time_point_cast<std::chrono::minutes>(then);
    tt = std::chrono::system_clock::to_time_t(then_min);
    std::cout << std::put_time(std::localtime(&tt), "%F %T") << std::endl;

    return 0;
}

运行时时间是:13:30:13,对应输出如下:

1
2
3
timestamp is : 16006662135386558
2020-09-21 13:50:13
2020-09-21 13:50:00

一些应用

时间戳与时间之间互转

获取时间戳。

1
2
3
4
5
6
7
8
std::time_t getTimeStamp()
{
    std::chrono::time_point<std::chrono::system_clock,std::chrono::milliseconds> tp = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::system_clock::now());
    auto tmp=std::chrono::duration_cast<std::chrono::milliseconds>(tp.time_since_epoch());
    std::time_t timestamp = tmp.count();
    //std::time_t timestamp = std::chrono::system_clock::to_time_t(tp);
    return timestamp;
}

将时间戳转为日期。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
std::tm* gettm(int64 timestamp)
{
    int64 milli = timestamp+ (int64)8*60*60*1000;//此处转化为东八区北京时间,如果是其它时区需要按需求修改
    auto mTime = std::chrono::milliseconds(milli);
    auto tp=std::chrono::time_point<std::chrono::system_clock,std::chrono::milliseconds>(mTime);
    auto tt = std::chrono::system_clock::to_time_t(tp);
    std::tm* now = std::gmtime(&tt);
    printf("%4d年%02d月%02d日 %02d:%02d:%02d\n",now->tm_year+1900,now->tm_mon+1,now->tm_mday,now->tm_hour,now->tm_min,now->tm_sec);
   return now;
}

在上面的程序中,时间精度只到秒,这是time_t结构的限制,如果希望获取到ms精度,那么直接使用timestamp % 1000的方式就行。

参考链接

  1. Date and time utilities
  2. c++11中的日期和时间库
  3. How to convert std::chrono::time_point to calendar datetime string with fractional seconds?
  4. C++11获取时间戳和时间戳转日期(毫秒精度)