Proposal to Add Date-Time to the C++ Standard Library 0.75

Jeff Garland (jeff-at-crystalclearsoftware.com)


Table of Contents

Motivation and Scope

The representation of dates and times is a recurring problem for software developers. Almost all non-trivial programs have the need to represent dates or times for one or more purposes. This proposal describes additions and changes to the C++ standard library to facilitate programming with dates and times. Most elements of the proposal are currently implemented in the Boost Date-Time Library.

Target Audience

The primary audience for this proposal is C++ application and library developers. The proposal provides a foundational library to simplify application development that needs to manipulate dates and times. In addition, by providing efficient value types, like date, the library provides an excellent foundation for building higher level interfaces. For example, a socket library that provides a timeout value can use the milliseconds type to provide a cleaner interface than is possible with a primitive type.

Due to the pervasive need for date-time programming, many libraries have been written to support date-time programming. Unfortunately, the date-time domain appears simple and trivial, but is deceptively complex. Thus, many libraries fail to address a broad range of applications by failing to solve some of the difficult date-time problems, or, providing inflexible and inefficient implementations. For example, very few libraries support flexible localized input-output capabilities. The lack of more complete standard library support means the re-invention of various date-time capabilities and a lack of portable capabilities to support C++ programmers. The Boost Date-Time Library and this proposal are both attempts to remedy this condition by building on other standards (such as ISO 8601 and Posix 1003.1) and building a best of breed solution to support a broad range of applications.

Applications and General Functionality

A wide variety of modern software applications need to manipulate dates and times. Like numeric values, the span of application types that use dates and times include business, scientific, communications, and many others. The following are some fairly typical uses of dates and times in applications:

  • recording business transaction dates
  • presenting calendars and schedules
  • calculation of elapsed times
  • logging time of an event
  • creating a schedule or plan for one or more activities
  • calculate times in multiple time zones
  • finding the date of an event (eg: third Thursday in March)

Consider an Estimate class that records an estimate for services rendered. To design this class at least two temporal values need to be recorded:

  • the day the estimate is made, and
  • the number of days the estimate is valid

One core function of the Estimate class is to determine if the estimate is still valid. The following is a sketch of how this logic might be implemented using the Boost Date-Time Library:


//Example usage of a date temporal types
class Estimate {
 //...
 bool is_valid() const
 {
    date_period valid_period(date_of_estimate, valid_days);  
    date today = day_clock::local_time(); //read the computer clock
    return valid_period.contains(today);
 }
 date last_valid_day() const
 {
    return (date_of_estimate + valid_days);
 }
private:
  std::string estimate_id;
  date        date_of_estimate; 
  days        valid_days;  //number of days estimate is valid
  //...
};

In this small example, date, days, and date_period demonstrate the three core temporal types: time points, durations, and time periods. These are described in more detail in the concepts section.

Main Capabilities

Overall, the main capabilities needed for a standard date-time library include:

Temporal Types

Just as programmers are provided with int and double for development of programs involving basic mathematics, temporal types provide concise representation and calculation with dates and times. Clearly defined temporal types enhance coding practice by facilitating the creation of more precise functional interfaces. As with other value types, programmers expect temporal types to support basic value concepts such as assignability, comparability, and streamability.

The temporal type concepts are described in Points, Durations, and Periods. The list of temporal types provided in the proposal can be found in Summary of Temporal Types.

Calculation with Dates and Times

Like int and double the temporal types provide the framework for time-based arithmetic. The following are some examples:


date d(2004,Jan,1);
d += year(1);
d += months(3);
d += days(10);

date_time t(d, hours(5));
t+= minutes(3) - seconds(2);

milliseconds ms_count = hours(3) + milliseconds(100);

The design of these features is described in more detail in Arithmetic Operations on Temporal Types.

Measurement of Times From Clocks

Computer applications frequently need to determine the current time or date. To determine the time, applications read a hardware device that provides a representation of the current time. Usually, this representation is a counter that represents a duration offset from a well defined epoch time. For example, std::gmtime is a clock interface that retrieves the number of seconds since 1970-Jan-1 00:00:00 (the epoch) from the local computer clock. The gmtime call provides the time based on the standard Universal Coordinated Time (UTC). Note that many of the clock APIs also embed the concept of local time adjustment. This typically depends on the time zone settings of the computer.

Reading a clock results in the construction of a time point. For example:


//construct UTC time based 
date_time t1 = clock::universal_time();

//construct localized time based on time zone setting of computer
date d = day_clock::local_time();
date_time t2 = second_clock::local_time();
date_time t3 = microsecond_clock::local_time();


//construct localized time based on time zone specification
posix_time_zone tz("EST-05EDT,M4.1.0,M10.5.0");
date_time t4 = second_clock::local_time(tz);

Input and Output

People have invented a seemingly infinite set of combinations for representing dates and times, making good input output support quite challenging. The ISO 8601 standard provides a specification for formatting of dates and times. However, most applications have requirements that extend beyond ISO 8601 including the need for localized and custom formats. Some of these requirements include the need to support customized strings for elements of a time representation such as the month name. To support this variety of representations requires a relatively sophisticated input/output capability. The Boost Date-Time Library provides a set of facets that use format strings and interoperate with the temporal types. Based on the current C++ formatting facets and integrated with standard streams they provide for customization of all aspects of input and output. For example:


date d(2005,Jun,25);
cout << pt << endl; // "2005-Jun-25"
//example to customize output to be "LongWeekday LongMonthname day, year"
//                                  "%A %b %d, %Y"
date_facet* facet(new date_facet("%A %B %d, %Y"));
cout.imbue(locale(cout.getloc(), facet));
cout << d << endl;
// "Saturday June 25, 2005"

stringstream ss;
ss.str("Saturday June 25, 2005");
date_input_facet* input_facet(new date_input_facet("%A %B %d, %Y");
ss.imbue(locale(ss.getloc(), input_facet));
date d2; //not_a_date_time
ss >> d2;

The section Extensions to time_put Format Flags describes the additional formatting flags provided in this proposal. The new flags support concepts like fractional seconds formatting which are needed to support new concepts provided by this proposal. The classes date_facet, time_facet, date_input_facet, and time_input_facet provide support for localization and customization of input/output.

Local Time Adjustments

Local time adjustment is an example of particularly difficult domain logic that is simplified by the proposed library. The rules associated with these adjustments are needed in many applications and overall the library support is quite poor. Consider, for example, an application that calculates arrival times for an airline. For the end user, it is important to see the arrival time of the flight in the arriving time zone not the departing time zone. Thus the application needs to manage the logic associated with time zone transitions and daylight savings time transitions. Other than that, the core application comes down to adding the length of the flight to the starting time of the flight.

One core element of the local time adjustment is the representation of the time zone. A time zone is quite complex to specify completely since it needs to include everything from output strings to rules that define the start and end of daylight savings time. The most broadly used solution to representing time zones is provided by the POSIX 1003.1 standard for representation of timezones. The posix_time_zone class provides direct support for this standard.

Here's a snippet of the core of this application as written using the proposed library extension including posix_time_zone:


//Red-eye flight from Arizona to New York that transits over 
//a daylight savings time (DST) transition boundary.  Az 
//doesn't ever shift to DST while New York does.  Thus at 
//2:00 (in the middle of the flight the eastern US shifts 
//it's clocks forward an hour.  Luckily the application 
//developer doesn't need to know these details.

//setup the timezones      
posix_time_zone nyc_tz("EST-05EDT,M4.1.0,M10.5.0");
posix_time_zone phx_tz("MST-07:00:00");

//construct the departure time in phx local time
date_time departure_time_phx(date(2004, Oct, 30), hours(23));

//convert to utc time
date_time departure_time_utc = departure_time_phx.to_utc(phx_tz);

//calculate the arrival time in utc
minutes flight_length = hours(4) + minutes(30);
date_time arrival_time_utc = departure_time_utc + flight_length;

//now adjust the arrival time which is in the Phoenix timezone to NY 
date_time arrival_time_nyc = arrival_time.to_local(nyc_tz);

Additional Capabilities

Some other significant capabilities that simplify programming with dates and times include:

The following sections describe these capabilities in more detail.

Calendrical Algorithms

Calendrical algorithms, or date generators, are tools for generating other dates or schedules of dates. These generator algorithms allow the representation of concepts such as "The first Sunday in February". They are useful in performing tasks such as calculating holidays. In this proposal these algorithms are incorporated into the interface of the date class. For example, the following constructors incorporate these algorithms:


//Construct the date for the Last Sunday in January
date d1(2004, Jan, Last_Week, Sunday);

//Construct a date for the Sunday in the 50th week of the year
date d2(2004, 50, Sunday);

Some additional algorithms included in the date class allow for calendar navigation and calculations. For example:


date d2 = d1.next_weekday(Tuesday);
date d3 = d1.previous_weekday(Friday);

days day_count1 = d1.days_until_weekday(Thursday);
days day_count2 = d1.days_before_weekday(Monday);

Compatibility with Standard Library

Compatibility with the current standard library is an important requirement for any new addition to the library. In this proposal there are several aspect of compatibility including:

  • compatibility with collections
  • compatibility with current input-output library
  • compatibility with C types time_t and tm
  • build on current capabilities where possible

Since the temporal types in this proposal are value types, they can be used as values in standard library collection classes. In addition, since the temporal types support comparison operations they can be used as a key in a map or as an element of a set.

Input and output using the standard library streaming and facet capabilities is another key aspect of standard library compatibility.

The proposal integrates with time_t and tm by allowing dates and times to construct from these types.

Flexible Time Resolutions

Some of the most difficult issues with programming dates and times is selecting an appropriate epoch, resolution, and size of internal representation. The choices are classic time and space tradeoffs. For this reason, providing a framework for users to provide customization of these elements greatly expands the utility of the library.

This is discussed in more detail in Underlying representations for dates and times.

Special Value Support

Special values provide the ability to represent 'logical values' with the various temporal types. For example, the ability to represent not-a-date-time or infinities is helpful in many circumstances. This is similar to how floating point types have values for not-a-number (NAN) and infinity. Infinities are useful in applications that need to represent concepts like 'a-long-time-ago' or 'forever'.

Special values can apply to both time points and time durations and thus alter the rules of calculation. Here are some code examples using special values:


date d; //default construct to not_a_date_time
if (d.as_special() == NOT_A_DATE_TIME) { //true
  //...
}

date_time inf(POSITIVE_INFINITY);
date_time t(date(2005, Jan, 1), hours(3));
if (t < inf) { //always true unless t == positive infinity    

Related Capabilities Not Included

There are other date-time related functionalities that are not included in this proposal. These decisions are primarily a reflection of the author's experience of useful capabilities in the domain, while keeping the size of the proposal manageable. If a consensus of the committee believes any of these items should be incorporated, the author is willing to incorporate them.

These include:

  • Recurring Intervals
  • Timezone Database
  • Local Time Types
  • Full support for leap seconds
  • Iterators in time
  • Timers

Recurring Intervals

Recurring intervals are a form of generator that are useful in scheduling and other applications. A recurring interval is typically defined by an initial interval and a recurrence duration. Conceptually this is the idea of "schedule the meeting for 9-10 every Monday starting on July 11, 2004". There are many options and variations on this theme. While these could be included the use of recurring intervals is somewhat specialized and can be built on top of the existing foundation.

Timezone Database

The timezone database capabilities of Boost Date-Time Library are highly useful. The primary capability is to support construction of time zones from a regional specification as follows:


tz_database tz_db;
tz_db.load_from_file("date_time_zonespec.csv");

boost::shared_ptr<time_zone_base> nyc_tz = 
       tz_db.time_zone_from_region("America/New_York");
boost::shared_ptr<time_zone_base> phx_tz = 
       tz_db.time_zone_from_region("America/Phoenix");

However, this capability introduces the issue of maintaining a set of data associated with the world timezones. This data changes frequently as governments change time zone rules. The management of the data makes this feature inappropriate for standardization.

Local Time Types

Boost Date-Time Library has a Time Point type that holds a time zone as well as the time value. While useful, this class is not included in the proposal as it adds significant complexity to the design. For example, a dependency on shared_ptr is introduced to allow for efficient management of the time zone types.

In addition, the overall benefit of this integrated class is somewhat limited. All the local time conversions and other desired operations can be performed without this class.

Iterators in Time

Iterators provide the ability to generate a set of dates or times based on some starting conditions and an ending point. Based on the foundation of the core temporal types bi-directional iterators can be provided that enable the simple generation of calendars and other time-related constructs.


//print a series of dates  
day_iterator start(date(2005,Jul,7));
day_iterator end(date(2005, Jul, 10));
std::copy(start, end, std::ostream_iterator<date>(std::cout, " \n"));

//make a list of 3 dates
std::list<date> dl;
std::copy(start, end, std::back_inserter(dl));

The Boost Date-Time Library provides the following iterator types: day_iterator, week_iterator, month_iterator, year_iterator, time_iterator, and local_time_iterator.

Timers

There are two primary types of timers:

  • Elapsed Timers
  • Countdown Timers

Elapsed timers are useful for measuring the duration of activities. For example:


//micro timer reads clock at microsecond resolution
micro_timer mt; //automatically starts timer
cout << mt.elapsed() << endl;
sleep(1);
//elapsed will be about a second - something like: 00:00:01.000123
cout << mt.elapsed() << endl;
sleep(1);
st.pause(); //stop timing
//...
st.resume(); //continue timing

Countdown timers provide the opposite interface from elapsed timers. Starting with a fixed time duration, they gradually subtract time until they reach zero. For example, measuring the time left in a basketball game is an application for a countdown timer.

Concepts Overview

The date time domain is rich in terminology and problems. The following is a brief introduction to the concepts reflected in this proposal.

Points, Durations and Periods

There are 3 basic concepts that serve as the foundation for the representing times and dates:

  • Time Point - an instant in the time continuum (dimensionless)
  • Time Duration - a length of time unattached to a any time point
  • Time Period - a length of time between two time points

as illustrated in the figure below.

Durations can be used to measure how long something takes. Consider the following:

  • the meeting took 2 hours
  • the flight will take 45 minutes
  • the program execution took 1.0020 seconds
  • the congress took 4 days to pass the bill
  • current time is -02:25:14 from launch of the rocket
  • timeout should fire in 20 milli-seconds

The examples above illustrate how resolution plays into durations. The resolution of interest depends very much on the application domain. And a single application may make broad use of many different resolutions. Also note that durations can have negative or positive values. This is useful in many applications and fits into the calculation rules described later.

Time points describe when an event has or will occur. Consider the following:

  • the meeting will be on June 10, 2004
  • the baby was born on June 10, 2004 at 1:00 pm
  • the rocket will launch on 2004-Jun-10 15:00:25.030 EST

A single time point may be described by one or more 'labels' of varying precision. For example,


2004-Jun-10 15:00:00     == 2004-Jun-10 3 pm
//UTC is Universal Time Coordinated - EDT is Eastern Daylight Time
2004-Jun-10 15:00:00 UTC == 2004-Jun-10 19:00:00 EDT

Note that these labels belie the complexities of what time they actually represent. All these examples assume we are using a gregorian calendar (discussed more later). In addition, the first example puts aside the issue of local time adjustments by leaving the time zone unspecified.

Time periods express a range of time. Consider the following:

  • the meeting will start on June 10, 3 pm and last 2 hours
  • the trip will start June 10th and last 4 days
  • the rocket burn went from 2004-Jun-10 15:00:00.0030 to 2004-Jun-10 15:00:01.0050
  • we will arrive between June 1st and June 3rd.

These examples of time periods illustrate that a time period is simply a starting time and an ending time or a starting time and a time duration. Periods are essential to the simplification of logic associated with planning and scheduling systems. To accomplish this, periods can be shifted, intersected, merged, and combined in various ways. In addition, they can be tested for adjacency, containment, and relative location.

These same concepts are discussed and reflected in "Patterns for things that change with time" by Martin Fowler. More recently, these same concepts have also been recognized in the ISO 8601 standard for representations of dates and times. Unlike dimensionless numbers, these different concepts serve different roles in application development. My personal background has included the development of a planning and scheduling system used to schedule thousands of daily events for a major satellite phone system. This experience, as well as development of other applications, led me to many of the same concepts described by Fowler.

Resolution

Each of these temporal types has a 'Resolution' which is defined by the smallest representable duration. Thus, a 'date' is a 'time point' with a resolution of 1 day. For representing a 'time' the resolution of the duration must increased. For example, time_t is a time-point with a resolution of 1 second. The ptime class in the Boost Date-Time Library has an adjustable resolution typically set at 1 micro-second or 1 nano-second.

The smallest duration representable within date, days, and date_period is one day. Thus these types have a resolution of one day. Other temporal types such as hours and milliseconds provide higher resolutions.

Calendar and Time Systems

Calendars describe the rules for for mapping the observable solar cycles into patterns such as days, weeks, months, and years. The Gregorian system, adopted in the sixteenth century, is the most widely used calendar system today. It defines the sequence of days and months in a year, as well as adjustments (such as leap years). The proposal uses a 'proleptic Gregorian' calendar which extends the Gregorian system back in time prior to it's adoption.

The ISO Calendar, defined in the ISO 8601 standard, maps to the Gregorian calendar, but has a unique technique for calculating week numbers. This proposal provides direct support for calculation of ISO week numbers. The ISO Week Data Calendar illustrates the iso week information.

There are many other calendar systems such as the Chinese, Mayan, Islamic, and Hebrew. This proposal does not attempt to provide direct extensibility to all other calendar systems. However, the concepts reflected in the proposal can be applied to other calendar systems which differ in the details of how days are labeled and how concepts such as years and months are treated.

A Time system provides all these categories of temporal types as well as the rules for labeling and calculating with time points. These systems may include additional adjustment rules such as 'leap seconds'.

Epoch Time

For the calculation of dates and times it is convenient to calculate using a count of days, seconds, or some other time unit starting from a particular point in the time continuum. The starting point for this count is called the Epoch Time. The Epoch time for time_t is 1/1/1900 00:00:00.

Leap Seconds

Like leap years, leap seconds help keep a clock measured time in alignment with observed solar times. Leap years follow a regular pattern while leap seconds are declared as the need arises due to wobbles in the earth's rotation. The irregular nature of leap seconds makes them difficult to incorporate into computer based time systems.

More information on leap seconds is described at the US Navy web site.

Time Zones and Local Time

Most local time systems are based on Coordinated Universal Time (UTC) but are also adjusted for earth rotation so that daylight hours are similar everywhere. In addition, some local times include Daylight Savings Time (DST) adjustments to shift the daylight hours during the summer.

Coordinated Universal Time - UTC

UTC (Coordinated Universal Time) is a widely used standard based on the solar time at the Prime Meridian. Formerly known as Greenwich Mean Time (GMT) it is also often referred to as 'Zulu Time' by the military organizations.

UTC is adjusted for earth rotation at longitude 0 by the use of Leap Seconds.

Daylight Savings Time - DST

Daylight Savings Time (called Summer Time in Europe) adjusts the clock forward during summer months so that the 'active' hours match the hours of daylight. This practice became widespread during the twentieth century. Wikipedia provides details on the practice.

For computer programs, DST presents a challenge. In addition to the complex rules that define the start and end of DST, the 'jumping of times' creates

  • ambiguous time labels
  • invalid time labels

Consider the switch to daylight savings for the eastern time zone in the United States. For 2005 this happened on April 3rd at 02:00:00 local time. During this transition, the clock ticks from 01:59:59 to 03:00:00. Thus, the time labels from 02:00:00 to 02:59:59 are said to be invalid.


//2005-Apr-03 01:00:00 EST
//2005-Apr-03 01:30:00 EST
//2005-Apr-03 03:00:00 EDT
//2005-Apr-03 03:30:00 EDT

During the switch back from daylight savings time the problem of ambiguous time labels occurs. That is, a time without an adornment for daylight savings cannot be calculated to be clearly in, or out of, daylight savings time. Thus, in the eastern United States timezone, on the switch back from daylight savings, the times between 01:00:00 and 01:59:59 repeat twice.


//2005-Oct-30 00:30:00 EDT
//2005-Oct-30 01:00:00 EDT
//2005-Oct-30 01:30:00 EDT
//2005-Oct-30 01:00:00 EST
//2005-Oct-30 01:30:00 EST
//2005-Oct-30 02:00:00 EST

Clocks

A Clock Device is software component (tied to some hardware) that provides the current date or time with respect to a time system. A clock can measure the current time to a known resolution which may be higher, or lower, than a particular time representation.

Different software systems have different needs for software clocks. Many modern applications need high resolution, millisecond or higher, clock measurements. Modern computers routinely provide these higher resolution clocks. In addition, dedicated clock hardware often provides even higher level resolutions than typical computer hardware provides.

Impact on the Standard

This is a pure library proposal, it does not add any new language features. It does include changes to the existing facets to support enhanced date-time input and output.

Limitations of the Current C++ Standard

The current C/C++ standard library recognizes the need for tools to manipulate dates and times. Although useful, the types in the current standard are mostly derived from the C library and do not reflect modern practice.

Some of the key issues with the current capabilities include:

  • limitations of the time ranges and resolution provide by time_t
  • all functions limited to one second time resolution
  • no distinction between durations and points in time
  • lack of symmetry in time_get and time_put facets
  • time range provided by tm is unspecified
  • no way to represent only a date

The time_t type provided in the C standard has a beginning epoch of 1970-Jan-1 00:00:00. For any application that needs to represent times prior to 1970 time_t is insufficient. Because of this, simple applications like representing birthdays cannot be written using time_t. Overall, the core function of time_t is to serve as an interface to computer clocks. Unfortunately, with a resolution of one second it is insufficient for many modern applications which require fractional seconds to measure event times. These applications cannot use time_t or tm to represent times.

Another limitation with the current standard is that it does not provide elegant support for comparison and mathematical operations on time values. For example, consider the following 'typical' date manipulation code:


//only want to calculate with dates, but time_t
//doesn't support that...
time_t start_time(0); 
start_time += 5*(60*60*24) + 2*(60*60*24*7);

A value object approach has some significant advantages in code clarity:


date d(1970, Jan, 1); 
d += days(5) + weeks(2);

Clearly the first code example could be simplified by the use of constants and such, but the lack of a standard here means that unclear code is all to frequently the norm. Also, constants are insufficient because some time durations (months and years) are not fixed lengths of time. A month varies in length from 28 to 31 days and a year varies in length from 365 to 366 days. This issue is discussed in more detail in the section on Arithmetic Operations on Temporal Types.

The tm in the current standard structure is used largely for input-output using time_get and time_put. Unfortunately the tm structure provided is a bit ambiguous about the range of times supported. The field tm.year is defined to start at 1900. It is unclear if years prior to 1900 (negative values) are supported. Many applications need to support year representations prior to 1900 making this ambiguity an issue for writing portable output code based on tm.

Changes to the Current time_get and time_put Facets

This proposal includes provisions for advanced input and output of date-time values. To support this, additional formatting flags are introduced to the existing time_put facet. In addition, methods are added to the time_get facet to provide for format-based parsing of dates and times. This is described in more detail below.

One major issue for the library is to support symmetry of input and output operations. For example, it is reasonable to expect that a date, or other temporal type, can be output to a stream and then read back in. For example:


date d(2004, Jan, 1);
std::stringstream ss;
ss << d;  //calls `time_put` with format %Y-%b-%d
//ss.str() == "2004-Jan-01"
date d2;  //not_a_date_time
ss >> d2; //can't implement directly with current `time_get`

The current time_get facet does not currently provide full support for format-based input. This is a result of the time_get facet not providing the ability to set input formats while time_put provides for sophisticated formatting via format strings. Specifically note the signatures of get_date and put:


time_get<...
  iter_type get_date(iter_type s, iter_type end, 
                     ios_base& f, ios_base::iostate& err, 
                     tm* t) const;  //no format pattern here

time_put<...
  iter_type put(iter_type s, ios_base& f, 
                char_type fill, const tm* tmb,
                const charT* pattern, const charT* pattern_end) const;

The pattern and pattern_end parameter provide for the flexible customization of date output. So, in essence, a new date_get is needed that supports the following signature:


time_get<...
  iter_type get_date(iter_type s, iter_type end, 
                     ios_base& f, ios_base::iostate& err, 
                     const charT* pattern, const charT* pat_end, //<-- added parameters
                     tm* t) const;

Note that the library working group already has recognized that the current state of these facets makes correct implementations difficult in [http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#461 DR 461]. Adding format parameters should improve the implementability as has been demonstrated in the Boost Date-Time Library.

In addition to enhancements to support format-based input, this proposal expands the set of format flags to support additional concepts such as fractional seconds. These additional format flags are described further in Extensions to time_put Format Flags.

Additions to Clock Interfaces

The current standard provides for std::localtime and std::gmtime functions to retrieve locally adjusted and UTC times respectively. The resolution of these calls is one second. This proposal recommends providing equivalent functions that provide microsecond resolution. Many platforms have clocks supporting microsecond resolution. This higher resolution is important for many modern applications. Proprietary versions of these functions exist on common platforms. Finally, the higher resolution can be made optional if the platform does not support the higher resolution. The approach of the proposal to clocks allows for the addition other clock device implementations.

Other Standards and Related Work

Other C++ Libraries

There are so many date-time libraries for C++ and other languages it would be literally impossible to list them all here.

Some important examples include the Rogue Wave library, Recursion Software C++ Toolkit (originally ObjectSpace.foundations) library, Microsoft Foundation Classes, IBM ICU.

Looking at many of these libraries there are lots of differences. However, some important recurring themes emerge:

  • representation of dates only
  • representation of dates and times
  • representation of time durations
  • support for time zones and local time adjustments

The following sections highlight some of the key classes and features of these libraries.

Rogue Wave

The Rogue Wave libraries have long provided support for temporal programming. The Rogue Wave libraries include several key types including:

One significant feature of the Rogue Wave libraries is the representation of 'special values'. Called 'Sentinels' in the Rogue Wave library, they allow for the representation of special temporal values such as 'Not A Date Time' or 'Infinity'. This is similar to the special values concepts provided in this proposal.

Recursion Software Time<Toolkit>

The Recursion Software C++ Toolkit library supports several key types including:

  • date - a date
  • time - a 24 hour time_duration to microsecond resolution
  • date_and_time - date plus a time
  • timeperiod - equivalent to time_duration concept in this proposal
  • stopwatch - a timer
  • timezone - encapsulation of daylight savings and UTC offset information

Microsoft Foundation Classes

The Microsoft Foundation Classes provide two primary classes for manipulation of dates and times: CTime and CTimeSpan. CTime is a point in time with a resolution of one second. CTimeSpan is a time duration. Essentially small value types that wrap the C library, these classes create a basic C++ interface for application developers.

ICU Library

The IBM International Components for Unicode (ICU) libraries have several date-time related capabilities. The ICU date-time user guide provides more information on this C++ library. This library provides a class UDate to represent dates. In addition, ICU includes a TimeZone class to provide for localization of dates.

Other Language Libraries

Most modern languages support a variety of types to support date-time programming.

JAVA Libraries

JAVA provides a number of different types for the representation of dates and times. These include

Confusingly, the JAVA Date actually represents a time to millisecond resolution. It serves as the primary temporal type provided by the JAVA foundation libraries. Calendar is the base class for Gregorian Calendar that provides arithmetic capabilities with times. Locale specific formatting and parsing is specified by DateFormat and implemented using format flags in the implementation SimpleDateFormat. TimeZone provides an interface and SimpleTimeZone provides an implementation for performing local time adjustments.

Python Libraries

The Python Date Time Module provides a number of different types for the representation of dates and times. These include

The timedelta class is an example of the time duration concept. datetime and date are time points that are similar to the date_time and date classes in this proposal.

ISO 8601 - Representation of Dates and Times

The ISO 8601 standard defines an international standard for formatting of dates and times. Conceptually, ISO 8601 provides for the representation of lengths of time, points in time, and intervals of time at different resolutions. Most of the concepts of time representation in the ISO 8601 standard, with the exception of recurring intervals, are reflected in this proposal. In addition, the proposal provides support for input and output of dates and times as specified in ISO 8601.

ISO 8601 provides for two main format types: normal and extended. The normal representation represents a date or time using numeric values starting at the largest time division down. For example a date is represented as YYYYMMDD where YYYY is a four digit specification of the year, MM is a two digit specification of a month and DD is a two digit specification of a day.

Some examples of Normal representation include:


20040301  is March 1, 2004
20040301T020559,01  is March 1, 2005 02:05:59.01

The extended form includes additional punctuation and thus is a bit easier for humans to read:


2004-03-01   is March 1, 2004
2004-03-01T02:05:59,01 is March 1, 2005 02:05:59.01

One nice feature of ISO 8601 is a consistent ordering of time components from larger to smaller. For example, years before months before days in the date.

This proposal provides direct support for iso 8601 formatting. For example, the facet classes provide for ISO 8601 input and output:


date d(2005,Jun,25);
date_facet* facet(new date_facet());
facet->set_iso_format();
cout.imbue(locale(cout.getloc(), facet));
cout << d << endl; //20050625
facet->set_iso_extended_format();
cout << d << endl; //2005-06-25

While ISO 8601 standard provides an excellent foundation for one standard set of input and output approaches, there are limits. Specifically ISO 8601 does NOT provide for:

  • using 'strings' for names of elements (eg: 'September' instead of 09)
  • localization of date and time strings
  • representation of time periods as open or closed ranges
  • full specification of time zones

So while this proposal provide direct support for input and output in ISO formats, it provides a more sophisticated input-output capability overall.

POSIX 1003.1 Timezone Representation

The IEEE 1003.1 POSIX standard provides a textual specification for the representation of timezones. This is summarized as follow:


"std offset [dst [offset],start[/time],end[/time]]" (w/no spaces).

'std' specifies the abbreviating of the time zone name. '[' and ']' indicate optional fields. 'offset' is the offset from Universal Coordinated Time (UTC). 'dst' specifies the abbrev of the time zone during daylight savings time. The second offset is how many hours changed during Daylight Savings Time (DST). 'start' and 'end' are the dates when DST goes into, and out of, effect. 'offset' takes the form of:


[+|-]hh[:mm[:ss]] {h=0-23, m/s=0-59}

'time' and 'offset' take the same form. 'start' and 'end' can be one of three forms:


Mm.w.d {month=1-12, week=1-5 (5 is always last), day=0-6} 
Jn {n=1-365 Feb29 is never counted} 
n {n=0-365 Feb29 is counted in leap years}

The following is an example defining the timezone for the West Coast of the United States.


PST-08PDT,45/02,310/02

Breaking this down:


PST-08PDT,45/02,310/02
^^^   ^^^
 |     |
 |     When Daylight Savings Time (DST) is in effect the abbreviation is PDT (Pacific Daylight Time)
 |
 defines PST as the normal abbreviation


PST-08PDT,45/02,310/02
   ^^^    ^^^^^ ^^^^^
    |       |     Switch back from DST on day 310 02:00:00
    |       |
    |       Switch to DST is day 45 at 02:00:00 hours
    |
    Non DST offset from Universal Coordinated Time (UTC) is -8 hours

The Posix Time Zone section describes the class posix_time_zone that implements these rules.

C Standard Library

The current C standard library, via header <time.h>, provides types and functions that form the basis of most current time manipulation programs. This includes types such as time_t and tm, as well as functions such as gmtime, mktime, and strftime.

The author is unaware of any formal proposals to the C standard committee for changes to date and time. However, David Tribble has a number of proposals that may be brought to the C committee at some point in the future.

Overall the proposals in these papers are complementary to this proposal. The suggested enhancements to the C library would serve as an excellent foundation for building the functionality provided in this proposal.

OMG/Corba Time Service

The Object Management Group (OMG) has defined two standards for timing services: Time Service and Enhanced Time Service. These standards serve as another example of a standard that reflects many of the core elements of this proposal. Some of the similar features of these services include:

  • time period concepts
  • timepoint resolutions to 100 nano seconds
  • epoch from 1582

The current proposal would provide for the basis for simple integration with these services allowing an alternative implementation of a clock from a distributed server.

Timezone Database

The Timezone Database provides an open collection of timezone information and tools.

A variety of operating systems provide an extension that provides The work captured in the timezone database demonstrates much of the complexity associated with various time adjustments. Some of the interesting capabilities of the timezone database include:

  • mapping of regions to timezones (eg: "america/phoenix")
  • representation of historical timezone data

While the Boost Date-Time Library provides a timezone capable of performing similar functions, that capability is NOT part of this proposal. In addition, historical time zones are not directly supported, although the base time zone and local time interactions allow for extended implementations to provide this capability.

Boost Date-Time Library

The Boost Date-Time Library serves as the primary foundation for this proposal. Differences between the proposal and Boost date-time are outlined in Difference from Boost Date-Time.

Design

Overview of Design

This proposal divides the library interface into several levels:

  • Core Interfaces
  • Concrete Temporal Types
  • Additional Types

The core interfaces provide a templated library core that supports flexible resolutions and other capabilities similar to the way std::basic_string provides a core for string types. The concrete temporal types are built from the core interfaces and realize a set of types that serve as the primary programmatic interface. This is similar to how std::string and std::wstring provide concrete realizations that most C++ programmers use. The additional types provide implementation of clocks, time zones, and input output facilities. These classes provide other capabilities not offered directly in the temporal types. The figure below illustrates the various classes that make up the proposal.

Core Interfaces

The following classes make up the core interfaces of the library. The core interfaces are templates the separate aspects of the key library interfaces from implementation so that additional temporal type variations can be provided. Typical developers will not use these interfaces directly, but will use the temporal types based on these interfaces.

Interface Types

Type NameDescription
basic_dateCore interface for dates.
basic_timeCore interface for combined date and time types.
basic_durationCore interface for duration types.
basic_time_periodCore interface for period types.
basic_time_zoneCore interface for time zones.
ymd_typeReplacement for tm that includes fractional seconds and resolution information.
gregorian_calendarCore implementation of gregrorian / iso calendar.

YMD Types

To efficiently format dates and times it is convenient to have a structure that represents the 'broken down' time. This is similar to tm without the string values and with some additional fields to represent fractional seconds and other information.


//struct to represent elements of a point in time
struct timepoint {
  year_type  year;        //32 bit unsigned integer -- range depends on calendar
  week_type  month;       //short integer 1-12 -- zero flags invalid
  short      day_of_year; //short 
  short      day_of_week;  //0-6  -- 0 == sunday
  short      week_number;  //1-53 - 0 indicates invalid
  short hours;             //0-24
  short minutes;           //0-59
  short seconds;           //0-60 -- 60 is leap second
  short fractional_seconds_count; 
  short frac_seconds_resolution;  //increments of 10 only
};

For input and output this form is a useful tool.

Summary of Temporal Types

The temporal types provide the main programmer interface to the library. The temporal types are value types that provide for efficient comparison, assignment, calculation, and other operations. In general, these types are designed to approach the efficiency of a regular integer type in terms of size, comparison and calculation efficiency. Some of the design decisions impacting these types is summarized in Temporal Types - Key Design Decisions.

Temporal Type Summary

Type NameTemporal TypeDescription
datepointRepresent a date using gregorian calendar.
date_periodperiodRepresent a date period using date and days.
daysdurationRepresent a count of days.
weeksdurationRepresent a count of weeks.
monthsdurationRepresent a count of months.
yearsdurationRepresent a count of years.
date_timepointRepresent a combined date and time with 1 microsecond resolution.
date_time_periodperiodRepresent a period using date_time.
secondsdurationRepresent a count of seconds.
millisecondsdurationRepresent a count of milliseconds.
microsecondsdurationRepresent a count of microseconds.
nanosecondsdurationRepresent a count of nanoseconds.

Note that user defined temporal types can be added as needed. For example, a user can use basic_date_time to create a time point with nanosecond level resolution. To accomplish this might require expanding the size of the underlying type to 96 bits or changing the epoch times.

Arithmetic Operations on Temporal Types

These 3 temporal types form the foundation for enabling sophisticated calculations using dates and times. For example, no matter the resolution of time points and durations we can say that the following calculations apply where --> means 'results in'.

Calculation Results and Typing

Rule --> Result TypeNotes
Timepoint + Duration --> TimepointValid only for durations of equal or greater resolution.
Timepoint - Duration --> TimepointValid only for durations of equal or greater resolution.
Timepoint - Timepoint --> DurationTimepoints of the same resolution only.
Duration + Duration --> DurationIn mixed resolution durations, higher resolution duration must be on the left.
Duration - Duration --> DurationIn mixed resolution durations, higher resolution duration must be on the left.
Duration * Integer --> Duration 
Integer * Duration --> Duration 
Duration / Integer --> DurationInteger Division rules.
Duration + Timepoint --> UndefinedCompilation error.
Duration - Timepoint --> UndefinedCompilation error.
Timepoint + Timepoint --> UndefinedCompilation error.

Note that the above typing rules are somewhat different from those used in Boost Date-Time Library. After some experimenting with various solutions to handling multi-resolution arithmetic, the proposed approach seems to offer a pragmatic approach that enables clean application coding while avoiding truncation error surprises for application developers.

The effect of the above rules can be seen more clearly with some examples:


date d(...);
d += seconds(10); //compilation error -- dates have resolution of 1 day
d += days(1);     //ok.
d += months(2);   //ok.
d += days(3) + weeks(2)   //ok.
d += weeks(2) + days(2);  //compilation error
  date_time dt(...);     //microsecond resolution time point
dt += seconds(100);    //ok.
dt += days(2);         //ok.
dt += nanoseconds(1);  //compile error -- resolution insufficient
dt += microseconds(1); //ok.

In addition, to the rules above, there are additional rules associated with special value handling:

Special Value Calculation Rules

Rule --> Result TypeNotes
Timepoint(NADT) + Duration --> Timepoint(NADT) 
Timepoint(∞) + Duration --> Timepoint(∞) 
Timepoint + Duration(∞) --> Timepoint(∞) 
Timepoint - Duration(∞) --> Timepoint(-∞) 
Timepoint(+∞) + Duration(-∞) --> NADT 
Duration(+∞) + Duration(-∞) --> NADT 
Duration(∞) * Zero --> NADT 
Duration(∞) * Integer(Not Zero) --> Duration(∞) 
Duration(+∞) * -Integer --> Duration(-∞) 
Duration(∞) / Integer --> Duration(∞) 

Lack of Communitivity for Some Operations

While most time durations behave exactly as would be expected by normal numeric operations, years and months exhibit odd behavior because they are not of a fixed length. That is, sometimes a year is 365 days and sometimes it is 366 (leap year).

In addition to the basic capabilities above most mathematical operations on dates and times are reversible.

That is,


date d(2004,Jan,1);
d1 = d + months(1); //d1 == Feb 1
d1 -= months(1); //d1 == d

However the following is a difficulty:


date d(2004,Jan,31);
date d1 = d + months(1); //Feb 29
date d2 = d1 - months(1); //oops Jan 29th
d1 - months(1) == d; //false.

Not all temporal types follow the same rules as integer types in calculations. For example, months and years are not directly convertible to a number of days. A month is between 28 and 31 days. A year is either 365 or in a leap year 366 days. Logic that needs to 'add months' or 'add years' needs to manage this issue.

The following W3C XML reference describes the proposed arithmetic rules in more detail XML Schema Adding Durations .

Summary of Additional Types

The following are some additional classes that augment the temporal types to provide input/output and local time adjustment capabilities.

Input/Output Facets

Type NameDescription
time_facetFormat based output facet for localization/customization of output.
time_input_facetFormat based input facet for localization/customization of input.

The library provides several clock types for measuring the current time to different levels of resolution.

Clock Types

Type NameDescription
day_clockMeasures the current time at day level resolution.
second_clockMeasures the current time to one second resolution.
microsecond_clockMeasures the current time to microsecond resolution.

The concrete time zone classes provide the ability to perform local time adjustments.

Time Zone Types

Type NameDescription
time_zone_baseNarrow char instantiation of basic_time_zone for use with date_time.
custom_time_zoneClass that provides for the ability to provide custom timezones in an application.
posix_time_zoneProvides implementation of Posix 1003.1 time zone specifications.

Exceptions for Errors

All errors are signaled by exceptions. While this makes the library less suitable for some contexts, exceptions are a usual part of modern C++ code. Exceptions are thrown in the following circumstances:

  • Attempts to construct and invalid temporal type (eg: 2005-Feb-30)
  • Using input streaming with exceptions enabled

All exceptions derive from std::out_of_range or std::logic_error and are summarized in the table below.

Exception Summary

TypeMethodExceptionBase TypeDescription
dateconstructor, operator>> bad_day_of_month std::out_of_range Thrown if day of month is out of range (eg: Jan 32)
dateconstructor, operator>> bad_day_of_year std::out_of_range Thrown if day of year is invalid. (note this is t.b.d. since there is no constructor)
dateconstructor, operator>> bad_month std::out_of_range Thrown if the specified month is out of range (eg: month 13)
dateconstructor, operator>> bad_year std::out_of_range Thrown if the year is out of range (eg: >= 10000, or < 1400)

Extensions to time_put and Format Flags

The following table describes the new flags to be supported by time_put facet.

New Formatting Flags for Facets

FlagDescription
%fFractional seconds and separator - always used even when value is zero.
%FFractional seconds and separator - only output when non-zero.
%sSeconds separator and fractional seconds
%TTime in 24-hour notation %H:%M:%S (from strftime)
%qISO time zone
%QISO extended time zone
%rTime in AM/PM notation - same as '%I:%M:%S %p' (from strftime)
%VISO week number in range from 0 to 53 (from strftime)
%ZTime zone name - long (eg: 'Eastern Standard Time').
%ZPPosix time zone string (eg: EST-05EDT+01,M4.1.0/02:00,M10.5.0/02:00

In addition to adding new formatting flags this proposal recommends changing the default formatting of dates and times. The following table