CS253: Software Development with C++

Fall 2020

Chrono

Show Lecture.Chrono as a slide show.

CS253 Chrono


Roman Timekeeping

Time in C++

duration

Predefined duration types

The following types are predefined. They might not actually be long, but they’re an integer type that is big enough.

    using hours        = duration<long, ratio<60*60,1>>;
    using minutes      = duration<long, ratio<60,1>>;
    using seconds      = duration<long, ratio<1,1>>;
    using milliseconds = duration<long, ratio<1,1000>>;
    using microseconds = duration<long, ratio<1,1'000'000>>;
    using nanoseconds  = duration<long, ratio<1,1'000'000'000>>;

C++20 adds days, weeks, months, and years. Perhaps their variable nature (DST, leap years) caused a delay in their standardization.

duration constants

12hhours(12), 123msmilliseconds(123), etc.

using namespace std::chrono;
if (0.5h - 15min == 900s && seconds(5) == 5000000us)
    cout << "Hooray!\n";
Hooray!

Extracting the units

Even though 1h == 60min, they are represented differently:

using namespace std::chrono;
auto a = 1h;
auto b = 60min;
if (a == b)
    cout << a.count() << ' ' << b.count() << '\n';
1 60

This works because duration comparison via == is smart. It doesn’t just naïvely compare the .count() values—it scales them according to their ratios. Probably some sort of Least Common Multiple business.

We’re comfortable with 3 == 3.0, even though they’re represented differently. This is just more of the same.

Fun with durations

// How long is a semester, in seconds?
using namespace std::chrono;
using weeks = duration<long, ratio<7*24*60*60>>; // until C++20
weeks w(15);
seconds s = w;
cout << w.count() << " weeks is " << s.count() << " seconds.\n";
15 weeks is 9072000 seconds.

Define your own units

“In the future, everyone will be world-famous for 15 minutes.”
Andy Warhol

using namespace std::chrono;
using warhol = duration<float, ratio<15*60>>;
warhol teaching(75min);
cout << "Famous for " << teaching.count() << " Warhols.\n";
Famous for 5 Warhols.

Duration conversion

Durations can be assigned, sometimes:

using namespace std::chrono;
milliseconds ms = 2s;
cout << ms.count() << '\n';
2000

but not if there would be a loss of precision:

using namespace std::chrono;
seconds s = 1999ms; // 1s or 2s?
cout << s.count() << '\n';
c.cc:2: error: conversion from 'duration<[...],ratio<[...],1000>>' to 
   non-scalar type 'duration<[...],ratio<[...],1>>' requested

duration_cast()

To force a loss-of-precision assignment, use duration_cast(), which truncates (discards the fractional part):

using namespace std::chrono;
seconds s = duration_cast<seconds>(4567ms);
cout << s.count() << '\n';
4

or use floating-point storage:

using namespace std::chrono;
using floatsecs = duration<double, ratio<1,1>>;
floatsecs s = 5678ms;
cout << s.count() << '\n';
5.678

If the storage type of a duration is a floating-point type, the type system figures that you’ve got it covered.

Dates are tricky!

How many days in a year? 365, of course! Except …

So, an average year is 365 + 1 ⁄ 4 − 1 ⁄ 100 + 1 ⁄ 400 = 365.2425 days, or 365.2425 × 24 × 60 × 60 = 31556952 seconds.

Even this value is not exact. Why? Because the time it takes the Earth to go around the Sun is not a even multiple of the time it takes the Earth to turn once. Nice solar system, divine creator(s)!

Good enough?

Let’s see how big a quantity of nanoseconds we can represent, and translate that to years:

using namespace std::chrono;
auto nsmax = nanoseconds::max();
cout << "Max ns: " << nsmax.count() << '\n';
using dyears = duration<double, ratio<31556952>>;
dyears y = nsmax;
cout << "Max ns in years: " << y.count() << '\n';
Max ns: 9223372036854775807
Max ns in years: 292.277

Good enough.

time_point

time_point examples:

Clocks

Using a clock

using namespace std::chrono;
using dyears = duration<double, ratio<31556952>>;
auto now = system_clock::now();     // a time_point
dyears y = now.time_since_epoch();  // a duration
cout << y.count() << " years since the epoch.\n";
54.8911 years since the epoch.

Using a clock

system_clock has a static method ::to_time_t(), which converts a time_point to seconds since the Linux time_t epoch (generally the start of 1970).

using namespace std::chrono;
auto now = system_clock::now();                 // time_point
time_t t = system_clock::to_time_t(now);        // time_t
cout << t << " seconds since 1970 started.\n";
cout << ctime(&t);                              // char *
1732196838 seconds since 1970 started.
Thu Nov 21 06:47:18 2024

Age

How old is your instructor? Even that can be represented!

using namespace std::chrono;
using dyears = duration<double, ratio<31556952>>;   // till C++20
using months = duration<long, ratio<31556952/12>>;  // till C++20
using days = duration<long, ratio<24*60*60>>;       // till C++20

auto birth = system_clock::from_time_t(-383'806'800);   // 🍼 👶
auto age = system_clock::now() - birth;

cout << duration_cast<days>(age).count()   << " days\n"
     << duration_cast<months>(age).count() << " months\n"
     << dyears(age).count() << " years\n";
24490 days
804 months
67.0535 years

2038‒01‒18T20:14:08

On January 18, 2038, Linux time (seconds since the start of 1970) will reach 231, or 8000 000016. This will cause problems with systems that store time_t as a 32-bit signed integer.

using namespace std::chrono;
typedef duration<double,ratio<60*60*24>> ddays;  // until C++20
auto tp = system_clock::from_time_t(0x7fffffff); // 2038‒01‒18
ddays ndays = tp - system_clock::now();
cout << ndays.count() << " days until the y2038 bug!\n";
cout << "Our time_t is " << numeric_limits<time_t>::digits << " bits.\n";
4806.56 days until the y2038 bug!
Our time_t is 63 bits.

Timing

Use steady_clock to time code:

using namespace std::chrono;
auto start = steady_clock::now();                  // time_point
for (long i=0; i<1e8; i++);                        // count to 10⁸
auto stop = steady_clock::now();                   // time_point
auto ms = duration_cast<milliseconds>(stop-start); // duration
cout << "That took " << ms.count() << "ms.\n";
That took 182ms.

steady_clock certainly has at least microsecond resolution, so we must duration_cast() to milliseconds.