0% found this document useful (0 votes)
20 views40 pages

Time Programming Fundamentals - Greg Miller - CppCon 2015

Uploaded by

alan88w
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
20 views40 pages

Time Programming Fundamentals - Greg Miller - CppCon 2015

Uploaded by

alan88w
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 40

Time Programming

Fundamentals
Greg Miller ([email protected])

Slides: goo.gl/ofof4N
Outline
● Vocabulary

● Classic time programming

● A simplified mental model

● Final thoughts

● Q&A
Vocabulary
● Civil Time

● UTC
Vocabulary ● Absolute Time

● Time Zone
Civil Time
● 6 fields: Year, Month, Day, Hour, Minute, Second

● "Normal human time"

● Gregorian calendar

● Examples:

○ std::tm

○ 6 separate ints YYYY-MM-DD HH:MM:SS

○ "2015-01-02 03:04:05"
UTC — International Time Standard
● Basis for local civil times worldwide

● No Daylight-Saving Time (DST)


Source Initials Words
● Uses the Gregorian calendar
English CUT Coordinated Universal Time

French TUC Temps Universel Coordonné


● Ticks SI seconds
Compromise UTC Unofficial English: "Universal Time Coordinated";
Unofficial French: "Universel Temps Coordonné"
● Uses leap seconds
https://en.wikipedia.org/wiki/Coordinated_Universal_Time#Etymology
Absolute Time
● Uniquely and universally represents a specific instant in time

● Time zone independent

● Count of $unit since $epoch

● 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

● Rules defined using offsets from UTC

● Rules may change over time

● Rules established by local governments

● Rules may use Daylight-Saving Time (DST)


"PST" "Europe/London"
"America/New_York" "Asia/Tokyo" "Europe/Moscow" "Africa/Monrovia"
"America/Los_Angeles" "Australia/Sydney"
Interesting Time Zone Transitions
● Arizona/Phoenix has no DST Pro Tip
Do not special-case
● Asia/Kathmandu jumped 15 min in 1986 (non-DST) time transitions!

● Australia/Lord_Howe uses a 30 min DST offset


● Pacific/Apia skipped December 30, 2011 (non-DST)
● Africa/Cairo skipped midnight hour in Spring DST transition
● Africa/Monrovia used to use an offset of 44 min. 30 sec.
Diagram of a Time String

"2015-09-22 09:20:00 -07:00"


Civil Time Offset
(Time Zone)

Absolute Time
Classic Time Programming
Classic Time APIs

● std::time_t — non-leap seconds since January 1, 1970 00:00:00 UTC


● std::tm — broken-down year, month, day, hour, minute, second, etc.

UTC Local Time Zone

time_t → tm gmtime() localtime()

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);

// Shift epoch: UTC to "local time_t"


int off = GetOffset(now, "America/New_York");
const std::time_t now_nyc = now + off;
std::tm tm_nyc;
gmtime_r(&now_nyc, &tm_nyc);
std::cout << Format("NYC: %F %T\n", tm_nyc);

// Shift back: "local time_t" to UTC


off = GetOffset(now_nyc, "America/New_York");
const std::time_t now_utc = now_nyc - off;
return now_utc == now ? 0 : 1;
}
Epoch Shifting
int GetOffset(std::time_t t, const std::string& zone);
int main() {
const std::time_t now = std::time(nullptr);

// Shift epoch: UTC to "local time_t"


Problems:
int off = GetOffset(now, "America/New_York");
● now_nyc is not really a time_t
const std::time_t now_nyc = now + off;
● What is the offset? Add/sub?
std::tm tm_nyc;
● std::tm has some invalid fields
gmtime_r(&now_nyc, &tm_nyc);
● Local to UTC doesn't work
std::cout << Format("NYC: %F %T\n", tm_nyc);

// Shift back: "local time_t" to UTC


off = GetOffset(now_nyc, "America/New_York"); Pro Tip
const std::time_t now_utc = now_nyc - off; No such thing as a
return now_utc == now ? 0 : 1; "local time_t"
}
A Simplified Mental Model
Absolute Time Time Zone Civil Time

F(Absolute, TZ) → Civil

F(Civil, TZ) → Absolute


What's missing?

● Time zones are opaque

● Numeric offsets unnecessary

● No "local seconds" (i.e., no epoch shifting)

● No access to future/past transitions

"These concepts fill a


much needed gap."
— Ken Thompson
Classic APIs in this Model

Absolute Time Time Zone Civil Time

std::time_t UTC or localtime std::tm, int


std::chrono::
system_clock::time_point

F(Absolute, TZ) → Civil localtime_r()

F(Civil, TZ) → Absolute mktime()


Announcing: The CCTZ Library NEW

Absolute Time Time Zone Civil Time

cctz::time_point cctz::TimeZone cctz::Breakdown,


int, etc.

F(Absolute, TZ) → Civil cctz::BreakTime()

F(Civil, TZ) → Absolute cctz::MakeTime()


Hello, CCTZ
int main() {
cctz::TimeZone syd;
if (!cctz::LoadTimeZone("Australia/Sydney", &syd)) return -1;

// Neil Armstrong first walks on the moon


const cctz::time_point tp1 = cctz::MakeTime(1969, 7, 21, 12, 56, 0, syd);

const std::string s = cctz::Format("%F %T %z", tp1, syd);


std::cout << s << "\n";

cctz::TimeZone nyc;
cctz::LoadTimeZone("America/New_York", &nyc);

const cctz::time_point tp2 = cctz::MakeTime(1969, 7, 20, 22, 56, 0, nyc);


assert(tp2 == tp1);
}
The CCTZ Library Implementation
● Posix conventions: Ignores leap seconds, Proleptic Gregorian Calendar

● Uses IANA tzdata from the local system (e.g., /usr/share/zoneinfo)

● Normalizes out-of-range fields (e.g., Oct 32 → Nov 1)

● Faster than libc equivalents

● Nice default handling of discontinuities (e.g., DST)


00
:0 Absolute Time
0

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);

// Time Programming Fundamentals @cppcon


const cctz::time_point tp = cctz::MakeTime(2015, 9, 22, 9, 0, 0, lax);

cctz::TimeZone nyc;
LoadTimeZone("America/New_York", &nyc);

std::cout << cctz::Format("Talk starts at %T %z (%Z)\n", tp, lax);


std::cout << cctz::Format("Talk starts at %T %z (%Z)\n", tp, nyc);
}

Talk starts at 09:00:00 -0700 (PDT)


Talk starts at 12:00:00 -0400 (EDT)
Example 2: Parsing
int main() {
const std::string civil_string = "2015-09-22 09:35:00";

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;

const auto now = std::chrono::system_clock::now();


const std::string s = now > tp ? "running long!" : "on time!";
std::cout << "Talk " << s << "\n";
}

Talk on time!
Example 3: Adding Months
int main() {
cctz::TimeZone lax;
LoadTimeZone("America/Los_Angeles", &lax);

const auto now = std::chrono::system_clock::now();


const cctz::Breakdown bd = cctz::BreakTime(now, lax);

// First day of month, 6 months from now.


const cctz::time_point then =
cctz::MakeTime(bd.year, bd.month + 6, 1, 0, 0, 0, lax);

std::cout << cctz::Format("Now: %F %T %z\n", now, lax);


std::cout << cctz::Format("6mo: %F %T %z\n", then, lax);
}

Now: 2015-09-17 09:02:41 -0700


6mo: 2016-03-01 00:00:00 -0800
Example 4: Floor to "Midnight"
cctz::time_point FloorDay(cctz::time_point tp, cctz::TimeZone tz) {
const cctz::Breakdown bd = cctz::BreakTime(tp, tz);
const cctz::TimeInfo ti =
cctz::MakeTimeInfo(bd.year, bd.month, bd.day, 0, 0, 0, tz);
if (ti.kind == cctz::TimeInfo::Kind::SKIPPED) return ti.trans;
return ti.pre;
}

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

Greg Miller ([email protected])


github.com/google/cctz Bradley White ([email protected])

You might also like