CS253: Software Development with C++

Spring 2021

Chrono

Show Lecture.Chrono as a slide show.

CS253 Chrono


Roman Timekeeping

Time in Real Life

Time in Real Life

The common timekeeping system in the United States:

Time in C++

Given that monstrosity, time software is going to be complicated, because timekeeping in real life is complicated. There’s no way around that.

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.

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

Extracting the units

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

auto a = 1h;
auto b = 60min;
if (a == b)
    cout << a.count() << ' ' << b.count();
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 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 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:

milliseconds ms = 2s;
cout << ms.count();
2000

but not if there would be a loss of precision:

seconds s = 1999ms;  // 1s or 2s?
cout << s.count();
c.cc:1: 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):

seconds s = duration_cast<seconds>(4567ms);
cout << s.count();
4

or use floating-point storage:

using floatsecs = duration<double, ratio<1,1>>;
floatsecs s = 5678ms;
cout << s.count();
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:

constexpr long spy = 365.2425 * 24 * 60 * 60; // seconds/year
auto nsmax = nanoseconds::max();
cout << "Max ns: " << nsmax.count() << '\n';
using dyears = duration<double, ratio<spy>>;
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

constexpr long spy = 365.2425 * 24 * 60 * 60;  // seconds/year
using dyears = duration<double, ratio<spy>>;
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.893 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).

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 *
1732256361 seconds since 1970 started.
Thu Nov 21 23:19:21 2024

Age

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

constexpr long spy = 365.2425 * 24 * 60 * 60;  // seconds/year
using dyears = duration<double, ratio<spy>>;
using months = duration<long, ratio<spy,12>>;  // till C++20
using days = duration<long, ratio<24*60*60>>;  // till C++20

auto birth = system_clock::from_time_t(-0x16e06d50); // 🍼👶
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";
24491 days
804 months
67.0554 years

2038‒01‒18T20:14:08

In January 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.

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"
     << "My time_t is " << numeric_limits<time_t>::digits << " bits.\n";
4805.87 days until the y2038 bug!
My time_t is 63 bits.

Timing

Use steady_clock to time code:

auto start = steady_clock::now();                // time_point
for (long i=0; i<1e8; i++);                      // count to 10⁸
auto elapsed = steady_clock::now() - start;
auto ms = duration_cast<milliseconds>(elapsed);  // duration
cout << "That took " << ms.count() << "ms.";
That took 193ms.

steady_clock most likely has much better than millisecond resolution, so we duration_cast() to milliseconds.

Reminder

Don’t forget using namespace std::chrono; in your code!