Skip to main content

Time and Calendar

The Turbo time library contains abstractions for holding time values in terms of absolute time and civil time. The time library consists of the following components:

  • time.h: Contains abstractions for absolute time (and durations), along with helper functions for constructing, parsing, and converting these types. Additionally, this header file includes support for time zones, allowing you to map between absolute time and civil time.
  • civil_time.h: Contains abstractions for civil time, as well as helper functions for constructing, parsing, and converting these types.
  • clock.h: Contains utility functions for creating time objects using the system clock.

These abstractions and utility functions are documented below.

Fundamental Concepts

There are two ways to represent time: absolute time and civil time. Absolute time uniquely and universally represents a specific instant in time. Every event occurs at a specific absolute time, and everyone in the world agrees on the absolute time at which the event occurred. time_t is a well-known absolute time type. Think of the moment Neil Armstrong first set his left foot on the moon. He did this only once. An hour later, he did not do it again for viewers in a time zone to the west. Everyone in the world agrees on the absolute time at which that event occurred.

On the other hand, not everyone agrees on the "civil time" of this giant leap. Civil time is represented as "six separate fields" denoting year, month, day, hour, minute, and second (aka YMDHMS). These six fields represent time as defined by some local government. Your civil time matches the value shown on the clock hanging on Dilbert's wall and the calendar on your desk. A friend of yours living across the country may see a different civil time displayed on their distant calendar and clock at the same moment. For example, if you live in New York, you witnessed Neil Armstrong's small step at 10:56 PM on July 20, 1969, while your friend in San Francisco saw the same thing at 7:56 PM, and your pen pal in Sydney saw the same thing at 12:56 PM on July 21, 1969, during lunch. You all agree on the absolute time of the event but disagree on the civil time.

A time zone is a geopolitical region within which the rules for converting between absolute time and civil time are shared. The geographic nature of time zones is evident in their identifiers, such as America/New_York, America/Los_Angeles, and Australia/Sydney. Time zone rules include, for example, the offset of the region relative to the UTC time standard, daylight saving time adjustments, and short abbreviation strings. Because these rules can change at the whim of local governments in the region, time zones have historically had different rules that apply only to specific periods. Time zones are highly complex, which is why you should always let the time library perform time zone calculations for you.

A time zone defines the relationship between absolute time and civil time. Given an absolute time or civil time and a time zone, you can calculate the other, as shown in the example below and Figure 1.

Civil Time = F(Absolute Time, Time Zone)
Absolute Time = F'(Civil Time, Time Zone)

The concepts described so far—absolute time, civil time, and time zones—are universal concepts that apply equally to all programming languages because they describe time in the real world. Different programming languages and libraries may model these concepts differently using different classes and sometimes even different names, but these fundamental concepts and relationships remain.

turbo::Time Construction

The Turbo time library includes several core classes that map to the concepts described above:

  • Absolute time is represented by the turbo::Time class. (See time.h.) This is a small, integer-like type that should be passed by value and computed using arithmetic operators and descriptively named functions. turbo::Time can be converted to and from other absolute time representations using conversion functions, and to and from civil time representations with the help of turbo::TimeZone.
  • Civil time is represented by six separate integers specifying the year, month, day, hour, minute, and second of turbo::Civil* objects, which should be passed by value. (See civil_time.h.) These integer values can be specified as parameters to civil time constructors (e.g., turbo::CivilYear or turbo::CivilSecond), or parsed from formatted time strings.
  • Time zones are represented by the turbo::TimeZone class. (See time.h.) This mostly opaque value type is passed by value to other Turbo time functions that then perform the necessary conversions to and from absolute or civil time. The Turbo time library itself performs all time zone arithmetic on your behalf, virtually eliminating offset calculation errors in your code.

An important auxiliary concept not yet discussed is the fixed-length time span, represented by the turbo::Duration class. This small, integer-like type should be passed by value and computed using ordinary integer-like arithmetic operators. The Turbo time library includes several functions for converting to and from other duration types and performing floating-point arithmetic. Durations are unit-safe, making them ideal for interfaces that accept timeouts or any other fixed-length time span.

Figure 2 illustrates the main Turbo Time types and shows how they map to the fundamental time concepts described above.

![Turbo Time Classes](images/turbo-time-classes.png) Figure 2

Absolute Time with turbo::Time

turbo::Time represents a specific moment as a count of clock ticks at a certain granularity (resolution) from some starting point (epoch). turbo::Time should be passed by value, not by const reference. Arithmetic operators are provided to express time calculations naturally.

info

Note: Although the resolution of turbo::Time is guaranteed to be at least as low as nanoseconds, it may actually be finer. Despite this fact, the time resolution and epoch are implementation details that you should not rely on.

It works across most available time scales (i.e., precision of at least one nanosecond, range of +/- 100 billion years). Conversions between time scales are performed by truncating (towards negative infinity) to the nearest representable point.

turbo::Time assumes that a minute has 60 seconds, which means the underlying time scale must be "smeared" to account for leap seconds. See https://developers.google.com/time/smear.

Constructing turbo::Time

An instance of turbo::Time can be constructed directly:

// Construct an turbo::Time from the system clock. (See clock.h)
turbo::Time t1 = turbo::Time::current_time();

// Default construction produces an absolute time of the UNIX epoch
turbo::Time t2 = turbo::Time();

// For clarity, prefer constructing such times directly
turbo::Time t3 = turbo::Time::unix_epoch();

turbo::Time values are typically created from other value types. The Turbo time library includes numerous turbo::Time::from_*() factory functions that accept other time representations.

// Construct an turbo::Time from a time_t. time() returns the number of seconds
// since the UNIX epoch.
time_t tt = time(NULL);
turbo::Time t1 = turbo::Time::from_time_t(tt);

// Construct an turbo::Time from a std::chrono time
auto tp = std::chrono::system_clock::from_time_t(123);
turbo::Time t2 = turbo::Time::from_chrono(tp);

// Construct an turbo::Time using a conversion function. (Assume MyCustomTime()
// returns microseconds since the UNIX epoch.)
int64_t unix_micros = MyCustomTime();
turbo::Time t3 = turbo::Time::from_unix_micros(unix_micros);

// Construct an turbo::Time from a civil time and time zone
// (See below for more information on these types)
turbo::TimeZone nyc;
// TimeZone::load may fail so it's always better to check for success.
if (!turbo::TimeZone::load("America/New_York", &nyc)) {
// handle error case
}
// My flight leaves NYC on Jan 2, 2017 at 03:04:05
turbo::CivilSecond ct(2017, 1, 2, 3, 4, 5);
turbo::Time takeoff = turbo::Time::from_civil(ct, nyc);

turbo::Time can obtain time in the future or past. The Turbo time library includes numerous turbo::Time::future_*() factory functions that accept other time representations.

    static constexpr Time future_infinite();

static Time future_time(Duration d);

static int64_t future_nanoseconds(int64_t ns);

static int64_t future_microseconds(int64_t us);

static int64_t future_milliseconds(int64_t ms);

static int64_t future_seconds(int64_t s);

static constexpr Time past_infinite();

static Time past_time(Duration d);

static int64_t past_nanoseconds(int64_t ns);

static int64_t past_microseconds(int64_t us);

static int64_t past_milliseconds(int64_t ms);

static int64_t past_seconds(int64_t s);

Get the current nano second, micro second via turbo::Time:

        static Time current_time();

static int64_t current_nanoseconds();

static T current_microseconds();

static T current_milliseconds();

static T current_seconds();

Formatting turbo::Time

Formatting and parsing functions are provided for conversion to and from strings. FormatTime() allows you to take an absolute time and a time zone and return a string representing that time. (See Time Zones below.)

// Construct an turbo::Time from the system clock.
turbo::Time t1 = turbo::Time::current_time();

// When formatting a time, a time zone should be passed.
turbo::TimeZone utc = turbo::UTCTimeZone();
std::cout << turbo::Time::format_time(t1, utc);
// Outputs, e.g. "2018-08-06T23:35:32.637472794+00:00"

// The stream operator for turbo::Time uses turbo::Time::format_time(), using the
// turbo::TimeZone::local(). Prefer calling turbo::Time::format_time() directly using an
// explicit time zone, as done above.
std::cout << t1 << "\n";
// Outputs e.g. "2018-08-06T17:35:32.637472794-04:00"

Time Durations

turbo::Duration represents a signed, fixed-length time span. A Duration is generated using unit-specific factory functions or as the result of subtracting one turbo::Time from another turbo::Time. Durations behave like unit-safe integers and support all natural integer-like arithmetic operations. Arithmetic overflows and saturates at +/- infinity. Duration should be passed by value, not by const reference.

The factory functions Duration::nanoseconds(), Duration::microseconds(), Duration::milliseconds(), Duration::seconds(), Duration::minutes(), Duration::hours(), and Duration::infinite_duration() allow the creation of constexpr Duration values.

constexpr turbo::Duration ten_ns = turbo::Duration::nanoseconds(10);
constexpr turbo::Duration min = turbo::Duration::minutes(1);
constexpr turbo::Duration hour = turbo::Duration::hours(1);
turbo::Duration dur = 60 * min; // dur == hour
turbo::Duration half_sec = turbo::Duration::milliseconds(500);
turbo::Duration quarter_sec = 0.25 * turbo::Duration::seconds(1);

Duration values can be easily converted to integer units using the division operator.

constexpr turbo::Duration dur = turbo::Duration::milliseconds(1500);
int64_t ns = dur / turbo::Duration::nanoseconds(1); // ns == 1500000000
int64_t ms = dur / turbo::Duration::milliseconds(1); // ms == 1500
int64_t sec = dur / turbo::Duration::seconds(1); // sec == 1 (subseconds truncated)
int64_t min = dur / turbo::Duration::minutes(1); // min == 0

Additionally, the Turbo time library provides helper functions for converting duration values to int64_t or double values:

  • Duration::to_int64_nanoseconds() and Duration::to_double_nanoseconds()
  • Duration::to_int64_microseconds() and Duration::to_double_microseconds()
  • Duration::to_int64_milliseconds() and Duration::to_double_milliseconds()
  • Duration::to_int64_seconds() and Duration::to_double_seconds()
  • Duration::to_int64_minutes() and Duration::to_double_minutes()
  • Duration::to_int64_hours() and Duration::to_double_hours()
turbo::Duration d = turbo::Duration::milliseconds(1500);
int64_t isec = turbo::Duration::to_int64_seconds(d); // isec == 1

turbo::Duration d = turbo::Duration::milliseconds(1500);
double dsec = turbo::Duration::to_double_seconds(d); // dsec == 1.5

Civil Time

The term "civil time" refers to legally recognized human-scale time, represented by six fields: "YYYY-MM-DD hh:mm:ss". A "date" is perhaps the most common example of civil time. Modern civil time follows the Gregorian calendar and is a time zone-independent concept: for example, the civil time of "2016-06-01 12:00:00" is independent of time zones. In other words, civil time does not map to a unique point in time; instead, civil time must be mapped to absolute time via a time zone.

Since civil time is what most people think of as "time", absolute time is often mapped to civil time for presentation to users. The Turbo time library allows you to construct such civil times from absolute time; see time.h for such functions.

The library provides six classes for constructing civil time objects, along with several helper functions for rounding, iterating over, and performing arithmetic operations on civil time objects, while avoiding complexities such as Daylight Saving Time (DST):

  • turbo::CivilSecond
  • turbo::CivilMinute
  • turbo::CivilHour
  • turbo::CivilDay
  • turbo::CivilMonth
  • turbo::CivilYear

Prefer passing these turbo::Civil* types by value rather than by const reference.

Each of these civil time types is a simple value type with the same construction interface and the same six accessors for each civil time field (year, month, day, hour, minute, and second, aka YMDHMS). The classes differ only in alignment, which is indicated by the type name and specifies the field for arithmetic operations.

Civil Time Construction

Each civil time type can be constructed in two ways: by passing up to six integers representing the YMDHMS fields directly to the constructor, or by copying the YMDHMS fields from a civil time type with different alignment. Omitted fields are assigned their minimum valid values. Hours, minutes, and seconds are set to 0, and months and days are set to 1. Since there is no minimum year, it defaults to 1970.

turbo::CivilDay default_value;               // 1970-01-01 00:00:00

// Construct a civil-time object for a specific day
turbo::CivilDay a(2015, 2, 3); // 2015-02-03 00:00:00
turbo::CivilDay b(2015, 2, 3, 4, 5, 6); // 2015-02-03 00:00:00
turbo::CivilDay c(2015); // 2015-01-01 00:00:00

// Construct a civil-time object for a specific second
turbo::CivilSecond ss(2015, 2, 3, 4, 5, 6); // 2015-02-03 04:05:06
turbo::CivilMinute mm(ss); // 2015-02-03 04:05:00
turbo::CivilHour hh(mm); // 2015-02-03 04:00:00
turbo::CivilDay d(hh); // 2015-02-03 00:00:00
turbo::CivilMonth m(d); // 2015-02-01 00:00:00
turbo::CivilYear y(m); // 2015-01-01 00:00:00

m = turbo::CivilMonth(y); // 2015-01-01 00:00:00
d = turbo::CivilDay(m); // 2015-01-01 00:00:00
hh = turbo::CivilHour(d); // 2015-01-01 00:00:00
mm = turbo::CivilMinute(hh); // 2015-01-01 00:00:00
ss = turbo::CivilSecond(mm); // 2015-01-01 00:00:00
Note

Out-of-range fields are normalized (e.g., October 32 -> November 1) so that all civil time objects represent valid values. See Normalization below for details.

Civil Time Alignment

Each civil time class is aligned to the civil time field indicated in the class name after normalization. Alignment is performed by setting all subordinate fields to their minimum valid values (as described above). The following example illustrates how each of the six types aligns fields representing 12:34:56 PM on November 22, 2015. (Note: The string format used here is not important; it is simply a shorthand for showing the six YMDHMS fields.)

turbo::CivilSecond   : 2015-11-22 12:34:56
turbo::CivilMinute : 2015-11-22 12:34:00
turbo::CivilHour : 2015-11-22 12:00:00
turbo::CivilDay : 2015-11-22 00:00:00
turbo::CivilMonth : 2015-11-01 00:00:00
turbo::CivilYear : 2015-01-01 00:00:00

Each civil time type performs arithmetic operations on the civil time field indicated by its alignment. This means that adding 1 to a turbo::CivilDay increments the day field (normalizing as needed), while subtracting 7 from a turbo::CivilMonth operates on the month field (normalizing as needed). All arithmetic produces valid civil times. The difference operator takes two civil time objects with similar alignment and returns a scalar answer in the units of the object's alignment. For example, the difference between two turbo::CivilHour objects will give the answer in civil hours.

Civil Time Conversions

The alignment of a civil object cannot be changed, but the object can be used to construct a new object with different alignment. This is known as "realignment". When realigning to a type with the same or higher precision (e.g., turbo::CivilDay -> turbo::CivilSecond), the conversion can be performed implicitly because no information is lost. However, if information can be discarded (e.g., turbo::CivilSecond -> turbo::CivilDay), the conversion must be performed explicitly at the call site.

void UseDay(turbo::CivilDay day);

turbo::CivilSecond cs;
UseDay(cs); // Won't compile because data may be discarded
UseDay(turbo::CivilDay(cs)); // OK: explicit conversion

turbo::CivilDay cd;
UseDay(cd); // OK: no conversion needed

turbo::CivilMonth cm;
UseDay(cm); // OK: implicit conversion to turbo::CivilDay

Civil Time Normalization

Normalization takes invalid values and adjusts them to produce valid values. In the civil time library, integer arguments passed to Civil* constructors may be out of range, in which case they are normalized by carrying overflows to coarser-grained fields to generate a valid civil time object. This normalization allows natural arithmetic on constructor arguments without worrying about field ranges.

// Out-of-range; normalized to 2016-11-01
turbo::CivilDay d(2016, 10, 32);

// Out-of-range, negative: normalized to 2016-10-30T23
turbo::CivilHour h1(2016, 10, 31, -1);

// Normalization is cumulative: normalized to 2016-10-30T23
turbo::CivilHour h2(2016, 10, 32, -25);
info

If normalization is undesired, you can signal an error by comparing the constructor arguments to the normalized values returned by the YMDHMS properties.

Civil Time Comparisons

Comparisons between civil time objects consider all six YMDHMS fields, regardless of the type's alignment. Comparisons between civil time types with different alignments are allowed.

turbo::CivilDay feb_3(2015, 2, 3);  // 2015-02-03 00:00:00
turbo::CivilDay mar_4(2015, 3, 4); // 2015-03-04 00:00:00

// feb_3 < mar_4
// turbo::CivilYear(feb_3) == turbo::CivilYear(mar_4)

turbo::CivilSecond feb_3_noon(2015, 2, 3, 12, 0, 0); // 2015-02-03 12:00:00

// feb_3 < feb_3_noon
// feb_3 == turbo::CivilDay(feb_3_noon)

// Iterates all the days of February 2015.
for (turbo::CivilDay d(2015, 2, 1); d < turbo::CivilMonth(2015, 3); ++d) {
// ...
}

Civil Time Arithmetic

Civil time types support natural arithmetic operators such as addition, subtraction, and difference. Arithmetic operations are performed on the civil time field indicated in the type name. The difference operator requires arguments with the same alignment and returns an answer in the units of the alignment.

turbo::CivilDay a(2015, 2, 3);
++a; // 2015-02-04 00:00:00
--a; // 2015-02-03 00:00:00
turbo::CivilDay b = a + 1; // 2015-02-04 00:00:00
turbo::CivilDay c = 1 + b; // 2015-02-05 00:00:00
int n = c - a; // n = 2 (civil days)
int m = c - turbo::CivilMonth(c); // Won't compile: different types.

Civil Time Accessors

Each civil time type has accessors for all six civil time fields: year, month, day, hour, minute, and second.

  • year() returning a civil_year_t value
  • month() returning an int
  • day() returning an int
  • hour() returning an int
  • minute() returning an int
  • second() returning an int

Recall that fields below the type's alignment are set to their minimum valid values.

turbo::CivilDay d(2015, 6, 28);
// d.year() == 2015
// d.month() == 6
// d.day() == 28
// d.hour() == 0
// d.minute() == 0
// d.second() == 0

Time Zones

turbo::TimeZone is an opaque, small value type class representing a geopolitical region within which specific rules are used to convert between absolute time and civil time. turbo::TimeZone values are named using TZ identifiers from the IANA Time Zone Database, such as "America/Los_Angeles" or "Australia/Sydney", and can be loaded from the turbo::TimeZone::load() factory function.

warning

Note: Strings such as "PST" and "EDT" are not valid TZ identifiers.

turbo::TimeZone lax;

// Because TimeZone::load() may fail, it is always safer to load time zones
// checking its return value:
if (!turbo::TimeZone::load("America/Los_Angeles", &lax)) {
// Handle failure
}

Prefer passing turbo::TimeZone by value rather than by const reference.

The Turbo time library also includes several convenience functions for constructing time zones:

  • turbo::UTCTimeZone()
  • turbo::FixedTimeZone() constructs a time zone as an offset from UTC (pro tip: avoid this function if possible. It is intended for legacy APIs).
  • turbo::LocalTimeZone() constructs a time zone from the "local" time zone (defaulting to UTC if unknown). Prefer using explicit time zone names.
turbo::TimeZone utc = turbo::TimeZone::utc();

// Constructing a time zone from a fixed UTC-offset may be necessary when
// working with legacy APIs that aren't really timezone-aware. Modern code
// should avoid this as much as possible.
turbo::TimeZone pst = turbo::FixedTimeZone(-8 * 60 * 60);

// Similarly, using a local time zone, rather than explicitly loading a
// particular time zone, should be done with caution, because you don't know
// which time zone will actually be returned.
turbo::TimeZone loc = turbo::LocalTimeZone();

For more background on time zones, see the IANA Time Zone Database and the Wikipedia article on time zone definitions.

Converting Between Absolute and Civil Times

turbo::TimeZone is used to convert between turbo::Time values and Turbo civil time values. In both cases, you use the turbo::TimeZone::At() member function. Helper functions also exist for converting between absolute and civil times.

See Converting Absolute Time to Civil Time and Converting Civil Time to Absolute Time for using turbo::TimeZone::at() to convert between these time values.

Converting Absolute Time to Civil Time

The Turbo time library includes a set of functions for converting turbo::Time and turbo::TimeZone to civil time:

  • ToCivilSecond()
  • ToCivilMinute()
  • ToCivilHour()
  • ToCivilDay()
  • ToCivilMonth()
  • ToCivilYear()
turbo::Time t = ...;
turbo::TimeZone tz = ...;
const turbo::CivilDay cd = turbo::ToCivilDay(t, tz);

If you need more than just the civil time, TimeZone::at(Time) returns additional information about the conversion. If the input time is infinite, the output civil second will be set to CivilSecond::max() or CivilSecond::min() as needed, and the subsecond will be infinite.

// Load the Los Angeles TimeZone
turbo::TimeZone lax;
if (!turbo::TimeZone::load("America/Los_Angeles", &lax)) {
// handle error case
}

// Determine the civil-time within the Los Angeles time zone for the UNIX Epoch.
const turbo::TimeZone::CivilInfo epoch_info = lax.at(turbo::Time::unix_epoch());
// epoch_info.cs == 1969-12-31 16:00:00
// epoch_info.subsecond == turbo::ZeroDuration()
// epoch_info.offset == -28800
// epoch_info.is_dst == false
// epoch_info.abbr == "PST"

Converting Civil Time to Absolute Time

Converting civil time to absolute time requires more care because some civil times may not map to a real or unique absolute time. For example, Daylight Saving Time (DST) transitions skip or repeat civil times; in the United States, 02:15 on March 13, 2011 is a civil time that never occurred, while 01:15 on November 6, 2011 is a civil time that occurred twice. Requests for such times are not well-defined.

The Turbo time library includes a FromCivil() function that can convert any aligned civil time to absolute time. This function returns a well-defined absolute time but makes certain assumptions about the conversion:

  • If the civil time maps to a unique time, that time is returned.
  • If the civil time is repeated in the given time zone, the earlier time is returned.
  • If the civil time is skipped in the given time zone, the transition time is returned.

This means that for any two civil times ct1 and ct2 where ct1 < ct2, FromCivil(ct1) <= FromCivil(ct2). (The same occurs when two non-existent civil times map to the same transition time.)

turbo::CivilSecond cs = ...;
turbo::TimeZone tz = ...;
turbo::Time t = turbo::FromCivil(cs, tz);

If FromCivil() does not return the desired answer for non-unique inputs, TimeZone::at(CivilSecond) returns a turbo::TimeZone::TimeInfo structure containing multiple time values and more complete information about the conversion:

  • TimeInfo.pre holds the absolute time using the time zone's pre-transition UTC offset.
  • TimeInfo.post holds the absolute time using the time zone's post-transition UTC offset.
  • TimeInfo.trans holds the absolute time of the transition discontinuity itself.
  • TimeInfo.kind, an enum indicating the context of the conversion, as one of the following values:
    • UNIQUE means the requested civil time occurred without incident during conversion.
    • REPEATED means the requested civil time occurred twice. In this case, both the pre and post fields will convert to the requested civil time.
    • SKIPPED means the requested civil time did not occur at all. In this case, neither the pre nor post fields will convert to the requested civil time. Instead, these two times are calculated using the UTC offsets around the transition. These are the most likely alternatives for non-existent civil times.

Example:

// NOTE: the examples below denote resulting absolute times (`pre`, `post`, and
// `trans` values) using the convention of a civil time + utc offset.

// Midnight on January 1 is a unique civil time in the Los Angeles time zone.
const auto jan01 = lax.At(turbo::CivilSecond(2011, 1, 1, 0, 0, 0));
// jan01.kind == TimeZone::TimeInfo::UNIQUE
// jan01.pre is 2011-01-01 00:00:00 -0800
// jan01.trans is 2011-01-01 00:00:00 -0800
// jan01.post is 2011-01-01 00:00:00 -0800

// 2:15 AM on March 13, 2011 is a time which was skipped in the Los Angeles
// time zone during the DST transition when time would "spring forward."
const auto mar13 = lax.At(turbo::CivilSecond(2011, 3, 13, 2, 15, 0));
// mar13.kind == TimeZone::TimeInfo::SKIPPED
// mar13.pre is 2011-03-13 03:15:00 -0700
// mar13.trans is 2011-03-13 03:00:00 -0700
// mar13.post is 2011-03-13 01:15:00 -0800

// 1:15 AM on November 6, 2011 is a time that was repeated in the Los Angeles
// time zone during the DST transition when time would "fall back."
const auto nov06 = lax.At(turbo::CivilSecond(2011, 11, 6, 1, 15, 0));
// nov06.kind == TimeZone::TimeInfo::REPEATED
// nov06.pre is 2011-11-06 01:15:00 -0700
// nov06.trans is 2011-11-06 01:00:00 -0800
// nov06.post is 2011-11-06 01:15:00 -0800

Proper Time Hygiene

  • Use Turbo time library types everywhere; including in your interfaces!
  • Convert to/from other types at system boundaries—use only Turbo Time types within the system.
  • Never use time zone offsets in calculations. Let the Turbo time library do this!
  • Prefer explicit time zone names. Do not assume "local time".
  • Make your code's time zone requirements explicit.
  • Make your code time zone-agnostic if possible. If this is not possible, UTC is preferred.
  • Include the offset (%z) in time strings. Prefer RFC3339/ISO8601 formats (e.g., turbo::RFC3339_full).
  • Use turbo::Time::format_time to format time strings and turbo::Time::parse_time to parse time strings.