Show Lecture.⏰ as a slide show.
CS253 ⏰
Roman Timekeeping
Time in C++
- overhead:
#include <chrono>
using namespace std::chrono;
- This introduces a lot of new symbols, so they’re quarantined
to their own namespace suburb to avoid conflicts.
- duration
- a length of time: 75 minutes, 3.456μs, etc.
- time_point
- an instant in time: 2024-11-22T05:44:37,
the assassination of Archduke Ferdinand,
the launch of Sputnik, etc.
- clock
duration
- A duration is not much more than a number and units.
- These are stored in the tempated class
duration
:
template<class 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 integral type that is big enough.
using hours = template<long, ratio<60*60,1>>;
using minutes = template<long, ratio<60,1>>;
using seconds = template<long, ratio<1,1>>;
using milliseconds = template<long, ratio<1,1000>>;
using microseconds = template<long, ratio<1,1000000>>;
using nanoseconds = template<long, ratio<1,1000000000>>;
C++20 adds days
, weeks
, months
, and years
.
duration constants
- Several suffixes exist for duration constants:
- number
h
(hours)
- number
min
(minutes)
- number
s
(seconds)
- number
ms
(milliseconds)
- number
us
(microseconds)
- 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.
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 concerning the ratio of
weeks to seconds (604800:1).
s
is, essentially, a long
containing 9072000.
- It also has compile-time information concerning 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
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 …
- 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, a 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
- 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:
system_clock
- Epoch: start of 1970
- Units: seconds
steady_clock
- Epoch: system boot
- Units: nanoseconds
- The epochs and units are implementation-defined, not always as listed above.
- Use
system_clock
to measure actual time, like 5:44ᴀᴍ.
- Use
steady_clock
to measure elapsed time—how long a program runs.
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.8938 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 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 *
1732279477 seconds since 1970 started.
Fri Nov 22 05:44:37 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"
<< duration_cast<dyears>(age).count() << " years\n";
24491 days
804 months
67.0561 years
2038‒01‒18T20:14:08
On January 18, 2038, Linux time (seconds since the
start of 1970) will reach 231, or 8000000016. 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";
4805.6 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 183ms.
steady_clock
certainly has at least microsecond resolution,
so we must duration_cast
to milliseconds.