Time Programming Fundamentals - Greg Miller - CppCon 2015
Time Programming Fundamentals - Greg Miller - CppCon 2015
Fundamentals
Greg Miller ([email protected])
Slides: goo.gl/ofof4N
Outline
● Vocabulary
● Final thoughts
● Q&A
Vocabulary
● Civil Time
● UTC
Vocabulary ● Absolute Time
● Time Zone
Civil Time
● 6 fields: Year, Month, Day, Hour, Minute, Second
● Gregorian calendar
● Examples:
○ std::tm
○ "2015-01-02 03:04:05"
UTC — International Time Standard
● Basis for local civil times worldwide
● Examples:
○ std::time_t
○ std::chrono::system_clock::time_point
○ int
Absolute Time Civil Time
Time Zone
● Rules for converting between absolute times and civil times
Absolute Time
Classic Time Programming
Classic Time APIs
tm → time_t X mktime()
Classic Example
std::string Format(const std::string& fmt, const std::tm& tm);
int main() {
const std::time_t now = std::time(nullptr);
std::tm tm_utc;
gmtime_r(&now, &tm_utc);
std::cout << Format("UTC: %F %T\n", tm_utc);
std::tm tm_local;
localtime_r(&now, &tm_local);
std::cout << Format("Local: %F %T\n", tm_local);
}
Epoch Shifting
int GetOffset(std::time_t t, const std::string& zone);
int main() {
const std::time_t now = std::time(nullptr);
cctz::TimeZone nyc;
cctz::LoadTimeZone("America/New_York", &nyc);
01
:0
0
02
:0
0
03
02:30
:0
0
SKIPPED
04
:0
03:30
0
05
:0
0
00
:0 Absolute Time
0
01
"Daylight-Saving Time" Transitions
:0
0
02
01:30
:0
0
REPEATED
03
:0
0
04
:0
0
05
:0
0
cctz::MakeTime F(Civil, TZ) → Absolute
struct TimeInfo {
enum class Kind {
UNIQUE, // the civil time was singular (pre == trans == post)
SKIPPED, // the civil time did not exist
REPEATED, // the civil time was ambiguous
} kind; Pro Tip
time_point pre; // Uses pre-transition offset
time_point trans; Use cctz::MakeTime()
time_point post; // Uses post-transition offset
bool normalized;
};
TimeInfo MakeTimeInfo(int64_t y, int m, int d, int hh, int mm, int ss,
const TimeZone& tz);
time_point MakeTime(int64_t y, int m, int d, int hh, int mm, int ss,
const TimeZone& tz);
cctz::BreakTime F(Absolute, TZ) → Civil
struct Breakdown {
int64_t year; // year (e.g., 2013)
int month; // month of year [1:12]
int day; // day of month [1:31]
int hour; // hour of day [0:23]
int minute; // minute of hour [0:59] Pro Tip
int second; // second of minute [0:59] Probably ignore:
duration subsecond; // [0s:1s) offset, is_dst, abbr
int weekday; // 1==Mon, ..., 7=Sun
int yearday; // day of year [1:366]
int offset; // seconds east of UTC
bool is_dst; // is offset non-standard?
std::string abbr; // time-zone abbreviation (e.g., "PST")
};
Breakdown BreakTime(const time_point& tp, const TimeZone& tz);
1. Translating Civil Time
2. Parsing
Examples 3. Adding Months
4. Calculate "Midnight"
5. Refactor: Epoch Shift
Example 1: Translating Civil Time
int main() {
cctz::TimeZone lax;
LoadTimeZone("America/Los_Angeles", &lax);
cctz::TimeZone nyc;
LoadTimeZone("America/New_York", &nyc);
cctz::TimeZone lax;
LoadTimeZone("America/Los_Angeles", &lax);
cctz::time_point tp;
const bool ok = cctz::Parse("%Y-%m-%d %H:%M:%S", civil_string, lax, &tp);
if (!ok) return -1;
Talk on time!
Example 3: Adding Months
int main() {
cctz::TimeZone lax;
LoadTimeZone("America/Los_Angeles", &lax);
int main() {
cctz::TimeZone lax;
LoadTimeZone("America/Los_Angeles", &lax);
const auto now = std::chrono::system_clock::now();
const auto day = FloorDay(now, lax);
std::cout << cctz::Format("Now: %F %T %z\n", now, lax);
std::cout << cctz::Format("Day: %F %T %z\n", day, lax);
}
Now: 2015-09-17 09:12:53 -0700
Day: 2015-09-17 00:00:00 -0700
Refactor: Old Code
void GetLocalTime(std::time_t t, const std::string& zone,
int* hour, int* min, int* sec) {
int off;
CalcOffset(zone, timet, &off);
std::time_t adjusted_time = t + off;
std::tm gm_tm;
...
::gmtime_r(&adjusted_time, &gm_tm);
*hour = gm_tm.tm_hour;
*min = gm_tm.tm_min;
*sec = gm_tm.tm_sec;
}
Refactor: Old Code (Epoch Shifting)
void GetLocalTime(std::time_t t, const std::string& zone,
int* hour, int* min, int* sec) {
int off;
CalcOffset(zone, t, &off);
std::time_t adjusted_time = t + off;
std::tm gm_tm;
::gmtime_r(&adjusted_time, &gm_tm);
*hour = gm_tm.tm_hour;
*min = gm_tm.tm_min;
*sec = gm_tm.tm_sec;
}
Refactor: NEW Code
void GetLocalTime(std::time_t t, const std::string& zone,
int* hour, int* min, int* sec) {
cctz::TimeZone tz;
cctz::LoadTimeZone(zone, &tz);
const cctz::time_point tp = std::chrono::system_clock::from_time_t(t);
const cctz::Breakdown bd = cctz::BreakTime(tp, tz);
*hour = bd.hour;
*min = bd.minute;
*sec = bd.second;
}
Refactor: NEW Code — BETTER
void GetLocalTime(cctz::time_point tp, cctz::TimeZone tz,
int* hour, int* min, int* sec) {
const cctz::Breakdown bd = cctz::BreakTime(tp, tz);
*hour = bd.hour;
*min = bd.minute;
*sec = bd.second;
}
Refactor: NEW Code — BEST
void GetLocalTime(cctz::time_point tp, cctz::TimeZone tz,
int* hour, int* min, int* sec) {
const cctz::Breakdown bd = cctz::BreakTime(tp, tz);
*hour = bd.hour;
*min = bd.minute;
*sec = bd.second;
} Pro Tip
Directly call:
cctz::BreakTime(tp, tz)
Final Thoughts
Pro Tips
● Use the mental model Absolute
Time Zone Civil Time
Time
● Use proper vocabulary
● Use "%z" in format strings — produces an absolute time
● Never compute with UTC offsets (no time_t math, no epoch shifting, etc)
● Compute cctz::Breakdown rather than pass it
● Do calendar-like calculations in the civil time domain
● Terminology: UTC rather than GMT
CCTZ Available Today
github.com/google/cctz
Q&A