转换绝对时间和相对时间——std::chrono

C++11之前,想要获取时间并对其打印是有些困难的,因为C++并没有标准时间库。想要对时间进行统计就需要调用C库,并且我们要考虑这样的调用是否能很好的封装到我们的类中。

C++11之后,STL提供了chrono库,其让对时间的操作更加简单。

本节,我们将会使用本地时间,并对本地时间进行打印,还会给时间加上不同的偏移,这些操作很容易使用std::chrono完成。

How to do it...

我们将会对当前时间进行保存,并对其进行打印。另外,我们的程序还会为已经保存的时间点添加不同的偏移,并且打印偏移之后的时间:

  1. 包含必要的头文件,并声明所使用的命名空间:

    #include <iostream>
    #include <iomanip>
    #include <chrono>
    
    using namespace std;
  2. 我们将打印绝对时间点。使用chrono::time_point模板类型来获取,因此需要对输出流操作符进行重载。对时间点的打印方式有很多,我们将会使用%c来表示标准时间格式。当然,可以只打印时间、日期或是我们需要的信息。调用put_time之前对不同类型的变量进行转换的方式看起来有些笨拙,不过这里只这么做一次:

    ostream& operator<<(ostream &os,
        const chrono::time_point<chrono::system_clock> &t)
    {
        const auto tt (chrono::system_clock::to_time_t(t));
        const auto loct (std::localtime(&tt));
        return os << put_time(loct, "%c");
    }
  3. STL已经定义了secondsminuteshours等时间类型,所以我们只需要为其添加days类型就好。这很简单,只需要对chrono::duration模板类型进行特化,将hours类型乘以24,就表示一天具有24个小时:

    using days = chrono::duration<
        chrono::hours::rep,
        ratio_multiply<chrono::hours::period, ratio<24>>>;
  4. 为了用很有优雅的方式表示很多天,我们定义属于days类型的字面值操作符。现在我们程序中写3_days就代表着3天:

    constexpr days operator ""_days(unsigned long long d)
    {
        return days{d};
    }
  5. 实际程序中,我们将会对时间点进行记录,然后就会对这个时间点进行打印。因为已经对操作符进行了重载,所以完成这样的事情就变得很简单:

    int main()
    {
        auto now (chrono::system_clock::now());
    
        cout << "The current date and time is " << now << '\n';
  6. 我们使用now函数来获得当前的时间点,并可以为这个时间添加一个偏移,然后再对其进行打印。为当前的时间添加12个小时,其表示的为12个小时之后的时间:

        chrono::hours chrono_12h {12};
    
        cout << "In 12 hours, it will be "
            << (now + chrono_12h)<< '\n';
  7. 这里将使用chrono_literals命名空间中的函数,声明使用这个命名空间会解锁小时、秒等等时间类型的间隔字面值类型。这样我们就能很优雅的对12个小时15分之前的时间或7天之前的时间进行打印:

        using namespace chrono_literals;
    
        cout << "12 hours and 15 minutes ago, it was "
            << (now - 12h - 15min) << '\n'
            << "1 week ago, it was "
            << (now - 7_days) << '\n';
    }
  8. 编译并运行程序,我们将会获得如下的输出。因为使用%c格式对时间进行打印,所以得到还不错的时间输出格式。通过对不同格式的的字符串进行操作,我们可以获得想要的格式。要注意的是,这里的时间格式并不是以12小时AM/PM方式表示,因为程序运行在欧洲操作系统上,所以使用24小时表示的方式:

    $ ./relative_absolute_times
    The current date and time is Fri May 5 13:20:38 2017
    In 12 hours, it will be Sat May6 01:20:38 2017
    12 hours and 15 minutes ago, it was Fri May5 01:05:38 2017
    1 week ago, it was Fri Apr 28 13:20:38 2017

How it works...

我们可以通过std::chrono::system_clock来获取当前时间点。这个STL时钟类是唯一一个能将时间点的值转换成一个时间结构体的类型,其能将时间点以能够看懂的方式进行输出。

为了打印这样的时间点,我们可以对operator<<操作符进行重载:

ostream& operator<<(ostream &os,
                   const chrono::time_point<chrono::system_clock> &t)
{
    const auto tt (chrono::system_clock::to_time_t(t));
    const auto loct (std::localtime(&tt));
    return os << put_time(loct, "%c");
}

首先,将chrono::time_point<chrono::system_clock>转换为std::time_t。然后,使用std::localtime将这个时间值进行转换,这样就能获取到一个本地时钟的相对时间值。这个函数会给我们返回一个转换后的指针(对于这个指针背后的内存不用我们多操心,因为其是一个静态对象,并不是从堆上分配的内存),这样我们就能完成最终的打印。

std::put_time函数接受一个流对象和一个时间格式字符串。%c表示标准时间格式字符串,例如Sun Mar 12 11:33:40 2017。我们也可以写成%m/%d/%y;之后,时间就会按照这个格式进行打印,比如03/12/17。时间格式符的描述很长,想要了解其具体描述的最好方式就是去查看C++参考手册。

除了打印,我们也会为我们的时间点添加偏移。这也很简单,比如:12小时15分钟就可以表示为12h+15minchrono_literals命名空间为我们提供了字面类型:hours(h), minutes(min), seconds(s), milliseconds(ms), microseconds(us), nanoseconds(ns)

通过对两个时间间隔的相加,我们会得到一个新的时间点。要实现这样的操作就需要对左加operator+和左减operator-操作符进行重载,这样对时间偏移的操作就会变得非常简单。

Last updated