C + + chrono library for processing date and time

Posted by mlin on Thu, 20 Jan 2022 17:14:00 +0100

C++11 provides a date and time related library, chrono. Through chrono library, you can easily deal with date and time, which provides convenience for program development. The chrono library mainly contains three types of classes: time interval duration, clock, and time point.

Time interval duration

Common class members

Duration refers to a period of time interval, which is used to record the time length. It can represent the time interval of seconds, minutes and hours. The prototype of duration is as follows:

// Defined in header file < Chrono >
template<
    class Rep,
    class Period = std::ratio<1>
> class duration;

Rep: This is a numeric type, indicating the type of clock number (cycle) (integer by default). If rep is a floating-point number, duration can use decimal to describe the number of clock cycles.

Period: indicates the cycle of the clock. Its prototype is as follows:

// Defined in header file < ratio >
template<
    std::intmax_t Num,
    std::intmax_t Denom = 1

> class ratio;

The ratio class represents the seconds of each clock cycle. The first template parameter Num represents the numerator and Denom represents the denominator. The denominator value is 1 by default. Therefore, ratio represents the value of a numerator divided by the denominator. For example, ratio < 2 > represents a clock cycle of 2 seconds, ratio < 60 > represents a minute, ratio < 6060 > represents an hour, and ratio < 6060 * 24 > represents a day. Ratio < 11000 > represents 1 / 1000 second, that is, 1 millisecond, ratio < 11000000 > represents one microsecond, and ratio < 11000000000 > represents one nanosecond.

For ease of use, some common time intervals are defined in the standard library, such as hour, minute, second, millisecond, microsecond and nanosecond. They are all located in the chrono namespace. The definitions are as follows:

typedefinition
Nanosecond:STD:: Chrono:: nanoseconds duration < rep * / * at least 64 bit signed integer type /, STD:: nano >
Microseconds:STD:: Chrono:: microseconds duration < rep * / * at least 55 bit signed integer type /, STD:: Micro >
millisecond:STD:: Chrono:: milliseconds duration < rep * / * at least 45 bit signed integer type /, STD:: milli >
Seconds:STD:: Chrono:: seconds duration < rep * / * at least 35 bit signed integer type / >
minute:STD:: Chrono:: minutes duration < rep * / * at least 29 bit signed integer type /, STD:: ratio < 60 > >
Hours:STD:: Chrono:: hours duration < rep * / * at least 23 bit signed integer type /, STD:: ratio < 3600 > >

Note: each predefined duration type up to hours covers a range of at least ± 292 years.

The constructor prototype of the duration class is as follows:

// 1. Copy constructor
duration( const duration& ) = default;
// 2. Construct the object by specifying the type of clock cycle
template< class Rep2 >
constexpr explicit duration( const Rep2& r );
// 3. Construct the object by specifying the clock cycle type and clock cycle length
template< class Rep2, class Period2 >
constexpr duration( const duration<Rep2,Period2>& d );

In order to facilitate the operation between duration objects, operator overloading is carried out inside the class:

operators overloadingdescribe
operator=Assignment content (public member function)
operator+
operator-Implement unary + and unary - (expose member functions)
operator++
operator++(int)
operator–
operator–(int)Increment or decrement cycle count (public member function)
operator+=
operator-=
operator*=
operator/=
operator%=Realize the composite assignment between two time lengths (public member function)

The duration class also provides a method count() to obtain the number of clock cycles of the time interval. The function prototype is as follows:

constexpr rep count() const;

Use of classes

Constructor

int main()
{
	//Constructor
	//an hour
	chrono::hours h(1);
	//3 ms
	chrono::milliseconds{ 3 };
	//3000 seconds
	chrono::duration<int, ratio<1000>> ks(3);
	//6.6 seconds
	chrono::duration<double> dd(6.6);
	//Is the number of clock cycles expressed in decimals. The clock cycle is 1 / 30 second, and there are 3.5 clock cycles in total. Therefore, hz represents the time interval of 1 / 30 * 3.5 seconds
	chrono::duration<double, std::ratio<1, 30>> hz(3.5);

}

The chrono library encapsulates clock cycles of different lengths according to the duration class (which can also be customized). The total time interval can be obtained by setting the number of cycles based on this clock cycle (clock cycle * number of cycles = total time interval).

int main()
{
	//3 ms
	chrono::milliseconds ms{ 3 };
	//6000 subtle
	chrono::microseconds us = 2 * ms;
	//The interval period is 1 / 30 second
	std::chrono::duration<double, std::ratio<1, 30>> hz(3.54);

	std::cout << "3 ms duration has " << ms.count() << " ticks\n"
		<< "6000 us duration has " << us.count() << " ticks\n"
		<< "3.5 hz duration has " << hz.count() << " ticks\n";

}

Due to operator overloading within the duration class, arithmetic operations can be performed directly between time intervals. For example, if we want to calculate the difference between two time intervals, we can do the following processing in the code:

#include <iostream>
#include <chrono>
using namespace std;

int main()
{
    chrono::minutes t1(10);
    chrono::seconds t2(60);
    //540
    chrono::seconds t3 = t1 - t2;
    cout << t3.count() << " second" << endl;
}

Note: there are certain rules for the addition and subtraction operation of duration. When the clock cycles of two durations are different, they will be unified into one clock first, and then the arithmetic operation will be carried out. The unified rules are as follows: assuming that there are two clock cycles of ratio < x1, y1 > and ratio < x2, y2 >, first find the maximum common divisor X of X1 and x2, and then find the minimum common multiple y of y1 and y2, The clock cycle ratio after unification is ratio < x, Y >.

time point

A time class representing a point in time is provided in the chrono library_ Point, which is defined as follows:

// Defined in header file < Chrono >
template<
    class Clock,
    class Duration = typename Clock::duration
> class time_point;

It is implemented as storing the value of a time interval of Duration type from the beginning of the era of Clock. Through this class, we can finally get a certain point in time.

Clock: this time point is measured on this clock
Duration: std::chrono::duration type used to measure time from the era

time_ The constructor prototype of point class is as follows:

// 1. To construct an object with epoch (i.e. 1970.1.1) as the value, it needs to be used together with the clock class, and the parameterless constructor cannot be used alone
time_point();
// 2. Construct an object to represent a point in time. The duration of d starts from epoch and needs to be used with the clock class. This constructor cannot be used alone
explicit time_point( const duration& d );
// 3. Copy the constructor to construct an object at the same time point as t. you need to specify template parameters when using it
template< class Duration2 >
time_point( const time_point<Clock,Duration2>& t );

In this class, in addition to the constructor, another time is provided_ since_ The epoch() function is used to obtain the time from January 1, 1970 to time_ The time interval (duration) of the time recorded in the point object. The function prototype is as follows:

duration time_since_epoch() const;

In addition, point in time_ Direct arithmetic operation (i.e. addition and subtraction) is also supported between point object and time period object duration. Logical operation can be carried out between time point objects. For details, please refer to the following table:

Where tp and tp2 are time_point is an object of type, and dtn is an object of type duration.

describeoperationReturn value
Compound assignment (member function)operator+= tp += dtn*this
Compound assignment (member function)operator-= tp -= dtn*this
Arithmetic operator (nonmember function)operator+ tp + dtna time_point value
Arithmetic operator (nonmember function)operator+ dtn + tpatime_point value
Arithmetic operator (nonmember function)operator- tp - dtna time_point value
Arithmetic operator (nonmember function)operator- tp - tp2aduration value
Relational operators (nonmember functions)operator== tp == tp2a bool value
Relational operators (nonmember functions)operator!= tp != tp2a bool value
Relational operators (nonmember functions)operator< tp < tp2a bool value
Relational operators (nonmember functions)operator> tp > tp2a bool value
Relational operators (nonmember functions)operator>= tp >= tp2a bool value
Relational operators (nonmember functions)operator<= tp <= tp2a bool value

Since this time point class is often used together with the clock class to be introduced below, we will not give an example here. The use of the time point class will be involved in the sample code of the clock class. So far, we only need to understand the functions provided by the time point class.

clocks

The chrono library provides a clock class for obtaining the current system time. There are three kinds of clocks:

system_clock: the clock of the system. The system clock can be modified, or even the network timing. Therefore, the time difference calculated by using the system time may not be accurate.
steady_clock: a monotonous clock, equivalent to a stopwatch. After starting timing, the time will only increase and cannot be modified. It is suitable for recording the program time
high_resolution_clock: and clock class steady_clock is equivalent (its alias).
There is time inside these clock classes_ Point, duration, Rep, Period and other information, based on which to obtain the current time and implement time_t and time_ Mutual conversion between points.

Clock class member typedescribe
repA signed arithmetic type that represents the number of clock cycles
periodRepresents the std::ratio type of the clock count cycle
durationTime interval, which can represent negative duration
time_pointIndicates the time point recorded in the current clock

When using the clock class provided by chrono, you don't need to create a class object. You can get the desired time by directly calling the static method of the class.

system_clock

Specifically, the clock class is system_clock is a system wide real-time clock. system_clock provides the time of the current time point_ Point to convert the obtained time point into time_t type time object, you can get the current time information based on this time object.

system_ The clock class is defined in the underlying source code as follows:

struct system_clock { // wraps GetSystemTimePreciseAsFileTime/GetSystemTimeAsFileTime
    using rep                       = long long;
    using period                    = ratio<1, 10'000'000>; // 100 nanoseconds
    using duration                  = chrono::duration<rep, period>;
    using time_point                = chrono::time_point<system_clock>;
    static constexpr bool is_steady = false;

    _NODISCARD static time_point now() noexcept 
    { // get current time
        return time_point(duration(_Xtime_get_ticks()));
    }

    _NODISCARD static __time64_t to_time_t(const time_point& _Time) noexcept 
    { // convert to __time64_t
        return duration_cast<seconds>(_Time.time_since_epoch()).count();
    }

    _NODISCARD static time_point from_time_t(__time64_t _Tm) noexcept 
    { // convert from __time64_t
        return time_point{seconds{_Tm}};
    }
};

Through the above source code, we can understand that in the system_ Some details in the clock class:

rep: the number of clock cycles is recorded by shaping. long long
period: a clock cycle is 100 nanoseconds ratio < 1, 10'000'000 >
Duration: the time interval is rep*period nanosecond Chrono:: duration < Rep, period >
time_point: the time point is initialized by the system clock. Chrono:: time_point<system_ Clock >, which records the time point of the new era

You can also see the system_ The clock class provides three static member functions:

// Returns a point in time that represents the current time.
static std::chrono::time_point<std::chrono::system_clock> now() noexcept;
// Will time_ The point time point type is converted to std::time_t type
static std::time_t to_time_t( const time_point& t ) noexcept;
// std::time_t type converted to time_point time point type
static std::chrono::system_clock::time_point from_time_t( std::time_t t ) noexcept;

For example, we need to obtain the current system time and print it in a recognizable way. The example code is as follows:

#include <chrono>
#include <iostream>
using namespace std;
using namespace std::chrono;
int main()
{
    // New era 1970.1.1 time
    system_clock::time_point epoch;

    duration<int, ratio<60*60*24>> day(1);
    // New era 1970.1.1 time + 1 day
    system_clock::time_point ppt(day);

    using dday = duration<int, ratio<60 * 60 * 24>>;
    // New era 1970.1.1 time + 10 days
    time_point<system_clock, dday> t(dday(10));

    // Current system time
    system_clock::time_point today = system_clock::now();
    
    // Convert to time_ Ttime type
    time_t tm = system_clock::to_time_t(today);
    cout << "Today's date is:    " << ctime(&tm);

    time_t tm1 = system_clock::to_time_t(today+day);
    cout << "What's the date tomorrow:    " << ctime(&tm1);

    time_t tm2 = system_clock::to_time_t(epoch);
    cout << "epoch time :      " << ctime(&tm2);

    time_t tm3 = system_clock::to_time_t(ppt);
    cout << "epoch time +1 day:  " << ctime(&tm3);

    time_t tm4 = system_clock::to_time_t(t);
    cout << "epoch time +10 day: " << ctime(&tm4);
}

steady_clock

If we use the clock not to obtain the current system time, but the duration of the program, use syetem at this time_ Clock is not appropriate, because this time can change with the system settings. Clock class steady provided in C++11_ Clock is equivalent to a stopwatch. As long as it is started, it will accumulate time and cannot be modified. It is very suitable for time-consuming statistics.

steady_ The clock class is defined in the underlying source code as follows:

struct steady_clock { // wraps QueryPerformanceCounter
    using rep                       = long long;
    using period                    = nano;
    using duration                  = nanoseconds;
    using time_point                = chrono::time_point<steady_clock>;
    static constexpr bool is_steady = true;

    // get current time
    _NODISCARD static time_point now() noexcept 
    { 
        // doesn't change after system boot
        const long long _Freq = _Query_perf_frequency(); 
        const long long _Ctr  = _Query_perf_counter();
        static_assert(period::num == 1, "This assumes period::num == 1.");
        const long long _Whole = (_Ctr / _Freq) * period::den;
        const long long _Part  = (_Ctr % _Freq) * period::den / _Freq;
        return time_point(duration(_Whole + _Part));
    }
};

Through the above source code, you can learn that in steady_ Some details in the clock class:

rep: the number of clock cycles is recorded by shaping. long long
period: a clock cycle is 1 nanosecond nano
duration: nanoseconds with a time interval of 1 nanosecond
time_point: the time point is initialized by the system clock. Chrono:: time_point<steady_ Clock >, which records the time point of the new era

In addition, a static now() method is also provided in this class to obtain the current time point. The function prototype is as follows:

static std::chrono::time_point<std::chrono::steady_clock> now() noexcept;

Suppose you want to test the execution efficiency of a program, you can calculate the total time consumed during its execution. The example code is as follows:

#include <chrono>
#include <iostream>
using namespace std;
using namespace std::chrono;
int main()
{
    // Get start time point
    steady_clock::time_point start = steady_clock::now();
    // Execute business process
    cout << "print 1000 stars ...." << endl;
    for (int i = 0; i < 1000; ++i)
    {
        cout << "*";
    }
    cout << endl;
    // Get end time point
    steady_clock::time_point last = steady_clock::now();
    // Calculate difference
    auto dt = last - start;
    cout << "Total time: " << dt.count() << "nanosecond" << endl;
}

high_resolution_clock

high_resolution_clock provides better clock accuracy than system_clock should be high, and it cannot be modified. In the underlying source code, this class is actually steady_ Alias of the clock class.

using high_resolution_clock = steady_clock;

So high_ resolution_ How to use clock and steady_clock is the same, so I won't repeat it here.

Conversion function

duration_cast

duration_cast is a template function provided by the chrono library. This function does not belong to the duration class. Through this function, you can modify the clock cycle Period and the type Rep of cycle times in the duration class object. The prototype of this function is as follows:

template <class ToDuration, class Rep, class Period>
  constexpr ToDuration duration_cast (const duration<Rep,Period>& dtn);

When the source period can be exactly divided by the target period (such as hour to minute), the transition between floating-point duration and integer duration can be performed implicitly without duration_cast. In other cases, it needs to be converted through functions.

We can modify the above code to test the execution time of the program, and modify the attributes of the duration object in the code:

#include <iostream>
#include <chrono>
using namespace std;
using namespace std::chrono;

void f()
{
    cout << "print 1000 stars ...." << endl;
    for (int i = 0; i < 1000; ++i)
    {
        cout << "*";
    }
    cout << endl;
}

int main()
{
    auto t1 = steady_clock::now();
    f();
    auto t2 = steady_clock::now();

    // Integer duration: duration required_ cast
    auto int_ms = duration_cast<chrono::milliseconds>(t2 - t1);

    // Decimal duration: duration is not required_ cast
    duration<double, ratio<1, 1000>> fp_ms = t2 - t1;

    cout << "f() took " << fp_ms.count() << " ms, "
        << "or " << int_ms.count() << " whole milliseconds\n";
}

time_point_cast

time_point_cast is also a template function provided by the chrono library. This function does not belong to time_point class. The function is used to convert time points, because the types of clock cycle Period and cycle number Rep in different time point objects may also be different. Generally, implicit type conversion can be performed between them or through the displayed function. The function prototype is as follows:

template <class ToDuration, class Clock, class Duration>
time_point<Clock, ToDuration> time_point_cast(const time_point<Clock, Duration> &t);
#include <chrono>
#include <iostream>
using namespace std;

using Clock = chrono::high_resolution_clock;
using Ms = chrono::milliseconds;
using Sec = chrono::seconds;
template<class Duration>
using TimePoint = chrono::time_point<Clock, Duration>;

void print_ms(const TimePoint<Ms>& time_point)
{
    std::cout << time_point.time_since_epoch().count() << " ms\n";
}

int main()
{
    TimePoint<Sec> time_point_sec(Sec(6));
    // No precision loss, implicit type conversion is possible
    TimePoint<Ms> time_point_ms(time_point_sec);
    print_ms(time_point_ms);    // 6000 ms

    time_point_ms = TimePoint<Ms>(Ms(6789));
    // error, precision will be lost, and implicit type conversion is not allowed
    TimePoint<Sec> sec(time_point_ms);

    // Display type conversion will lose precision. 6789 truncated to 6000
    time_point_sec = std::chrono::time_point_cast<Sec>(time_point_ms);
    print_ms(time_point_sec); // 6000 ms
}

Note: for the conversion of time point, if there is no loss of precision, you can directly carry out implicit type conversion. If precision will be lost, you can only use display type conversion, that is, call time_point_cast function to complete the operation.

Topics: C++