Show Lecture.Chrono as a slide show.
CS253 Chrono
Roman Timekeeping
Time in C++
- overhead: #include <chrono>
- This introduces a lot of new symbols, quarantined to their own
namespace
std::chrono
to avoid conflicts.
- duration: a length of time
- time_point: an instant in time
- November 21, 2024, at 06:47 AM
- the launch of Sputnik
- 2020‒01‒20T12:00:00-0500
- clock: an epoch + units
- A duration is not much more than a number and units:
3ms, 1.25 hours, 100 years
- These are stored in the tempated class duration:
template<typename Rep, class Period>
Rep
is a representation: int, long, double, etc.
Period
is a number of seconds, expressed as a Ratio
type:
Ratio<1,1>
(seconds)
Ratio<1,1000>
(milliseconds)
Ratio<60*60,1>
or Ratio<60*60>
(hours)
duration::count()
returns the count.
- The
Period
is a compile-time construct. This enables
calculations to convert between different periods (hours, ms)
to be mostly done at compile-time, not at run-time.
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
- Several suffixes exist for duration constants:
- number
h
(hours)
- number
min
(minutes)
(as if C++ doesn’t have enough min
already)
- number
s
(seconds)
(as opposed to "
string object "s
)
- number
ms
(milliseconds)
- number
us
(microseconds) (alas, us
, not μs
)
- number
ns
(nanoseconds)
12h
≡ hours(12)
, 123ms
≡ milliseconds(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.
w
is, essentially, a long containing 15.
- It also has compile-time information of the ratio of
weeks to seconds (604800:1).
s
is, essentially, a long containing 9072000.
- It also has compile-time information of the ratio of
seconds to seconds (1:1).
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
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 …
- Add a leap day (February 29) in years divisible by 4.
- However, years divisible by 100 are not leap years
- However, years divisible by 400 are leap years.
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.
- A time_point is a point in time, an epoch:
- creation of the world
- birth of some popular dude
- the founding of the city or nation
- beginning of the semester
- January 1, 1970
- and a duration, which we’ve discussed above.
time_point examples:
- Midterm #2 (start of CSU semester + 10 weeks)
- American Revolution (Common Era start + 1776 years)
Clocks
- C++ defines several clocks, which yield time_point values.
- system_clock
- Think “wall clock”.
- Measure actual time, like 6:47ᴀᴍ,
and historic things like dates & ages.
- May have only crude precision, e.g., seconds.
- Epoch is assumed to be a significant time in the past, e.g., 1970.
- steady_clock
- Think “real-time clock”.
- Measure elapsed time—how long a program runs.
May only be able to deal with a small total amount of time.
- Assumed to have good precision, e.g., nanoseconds.
- Epoch may be recent, e.g., system boot.
Using a clock
- A clock has a
::now()
static method that yields a
time_point representing now.
- A time_point has a
.time_since_epoch()
method that yields a
duration since the epoch, in whatever units the clock naturally
deals with.
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.