Proposed Standard C (C0X)
Calendar Library Functions


By David R. Tribble
david@tribble.com

Revision 1.6, 2002-06-10

Contents


Cover Sheet


Document Number: WG14 N___/X3J11 __-___ C0X Revision Proposal ===================== Title: Calendar Functions Author: David R. Tribble Author Affiliation: Self Postal Address: Plano, TX USA E-mail Address: david@tribble.com Web URL: http://david.tribble.com Telephone Number: *************** Sponsor: ________________________________________ Revision: 1.6 Date: 2002-06-10 Supersedes: 1.5, 2002-04-20 Proposal Category: __ Editorial change/non-normative contribution __ Correction X_ New feature __ Addition to obsolescent feature list __ Addition to Future Directions __ Other (please specify) _____________________________ Area of Standard Affected: __ Environment __ Language __ Preprocessor X_ Library X_ Macro/typedef/tag name X_ Function X_ Header __ Other (please specify) _____________________________ Prior Art: No specific implementation. Target Audience: C/C++/POSIX programmers. Related Documents (if any): None Proposal Attached: X_ Yes __ No, but what's your interest? Abstract: The addition of new constants, types, and functions in the <time.h> header for calendar computations, intended to augment the existing capabilities of the 'tm' structure and its related functions. Date conversions are also brought closer in line with the rules of ISO 8601.


Our units of temporal measurement, from seconds on up to months, are so complicated, asymmetrical and disjunctive so as to make coherent mental reckoning in time all but impossible. Indeed, had some tyrannical god contrived to enslave our minds to time, to make it all but impossible for us to escape subjection to sodden routines and unpleasant surprises, he could hardly have done better than handing down our present system. It is like a set of trapezoidal building blocks, with no vertical or horizontal surfaces, like a language in which the simplest thought demands ornate constructions, useless particles and lengthy circumlocutions. Unlike the more successful patterns of language and science, which enable us to face experience boldly or at least level-headedly, our system of temporal calculation silently and persistently encourages our terror of time.

It is as though architects had to measure length in feet, width in meters and height in ells; as though basic instruction manuals demanded a knowledge of five different languages. It is no wonder then that we often look into our own immediate past or future, last Tuesday or a week from Sunday, with feelings of helpless confusion.

— Robert Grudin, Time and the Art of Living, 1982

1. Introduction

This proposal presents enhancements to the standard C library affecting the time and date handling functions. The standard addressed is ISO/IEC 9899:1999 [2].

There are several key components to the proper handling of dates, times, and calendars:

System time

This is the implementation's notion of the current date and time during program execution. In the standard library, this is embodied by the time_t type.

Typically, this type represents an interval of time from a specific predefined date (sometimes known as the system epoch). For example, POSIX systems implement the time_t type as a signed integer count of the number of seconds since 1970-01-01 00:00:00 UTC. Other systems encode the date and time as bitfields within time_t values, and still other systems define time_t as a floating-point type. The current (C99) definition of time_t establishes no constraints other than it must be an arithmetic type.

Calendric systems

This is the set of rules that specify how a given time value (of type time_t) is converted into a set of values representing the year, month, day, hour, etc., of the calendar. The most common calendric system in use is the Gregorian calendar, which was first established in 1582 by an edict from Pope Gregory XIII in several Catholic European countries, and by the end of the 20th century had been adopted by almost all of the countries of the world.

Timezones

The world is divided into timezones in order to deal with the fact that the Earth is round, such that 12:00 noon in London is not simultaneously noon in New York. Every country has rules governing its use of timezones. Most regions use an integer number of hours offset from the UTC timezone as the basis of their timezone calculations, but a few regions use offsets with fractional hours.

Daylight Saving Time (DST)

Some countries employ a set of timekeeping rules whereby the clock is advanced ahead a specific amount of time (typically one hour) during the Spring (usually in April) and back again in the Fall (usually October) as a way of increasing the number of working daylight hours during the summer months. These rules thus make it possible for times within a certain interval (typically between 02:00:00 and 03:00:00) to occur not at all on one particular night of the year (when DST takes effect) and to occur twice on another particular night of the year (when DST reverts back to normal).

Leap seconds

A leap second is a second of time added or subtracted from civil time to adjust for the rotation of the Earth. Some implementations are capable of keeping track of leap seconds, and some are not. TAI time is based on the rotation of the Earth, and is maintained worldwide by atomic clocks. UTC civil time is based on TAI atomic time. Every 18 months or so, an additional leap second is added to UTC time to align it with TAI time, so that the two times never differ by more than 0.9 seconds. This adjustment is necessary to account for the fact that the Earth rotates about two milliseconds slower per day than calendars allow for, which adds up to an additional second every 500 days or so. (Currently, about 30 leap seconds have been added to UTC time since the practice began in 1972.)

Printable representations

Calendar dates and times should be convertible into printable character (string) representations. Functions should exist to convert calendar dates into printable strings and vice versa. (The ISO 8601 specifies a particular standard character representation for dates, times, and durations, but it is not the only accepted representation. See reference [1].)

Standards

The ISO 8601 international standard defines character representations for dates, times, and durations. It also defines the numbering of weeks within years, the numbering of days within weeks, and the specification of timezone offsets. (See reference [1].)


2. Existing Deficiencies

There are several deficiencies and limitations with the existing (C99) standard library time and date functions, either because of missing functionality or because some operations are difficult (or impossible) to achieve.

System time limitations

  • There is no way to query the implementation about the range or precision of valid time_t values. (This missing functionality, however, is not discusssed in this proposal. See reference [P1] for a related proposal regarding enhancements to the standard time_t type.)

  • There are no reserved values of type time_t that indicate unknown or erroneous dates, except for -1 being returned as a special error value from the mktime() function. (A time_t value of -1 may actually represent a valid date in implementations that support negative system time values.)

  • There is no convenient way to add a given time interval (i.e., a specific number of days or seconds) to a time_t value. (The only portable way to achieve this is to convert the given system time into a broken-down time using localltime() or gmtime(), adding the given number of days to the tm_mday member or the given number of seconds to the tm_sec member, then normalizing and converting the broken-down time back into a time_t value.) In other words, there is no inverse of the difftime() function.
  • Calendar limitations

  • The standard library can apply the rules of only a single calendric system, namely the Gregorian calendar. While this is the most widely accepted calendar, this makes it impractical to support (in a standard manner) other regional calendric systems (e.g., the Islamic lunar calendar, the Jewish lunar calendar, the Greek Orthodox calendar, the Chinese National calendar, etc.).

  • There is no way to query the implementation about the range of valid dates, specifically, the range of valid year number values for the tm_year member of the tm structure.

  • There is no way to retrieve or manipulate time values with subsecond precision (irrespective of the precision of the time_t type). This capability is missing from the tm structure type and the strftime() function.

  • There are no special tm structure representations to indicate unknown or erroneous dates.

  • There is no easy way to determine if a given broken-down date (e.g., a set of year, month, and day numbers) represents a valid calendar date.

  • There is no member of the tm structure that specifies the week number of the year. This number must be calculated explicitly by user code or by calling the strftime() function.

  • The existing functions for converting a time_t value into a broken-down date return pointers to global data objects, and thus require special handling when implemented on multithreaded systems (typically requiring the allocation of thread-specific global memory areas). This applies to the following functions:

    asctime()
    ctime()
    gmtime()
    localtime()

  • There is no convenient way to compare two broken-down calendar dates, or to determine the difference between two broken-down calendar dates as a broken-down date itself. (For example, the difference between the dates 1979-01-01 and 1980-01-02 is exactly one year and one day, as is the difference between the dates 1980-01-01 and 1981-01-02; however, the first difference represents 366 days while the second difference represents 367 days because 1980 is a leap year.) Converting broken-down dates into time_t values in order to compare them may not work, because the complete range of broken-down dates may not be representable as system time values.
  • Timezone limitations

  • The standard library can only handle two timezones at the same time: UTC and the local timezone, as provided by the gmtime() and localtime() functions. The local timezone is generally established as a global variable.

  • Arbitrary timezone settings cannot be applied easily to a broken-down time value (tm structure). This makes it difficult to convert a broken-down time for one given timezone into a time for another arbitrary timezone.

  • It is possible to determine the offset of the local timezone from UTC, but this requires explicit calculations by user code (i.e., finding the difference between the results of the localtime() and gmtime() functions).

  • It is not possible to retrieve a timezone by name or offset from UTC (e.g., "PST" or "+0800").

  • Once a broken-down time value has been established, either relative to UTC or to the local timezone, its association with that timezone setting is lost. A broken-down time value (tm structure) contains no indication of what timezone was used to derive its member values.

  • There is no way to convert a broken-down time into a system time_t value relative to any timezone other than the local timezone (using the mktime() function).
  • Daylight Saving Time (DST) limitations

  • Historically, the application of Daylight Saving Time (DST) to civil time has varied considerably. Rules vary according to the period of history (notably World War II) and according to region. Some countries employed rules that resulted in up to six different possible DST adjustments for certain periods in time.
  • Leap second handling

  • There is no consistent method for handling (or ignoring) leap seconds, especially future leap seconds.

  • 3. Solutions

    The types and functions described in this proposal solve many of the problems mentioned in the previous section.

    Some of the ideas presented in this proposal were based on sections in an early draft of the ISO C99 standard which were removed from the final revision (see reference [3]). Other ideas were based on portions of the standard runtime library of the Java language (see reference [5]).


    Consider the geometry of how we measure time. It can be divided into circle time and square time: clock time and calendar time.

    — David Ewing Duncan, The Calendar, 1998.

    4. Types

    The following types are defined in the <time.h> standard header file:

        typedef time_t
        struct _calendar
    
        struct _calendardelta
    
        struct _calendarinfo
        struct _timezone
    

    Each type is described in detail below.


    4.1. Typedef time_t

        typedef arithmetic-type  time_t;
    

    This type represents a system date and time as reckoned by the implementation during program execution.

    The exact representation of the date and time values, the datatype employed, the range of values, and the precision of values are all implementation-defined.

    This type is the same as that defined by the ISO/IEC 9899:1999 (C99) standard. See reference [P1] for a related proposal regarding enhancements to the standard time_t type.


    4.2. Structure _calendar

    This structure represents a broken-down date and time under a specific calendric system.

    The _calendar structure contains at least the following members, in any order. The semantics of the members and their normal ranges are expressed in the comments, and each member is described in further detail in the sections below.

        int             cal_type;       // Calendar type
        const struct _timezone *
                        cal_zone;       // Timezone applied
        int             cal_era;        // Era number
        int             cal_year;       // Year number [1900,2099]
        int             cal_mon;        // Month of the year [1,12]
        int             cal_week;       // Week of the year [0,53]
        int             cal_mday;       // Day of the month [1,31]
        int             cal_yday;       // Day of the year [1,366]
        int             cal_wday;       // Day of the week [1,7]
        int             cal_hour;       // Hour of the day [0,23]
        int             cal_min;        // Minute of the hour [0,59]
        int             cal_sec;        // Second of the minute [0,59]
        long            cal_nsec;       // Nanosecond [0,999999999]
        int             cal_dst;        // Daylight Saving Time [0,1]
    

    The value ranges shown for each member are the minimum conforming ranges for the calendar type named "Gregorian", which do not necessarily apply to other calendar types.

    Rationale/Discussion

    This structure is an extension of the existing tm  structure, with enhancements for better handling of timezones, multiple calendric systems, subsecond time precision.

    All of the members of pointer type are restricted to being null or pointing to user-allocated data objects, so that there are no additional memory management requirements for the library (such as requiring calls to malloc() or free() for any of the objects pointed to by these members). This allows calendar objects to be easily allocated statically, on the stack, or on the heap without any complicating memory allocation constraints. It also simplifies the semantics of copying calendar objects, passing them as arguments to functions, and returning them from functions.

    Additional members may exist within this structure, but are implementation-specific (and thus any use of them is not portable). For example, there may exist members for specifying holidays, the effects of intercalary months, religious holidays, phases of the moon, etc.

    Rationale/Discussion

    Since calendar structure objects may be allocated by the programmer by any means at his disposal, all calendric systems must use the same calendar structure type. This implies that any additional members required to implement the rules for any particular calendric system supported by the implementation must be declared in this structure type, and thus shared by all of the supported calendric systems.

    Functions are provided to convert between system times (as represented by time_t values) and broken-down calendar dates. Thus there is a relationship between system times and calendar dates, but the nature of this relationship (e.g., the precision of system times versus the precision of calendar dates, and the range of year numbers for valid times and dates) is implementation-defined.

    4.2.1. Member cal_dst

        int  cal_dst;               // Daylight Saving Time adjustment
    

    Specifies whether a Daylight Saving Time adjustment has been applied to the date represented by the calendar object. This member has a value equal to one of the following constants:

      0
    1
    A DST adjustment is not in effect.
    A DST adjustment is in effect.

    Implementations may set this value to other values (the use of which is not portable), with the restriction that positive nonzero values indicate that DST adjustments have been made to the calendar date.

    This member, in combination with the cal_zone member, specifies the difference between the time represented by the calendar object and UTC time.

    4.2.2. Member cal_era

        int  cal_era;               // Era number
    

    Specifies the era represented by the date settings of the members of the calendar object. Valid ranges for this member are implementation-defined. The value of this member depends on the calendric system utilized by the calendar object (as specified by the cal_type member) as well as the settings of the other structure members.

    Conforming implementations are required to support, at a minimum, a single era within the Gregorian calendric system having a value equal to the _CAL_ERA_COMMON constant, representing the AD (or CE) era.

    Rationale/Discussion

    For the Gregorian calendar, this member specifies which era is represented by the calendar object, i.e., AD/CE or BC/BCE.

    Since conforming implementations are only required to support a Gregorian calendar with year numbers in the range [1900,2099], they are thus only required to support a single era value (representing the AD/CE era), which is represented by the _CAL_ERA_COMMON constant.

    This member is useful for the _calendarformat() function, which could simply employ this member as an index into an array of static strings (e.g., "AD" et al).

    The combination of this member and the cal_year member specifies a given year within the calendric system employed by the calendar date object.

    There is no requirement that the supported values of this member form a set of consecutive integers, nor that they must be strictly positive values.

    The term "cycle" can be used as a synonym for "era".

    4.2.3. Member cal_hour

        int  cal_hour;              // Hour of the day [0,23]
    

    Specifies the hour of the day of the date represented by the calendar object.

    For the Gregorian calendar, this represents the number of hours since midnight.

    4.2.4. Member cal_mday

        int  cal_mday;              // Day of the month [1,31]
    

    Specifies the day of the month of the date represented by the calendar object.

    4.2.5. Member cal_min

        int  cal_min;               // Minute of the hour [0,59]
    

    Specifies the minute of the hour of the date represented by the calendar object.

    4.2.6. Member cal_mon

        int  cal_mon;               // Month of the year [1,12]
    

    Specifies the month number of the date represented by the calendar object.

    Rationale/Discussion

    The definition differs from that of the tm_mon member of the tm structure in that it encodes the exact month number, rather than the number of months since the first month of the year. Thus January in the Gregorian calendar is month number 1 (rather than 0). This is intended to simplify its usage.

    4.2.7. Member cal_nsec

        long  cal_nsec;             // Nanosecond [0,999999999]
    

    Specifies the number of nanoseconds within the current second of the date value represented by calendar object.

    It is implementation-defined whether or not this member has any value other than zero after calling the _setcalendartime() function, or in exactly what manner such a value corresponds to time_t values.

    Rationale/Discussion

    Having this member fills a void in the capabilities of the current tm  structure by allowing for time representations with subsecond precision.

    This also draws on prior art from POSIX and BSD Unix systems, which provide the timeval and timespec structures and the gettimeofday() system function, which provide system time values with microsecond and nanosecond precisions.

    Some implementations are not capable of discerning time to greater precision than the nearest second, while others can discern time to the nearest microsecond or better.

    4.2.8. Member cal_sec

        int  cal_sec;               // Second of the minute [0,59]
    

    Specifies the second of the minute of the date represented by the calendar object.

    Conforming implementations are required to support a Gregorian calendric system having a range of seconds of at least [0,59].

    If leap seconds are implemented, the value of this member may include one extra leap second, for a value of 60.

    4.2.9. Member cal_type

        int  cal_type;              // Calendar type
    

    Specifies the calendar type of the calendar structure, i.e., the calendric system employed by the calendar object.

    Conforming implementations are required to support at least one calendar type named "Gregorian" (which is the same value as the _CAL_NAME_GREGORIAN constant), which corresponds to the _CAL_TYPE_GREGORIAN constant and which implements the date calculation rules of the worldwide Gregorian calendric system. It is unspecified whether the implementation of the Gregorian calendar handles leap seconds or not.

    This member is initialized by a call to the _getcalendar() function. A program shall expect that this member is set to a value corresponding in some implementation-defined manner to the calendar name passed to the _getcalendar() function, but that otherwise there are no rules governing its value.

    All of the possible values that this member may be assigned by a call to the _getcalendar() function should be defined as constants with names of the form '_CAL_TYPE_XXX'  in the <time.h> standard header file.

    Rationale/Discussion

    Having this member allows implementations to support multiple calendric systems, each having its own unique type number. The value of this member indicates to the calendar library functions what kind of calendric system is utilized by a calendar object, and thus what particular date and time calculation rules to apply to that object.

    It is conceivable that implementations may support more than one variant of the standard Gregorian calendar, e.g., those with different era names ("CE" instead of "AD") or those with gaps reflecting the historical acceptance of the Gregorian calendar in 1582, 1752, 1917, etc. Other variants include Gregorian calendars that observe leap second rules and those that do not.

    An obvious implementation of this member is to use it as a small integer index into an array of calendric calculation tables or function pointers, which could be static data or be dynamically loaded into memory on demand, but this is by no means the only possible approach.

    4.2.10. Member cal_wday

        int  cal_wday;              // Day of the week [1,7]
    

    Specifies the day of the week of the date represented by the calendar object. A value of 1 specifies the first day of the week.

    For the Gregorian calendar, the first day of the week is a Monday, as per the rules of ISO 8601 (see reference [1]).

    The range of values and and meaning of this member may be different for other calendric systems.

    Rationale/Discussion

    ISO 8601 rules state that weeks begin on Mondays, and that weekday numbers range from 1 to 7.

    This member is thus defined slightly differently than the tm_wday member of the tm structure. It is fairly trivial, however, to convert between the two structure member values:

        t.tm_wday = c.cal_wday % 7;
        c.cal_wday = (t.tm_wday == 0 ? 7 : t.tm_wday);
    

    4.2.11. Member cal_week

        int  cal_week;              // Week of the year [0,53]
    

    Specifies the week number in the year of the date represented by the calendar object.

    For the Gregorian calendar, week number 1 is the first week of January having at least four days (which also contains the first Thursday, and also contains January 4th), as per the rules of ISO 8601 (see reference [1]), except that the week number for days ocurring prior to week number 1 have a week number of 0 (rather than in the last week of the previous year), and the week number for days ocurring after the last week of the year (week 52 or 53) are considered as occurring in the same year (rather than in the first week of the subsequent year).

    Rationale/Discussion

    The definition of this member corresponds (almost) to the "%V" format specifier of the strftime() function.

    ISO 8601 rules state that weeks begin on Mondays, and week number 1 of the year is the week that includes January 4th, which is also the week that includes the first Thursday of the year, and is also the first week containing at least four days in the year.

    The ISO rules are modified somewhat so that any days occurring prior to the first week of the year (i.e., week number 1) are considered to be in week number 0 of the year (instead of week 52 or 53 of the previous year), and that any days occurring after the last week of the year (i.e., week number 52 or 53) are considered to be in the same year (instead of in the week 1 of the next year). These modifications to the rules exist to simplify the calculation of week numbers.

    Examples

    The following dates are shown with their corresponding ISO 8601 week and weekday designations and their corresponding <cal_week, cal_wday> pair.

        2000-01-01    1999-W52-6    <0,  6>
        2001-12-31    2002-W01-1    <53, 1>
        2003-12-31    2004-W01-3    <53, 3>
        2005-01-01    2004-W53-6    <0,  6>
    

    4.2.12. Member cal_yday

        int  cal_yday;              // Day of the year [1,366]
    

    Specifies the day of the year of the date represented by the calendar object.

    For the Gregorian calendar, January 1st is day number 1.

    Rationale/Discussion

    The definition differs from that of the tm_yday member of the tm structure in that it encodes the exact day number, rather than the number of days since the first day of the year. Thus January 1st in the Gregorian calendar is day number 1 (rather than 0). This is intended to simplify its usage.

    4.2.13. Member cal_year

        int  cal_year;              // Year number [1900,2099]
    

    Specifies the year number of the date represented by the calendar object.

    Conforming implementations are required to support a reasonably wide range of year values for each calendar type they support. For the Gregorian calendar, conforming implementations are required to support year number values at least in the range [1900,2099]. (Note that this does not imply any requirements for the supported range of valid time_t system time values.)

    This member may have a value equal to the _CAL_YR_ERROR constant, indicating that the calendar object does not represent a valid date, i.e., that it represents an erroneous date. (A calendar object having this member set to such a value implies that the rest of the members, except for the cal_type member, do not have meaningful values.)

    Whether non-positive year number values have meaningful interpretations or not is implementation-defined.

    Rationale/Discussion

    The definition differs from that of the tm_year member of the tm structure in that it encodes the exact year number, rather than an offset from some arbitrary year (e.g., 1900). This is intended to simplify its usage.

    This member also encodes erroneous dates. The special marker value (_CAL_YR_ERROR) must be a value outside the range of valid year numbers for all calendric systems.

    The combination of this member and the cal_era member specifies a given year within the calendric system employed by the calendar date object.

    Negative year values can be used to represent different eras within the calendric system utilized. For the Gregorian calendar, negative year numbers could represent dates in the BC/BCE era, and positive year numbers would represent dates in the AD/CE era. Note that the Gregorian calendar has no zero (0) year; thus it is implementation-defined whether this member should have the value 0 or -1 to represent the year BC 0001. (Thus the dates AD 1300 and BC 1300 could be represented as {AD,+1300} and {BC,-1299}, respectively.)

    An alternate representation could allow the cal_year member to specify the year number and the cal_era member to specify the era, such that the dates AD 1300 and BC 1300 both have a cal_year value of +1300, but different cal_era values (represented as {AD,+1300} and {BC,+1300}, respectively). This representational scheme is probably better for calendric systems having more than two eras (e.g., historical calendars representing year numbers relative to the reigning timespans of multiple emperors).

    The _calendarformat() function should generate the same string result for any given date regardless of the era+year representational scheme used.

    Mandating that conforming implementations must support years in a range of at least AD 1900 to 2099 seems like a reasonable minimum range, which provides for many applications that must deal with old dates (e.g., extant mortgage schedules) while also providing for dates spanning the current century.

    It is expected that quality implementations will support a wider range of years than this. For comparison, the built-in date functions of ANSI COBOL support dates going back to AD 1601, on the assumption that there are financial applications that must deal with very old mortgage and lease agreements that are still in force. The date 1601-01-01 also has the convenient property of being the first day of the previous 400-year cycle of the Gregorian calendar (the current cycle having begun on 2001-01-01). That day is also a Monday, when using a strictly linear (proleptic) representation of the Gregorian calendar rules and ignoring the various historical calendar changes.

    4.2.14. Member cal_zone

        const struct _timezone *  cal_zone;         // Timezone applied
    

    A pointer to a timezone object, specifying the timezone that was last applied to the calendar object. This pointer will be null if no timezone has been applied to the calendar object, or if the timezone information has been removed from the calendar object (by a call to the _setcalendarzone() function).

    This member in combination with the cal_dst member specifies the offset from UTC of the time represented by the calendar object.

    Rationale/Discussion

    This member specifies how the members of the calendar object have been adjusted for a particular timezone, which allows the _setcalendarzone() function to change or undo such adjustments.

    This member also provides a means of retrieving the name of the timezone applied to the calendar object, by calling the _calendarformat() function.

    Timezone information is represented by an entirely separate _timezone structure.

    The timezone object is used in conjunction with the the Daylight Saving Time setting specified by the cal_dst member to completely specify the offset from UTC represented by the calendar date object.

    The technique of storing a timezone within each calendar object allows for the manipulating of many calendar objects simultaneously, each having its own independent timezone setting.

    The allocation of the timezone object pointed to by this member is entirely the responsibility of the programmer. Thus, no calls to malloc() or free() are required of the standard library.

    It is intended that multiple calendar objects can share (i.e., point to) the same timezone object, since each cal_zone member is a pointer to a const object (and thus should not be used to modify the contents of the pointed-to timezone object). This also allows calendar objects to be copied in a straightforward manner.


    4.3. Structure _calendardelta

    This structure represents the absolute difference between two calendar dates, as computed by the _calendardiff() function.

    The _calendardelta structure contains at least the following members, in any order. The semantics of the members and their normal ranges are expressed in the comments, and each member is described in further detail in the sections below.

        int             cd_type;            // Calendar type number
        int             cd_eras;            // Eras
        int             cd_years;           // Total years
        long            cd_mons;            // Total months
        int             cd_ymons;           // Months less than a year
        long            cd_weeks;           // Total weeks
        int             cd_yweeks;          // Weeks less than a year
        long            cd_days;            // Total days
        int             cd_mdays;           // Days less than a month
        int             cd_ydays;           // Days less than a year
        int             cd_zones;           // Timezones
        int             cd_hours;           // Hours less than a day
        int             cd_mins;            // Minutes less than an hour
        int             cd_secs;            // Seconds less than a minute
        long            cd_nsecs;           // Nanoseconds less than a second
    

    Rationale/Discussion

    +INCOMPLETE.

    +INCOMPLETE


    4.3. Structure _calendarinfo

    This structure represents information about how a specific calendric system is implemented, providing ranges for each of the date and time members for valid broken-down date representations.

    The _calendarinfo structure contains at least the following members, in any order. The semantics of the members and their normal ranges are expressed in the comments, and each member is described in further detail in the sections below.

        int             ci_type;            // Calendar type number
        const char *    ci_name;            // Calendar name
        time_t          ci_first_time;      // Earliest time_t value
        time_t          ci_last_time;       // Latest time_t value
        int             ci_era_min;         // Minimum era number
        int             ci_era_max;         // Maximum era number
        int             ci_year_min;        // Minimum year number (1900)
        int             ci_year_max;        // Maximum year number (2099)
        int             ci_mon_min;         // Minimum month number (1)
        int             ci_mon_max;         // Maximum month number (12)
        int             ci_week_min;        // Minimum week of the year (0)
        int             ci_week_max;        // Maximum week of the year (53)
        int             ci_mday_min;        // Minimum day of the month (1)
        int             ci_mday_max;        // Maximum day of the month (31)
        int             ci_yday_min;        // Minimum day of the year (1)
        int             ci_yday_max;        // Maximum day of the year (366)
        int             ci_wday_min;        // Minimum day of the week (1)
        int             ci_wday_max;        // Maximum day of the week (7)
        int             ci_wday_1st;        // Weekday present in the 1st week (4)
        int             ci_hour_min;        // Minimum hour number (0)
        int             ci_hour_max;        // Maximum hour number (23)
        int             ci_min_max;         // Maximum minute number (59)
        int             ci_sec_max;         // Maximum second number (59)
        int             ci_leap_sec;        // Leap seconds supported (0)
    

    The values shown are the minimum values required for the Gregorian calendar, which might not necessarily apply to other calendar types.

    Additional members may exist within this structure, but are implementation-specific (and thus use of them is not portable).

    Conforming implementations are required to support at least one calendar information object for the calendar named "Gregorian".

    Rationale/Discussion

    This structure allows programs to determine the characteristics of the calendric systems supported by the implementation. In particular, the range of valid date components (e.g., valid year numbers) and whether or not leap seconds are supported are two useful pieces of information.

    4.3.1. Member ci_era_max

        int  ci_era_max;            // Maximum era number
    

    Specifies the maximum value that the cal_era member can have under the supported calendric system.

    Rationale/Discussion

    The Gregorian calendar has two eras, AD and BC (also known as CE and BCE, respectively). Conforming implementations are only required to support the AD/CE era of the Gregorian calendar, which corresponds to the _CAL_ERA_COMMON constant, since they are only required to support Gregorian calendar year numbers within the range [1900,2099].

    Implementations that support a larger range of years that spans both the AD/CE and BC/BCE eras, however, should support two distinct era numbers.

    4.3.2. Member ci_era_min

        int  ci_era_min;            // Minimum era number
    

    Specifies the minimum value that the cal_era member can have under the supported calendric system.

    Rationale/Discussion

    Implementations that support multiple eras are free to support negative era numbers.

    4.3.3. Member ci_first_time

        time_t  ci_first_time;      // Earliest time_t value
    

    Specifies the earliest time_t value that is representable by the calendar type. In other words, this is the earliest valid system time that can be converted into a meaningful calendar date object within the calendric system represented by the calendar information structure.

    Note that this value does not necessarily indicate anything about the complere range of valid calendar dates representable by the calendar type.

    Note also that this value, although of an arithmetic type, is not necessarily meaningfully comparable to any other time_t value (due to the fact that the representation of system time values is implementation-defined).

    4.3.4. Member ci_hour_max

        int  ci_hour_max;           // Maximum hour number
    

    Specifies the maximum value that the cal_hour member can have under the supported calendric system.

    Conforming implementations are required to support a Gregorian calendric system having hour numbers that fall within the range [0,23], where 0 represents midnight (12:00 AM).

    4.3.5. Member ci_hour_min

        int  ci_hour_min;           // Minimum hour number
    

    Specifies the minimum value that the cal_hour member can have under the supported calendric system.

    Conforming implementations are required to support a Gregorian calendric system having hour numbers that fall within the range [0,23], where 0 represents midnight (12:00 AM).

    4.3.6. Member ci_last_time

        time_t  ci_last_time;       // Latest time_t value
    

    Specifies the latest time_t value that is representable by the calendar type. In other words, this is the latest valid system time that can be converted into a meaningful calendar date object within the calendric system represented by the calendar information structure.

    Note that this value does not necessarily indicate anything about the complete range of valid calendar dates representable by the calendar type.

    Note also that this value, although of an arithmetic type, is not necessarily meaningfully comparable to any other time_t value (due to the fact that the representation of system time values is implementation-defined).

    4.3.7. Member ci_leap_sec

        int  ci_leap_sec;           // Leap seconds supported
    

    This member has a nonzero value if the supported calendric system recognizes leap seconds, otherwise it has a value of zero.

    Conforming implementations are not required to support calendric systems that are capable of recognizing leap seconds.

    4.3.8. Member ci_mday_max

        int  ci_mday_max;           // Maximum day of the month
    

    Specifies the maximum value that the cal_mday member can have under the supported calendric system.

    Conforming implementations are required to support a Gregorian calendric system having day of the month numbers that fall within the range [1,31].

    4.3.9. Member ci_mday_min

        int  ci_mday_min;           // Minimum day of the month
    

    Specifies the minimum value that the cal_mday member can have under the supported calendric system.

    Conforming implementations are required to support a Gregorian calendric system having day of the month numbers that fall within the range [1,31].

    4.3.10. Member ci_min_max

        int  ci_min_max;            // Maximum minute number
    

    Specifies the maximum value that the cal_min member can have under the supported calendric system. (The minimum value is 0.)

    Conforming implementations are required to support a Gregorian calendric system having minute numbers that fall within the range [0,59].

    4.3.11. Member ci_mon_max

        int  ci_mon_max;            // Maximum month number
    

    Specifies the maximum value that the cal_mon member can have under the supported calendric system.

    Conforming implementations are required to support a Gregorian calendric system having month numbers that fall within the range [1,12].

    4.3.12. Member ci_mon_min

        int  ci_mon_min;            // Minimum month number
    

    Specifies the minimum value that the cal_mon member can have under the supported calendric system.

    Conforming implementations are required to support a Gregorian calendric system having month numbers that fall within the range [1,12].

    4.3.13. Member ci_name

        const char *  ci_name;      // Calendar name
    

    Points to a string specifying the name of the calendric system utilized by the calendar information structure. This corresponds to the calendar name passed to the _getcalendarinfo() function in some implementation-defined manner, but is not required to be identical to it.

    All calendar types that are supported by an implementation should, in general, have a corresponding name constant defined in the <time.h> header file with a name of the form '_CAL_NAME_XXX'.

    A program shall expect that this member is initialized to the address of a null-terminated string containing only printable characters, whose contents are otherwise implementation-defined.

    4.3.14. Member ci_sec_max

        int  ci_sec_max;            // Maximum second number
    

    Specifies the maximum value that the cal_sec member can have under the supported calendric system. (The minimum value is 0.)

    Conforming implementations are required to support a Gregorian calendric system having second numbers that fall within the range [0,59].

    Rationale/Discussion

    Implementations that support leap seconds will initialize this member to a value of 60 to indicate that one additional (positive) leap second can occur on certain days.

    Conforming implementations are not required to recognize leap seconds, however. (See the ci_leap_sec member.)

    4.3.15. Member ci_type

        int  ci_type;               // Calendar type number
    

    Specifies the calendar type of the calendar information structure. Conforming implementations are required to support at least one calendar type named "Gregorian" (which is the same value as the _CAL_TYPE_GREGORIAN macro), which implements the date calculation rules of the worldwide standard Gregorian calendric system.

    A program shall expect that this member is set to a value corresponding in some implementation-defined manner to the calendar name passed to the _getcalendarinfo() function, but that otherwise there are no rules governing its value.

    All of the possible values that this member may be assigned by a call to the _getcalendarinfo() function should be defined as constants with names of the form '_CAL_TYPE_XXX'  in the <time.h> standard header file.

    Rationale/Discussion

    Having this member allows implementations to support multiple calendric systems, each having its own unique type number. The value of this member indicates to the library functions what kind of calendric system is utilized by a calendar object, and thus what particular date and time calculation rules to apply to that object.

    An obvious implementation of this member is to use it as a small integer index into an array of calendric calculation tables or function pointers, which could be static data or be dynamically loaded into memory on demand, but this is by no means the only possible approach.

    4.3.16. Member ci_wday_1st

        int  ci_wday_1st;           // Weekday present in the 1st week
    

    Specifies the day of the week that is always present in the first week of the year. If this value is indeterminate, the member is set to -1.

    Conforming implementations are required to support a Gregorian calendric system having a first week of the year containing a Thursday (day number 4), as per the rules of ISO 8601 (see reference [1]).

    4.3.17. Member ci_wday_max

        int  ci_wday_max;           // Maximum day of the week
    

    Specifies the maximum value that the cal_wday member can have under the supported calendric system.

    Conforming implementations are required to support a Gregorian calendric system having weekday numbers that fall within the range [1,7] where 1 represents Monday, as per the rules of ISO 8601 (see reference [1]).

    Rationale/Discussion

    ISO 8601 rules state that weeks begin on Mondays, and that weekday numbers range from 1 to 7.

    4.3.18. Member ci_wday_min

        int  ci_wday_min;           // Minimum day of the week
    

    Specifies the minimum value that the cal_wday member can have under the supported calendric system.

    Conforming implementations are required to support a Gregorian calendric system having weekday numbers that fall within the range [1,7] where 1 represents Monday, as per the rules of ISO 8601 (see reference [1]).

    Rationale/Discussion

    ISO 8601 rules state that weeks begin on Mondays and that weekday numbers range from 1 to 7.

    4.3.19. Member ci_week_max

        int  ci_week_max;           // Maximum week of the year
    

    Specifies the maximum value that the cal_week member can have under the supported calendric system.

    Conforming implementations are required to support a Gregorian calendric system having week numbers that fall within the range [0,53], as per the modified rules of ISO 8601 (see reference [1]).

    4.3.20. Member ci_week_min

        int  ci_week_min;           // Minimum week of the year
    

    Specifies the minimum value that the cal_week member can have under the supported calendric system.

    Conforming implementations are required to support a Gregorian calendric system having week numbers that fall within the range [0,53], as per the modified rules of ISO 8601 (see reference [1]).

    Rationale/Discussion

    ISO 8601 rules state that week priors to week number 1 of a given year are numbered as week 52 or 53 of the previous year. However, proper calculation of such week numbers is somewhat cumbersome, so we instead use slightly modified rules stating that such weeks are week number zero.

    4.3.21. Member ci_yday_max

        int  ci_yday_max;           // Maximum day of the year
    

    Specifies the maximum value that the cal_yday member can have (within any valid year) under the supported calendric system.

    Conforming implementations are required to support a Gregorian calendric system having day of the year numbers that fall within the range [1,366] as per the rules of ISO 8601 (see reference [1]).

    Rationale/Discussion

    ISO 8601 rules state that days of the year are numbered in the range from 1 to 366, with January 1st being day number 1.

    4.3.22. Member ci_yday_min

        int  ci_yday_min;           // Minimum day of the year
    

    Specifies the minimum value that the cal_yday member can have (within any valid year) under the supported calendric system.

    Conforming implementations are required to support a Gregorian calendric system having day of the year numbers that fall within the range [1,366] as per the rules of ISO 8601 (see reference [1]).

    Rationale/Discussion

    ISO 8601 rules state that days of the year are numbered in the range from 1 to 366, with January 1st being day number 1.

    4.3.23. Member ci_year_max

        int  ci_year_max;           // Maximum year number
    

    Specifies the maximum value that the cal_year member can have (within any valid era) under the supported calendric system.

    Conforming implementations are required to support a Gregorian calendric system having a maximum year number not less than 2099.

    Rationale/Discussion

    Implementations that support a Gregorian calendar with a wider range of year numbers are encouraged to support all of the calendar year numbers that are convertible into valid time_t values.

    4.3.24. Member ci_year_min

        int  ci_year_min;           // Minimum year number
    

    Specifies the minimum value that the cal_year member can have (within any valid era) under the supported calendric system.

    Conforming implementations are required to support a Gregorian calendric system having a minimum year number no greater than 1900.

    Rationale/Discussion

    Implementations that support a Gregorian calendar with a wider range of year numbers are encouraged to implement the proleptic Gregorian calendar, which assumes that the rules for leap days are in effect for all dates prior to the present date. While this does not reflect the true historical dates, it is a much simpler and more predictable model to implement and use.


    4.4. Structure _timezone

    The contents of this structure are implementation-defined.

    Rationale/Discussion

    Timezone objects can be retrieved by name (e.g., "UTC", "CST", "EST5EDT", "Eastern Standard Time", etc.) by the _gettimezone() function. Such names are implementation-specific, although it is hoped that some common naming convention can be achieved.

    Timezone objects are allocated and deallocated by the programmer. Therefore there are no additional requirements for the standard library to call malloc() or free().

    It is intended that multiple calendar objects can share (i.e., point to) the same timezone object, since each cal_zone member is a pointer to a const object (and thus should not be used to modify the contents of the pointed-to timezone object).

    It is recommended that implementations define the timezone structure such that its members are not pointers to allocated data objects that would need to be explicitly deallocated by the programmer. On the other hand, the structure members may be pointers to statically allocated objects which do not require any deallocation.

    Intimately related to timezones is the application of Daylight Saving Time rules. Timezones thus come in (at least) two flavors, those with DST in effect and those without. This setting is presumably reflected in their names (e.g., "EST" versus "EDT").

    Implementations that support complicated timezone naming schemes (such as POSIX) will probably provide extra members in this structure to deal with DST time changes, etc.


    Our obsession with time is itself timeless. After self-awareness, it may be our most distinctive trait as a species, since undoubtedly one of the first things we become self-aware about is our own mortality - the fact that we live and die in a set amount of time.

    — David Ewing Duncan, The Calendar, 1998.

    5. Constants

    This section describes constants that are defined in the <time.h> header file of every conforming implementation.

    Unless specifically stated otherwise, these constants do not necessarily have to be implemented as preprocessor macros.

    5.1. Constant _CAL_ERA_COMMON

    This is an integer constant representing the AD (or CE) era in the Gregorian calendric system.

    The interpretation of this member for other calendric systems is implementation-defined.

    5.2. Constant _CAL_NAME_GREGORIAN

    This is a string constant equal to the string "Gregorian". It specifies the name of the standard Gregorian calendric system, which must be supported by all conforming implementations when passed as the name argument to the _getcalendar() function and the _getcalendarinfo() function.

    An implementation is free to provide other constants with names of the form '_CAL_NAME_XXX', representing additional calendric systems supported by the implementation.

    5.3. Constant _CAL_TYPE_GREGORIAN

    This is an integer constant which specifies the standard Gregorian calendric system supported by the implementation, and can be passed as the name argument to the _getcalendartype() function.

    An implementation is free to provide other constants with names of the form '_CAL_TYPE_XXX', representing additional calendric systems supported by the implementation.

    (See also the description for the cal_type member and the ci_type member.)

    5.4. Constant _CAL_YR_ERROR

    This is an integer constant that, when assigned to the cal_year member of a calendar date object, specifies an erroneous (invalid) date.

    The arithmetic value of this constant is, by definition, outside the range of all valid year numbers of all calendric systems supported by the implementation.

    5.5. Constant _TIME_ERROR

    This is a constant of type time_t which represents an erroneous time value which does not represent any valid date.

    It is unspecified how an erroneous time value compares to other time values, except that it is not equal to any valid time value.

    5.6. Constant _TZ_ERROR

    This is a constant of type long int, representing an invalid timezone offset.

    The value of this constant must not compare equal to the offset in milliseconds of any supported combination of timezone and Daylight Saving Time setting.

    Rationale/Discussion

    This constant is used as a special error return value of the _gettimezoneoffset() function.

    5.7. Constant _TZ_LOCAL

    This is a string constant, equal to the string "", which specifies the local timezone. It may be passed as the tzname argument to the _gettimezone() function.

    The value of this constant must not compare equal to the name of any other timezone supported by the implementation.

    Rationale/Discussion

    Implementations are required to support some notion of the "local" timezone. For implementations that are not capable of discerning timezones, this may simply be the same as the UTC timezone.

    The value of the _TZ_LOCAL constant is guaranteed to compare unequal to any valid timezone name supported by the implementation.

    5.8. Constant _TZ_UTC

    This is a string constant, equal to the string "UTC", which specifies the Coordinated Universal Time (UTC) timezone. It may be passed as the tzname argument to the _gettimezone() function.

    Rationale/Discussion

    Implementation that cannot fully support timezones are nonetheless required to support at a minimum one timezone with a name matching the _TZ_UTC constant.


    It is given to us to live for the most part under the guidance of the discipline of Mathematics. If we learn the hours by it, if we calculate the courses of the moon, if we take note of the time lapsed in the recurring year, we will be taught by numbers and preserved from confusion. Remove the computus from the world, and everything is given over to blind ignorance.

    — Cassiodorus, ca. 550

    6. Functions

    The following sections describe the proposed functions to be added to the <time.h> standard library header.

    6.1. The _calendaradd() function

    Synopsis

        #include <time.h>
        int _calendaradd(struct _calendar *restrict cal,
            const struct _calendar *restrict dif);
    

    Description

    Adds the broken-down delta time pointed to by dif to the calendar date object pointed to by cal. The adjusted date is then normalized. The values of the members of the delta time object are not constrained to fall within their normal ranges upon entry to the function, and may be positive or negative. (The delta time object can be the result of a call to the _calendardiff() function.)

    +INCOMPLETE

    [[Describe the addition of the struct members, rollover, etc.]]

    Returns

    The function returns zero on success.

    If the cal_year member of the calendar object has a value equal to the _CAL_YR_ERROR constant, indicating that the calendar object represents an erroneous date, the function fails and returns -1.

    If the resulting adjusted date cannot be represented as a valid date within the calendric system employed by the calendar object, the function fails and returns a negative value.

    If the cal_type member has a value that is not supported by the implementation, the function fails and returns -1.

    Examples

    The following function adds a specified number of days to a given date object.

        #include <time.h>
    
        int add_days(struct _calendar *cal, int days)
        {
            struct _calendar    del;
    
            // Convert days into a delta time
            _getcalendar(&del, _CAL_NAME_GREGORIAN, NULL);
            del.cal_mdays = days;
    
            // Add the delta time to the given date
            return (_calendaradd(cal, &del));
        }
    

    +INCOMPLETE

    THE SUBSEQUENT SECTIONS MUST BE RENUMBERED!


    6.2. The _calendardiff() function

    Synopsis

        #include <time.h>
        long _calendardiff(struct _calendardelta *restrict dif,
            const struct _calendar *a, const struct _calendar *b);
    

    Description

    Determines the difference between the date represented by the calendar object pointed to by a and the one pointed to by b as a broken-down time. The difference is a broken-down date having all non-negative members, representing the absolute (non-negative) difference between the two dates.

    +REDO
    The members of the calendar object pointed to by dif are set to values corresponding to the number of years, months, days, etc., that must be added to the earlier of the two dates pointed to by a and b in order to get the later of the two dates. The resulting broken-down difference is such that adding each member of the broken-down date in the calendar object pointed to by dif to the corresponding members of the earlier calendar data pointed to by a or b (after adjusting for timezone differences) and then normalizing the result would result in a calendar object with members equal to that resulting from normalizing the calendar object pointed to by the later date. The members of the broken-down difference are set to values that are, as closely as possible, within their normal ranges.

    Undefined behavior results if the cal_type member of the objects pointed to by a and b have different values. (This implies that the difference between two calendar dates can be determined portably only if the dates are represented under the same calendric system.)

    The members of the object pointed to by dif are set to values as described below. (These members are known as the delta members, since they contribute to the overall computed difference.) Each member is set to the least possible non-negative value that is less than its maximum normalized value, except for the cal_era and cal_year members, which may be set to values that exceed their maximum normalized value. Except where specifically noted, the order in which the members are modified is implementation-defined. The existing values of the corresponding members of the objects pointed to by a and b marked "ignored" are ignored for the purposes of calculating the difference.

    cal_type
    Set to the same value as the cal_type member of the objects pointed to by a and b. Undefined results occur if these two members are not equal.

    cal_zone
    This pointer is set to null.

    cal_era
    Set to the absolute difference in whole eras between the calendar date objects pointed to by a and b. The meaning of this difference depends upon the calendar type (as specified by the cal_type member), and is implementation-defined.

    cal_year
    Set to the absolute difference in whole years between the calendar date objects pointed to by a and b. Note that this difference is affected by the values of the cal_era members of the two calendar date objects. For calendric systems supporting multiple eras, this member should be set to a value representing the absolute difference in whole years between the dates, which may span more than one era.

    cal_mon
    Set to the absolute difference in whole months spanning less than one year between the calendar date objects pointed to by a and b.

    cal_week  (ignored)
    Set to the absolute difference in whole weeks spanning less than one year between the calendar date objects pointed to by a and b.

    cal_mday
    Set to the absolute difference in whole days spanning less than one year between the calendar date objects pointed to by a and b.

    cal_yday  (ignored)
    Set to the absolute difference in whole days spanning less than one year between the calendar date objects pointed to by a and b. This member is modified only after the values of the cal_year, cal_mon, and cal_mday members have been determined.

    cal_wday  (ignored)
    Set to the absolute difference in whole weekdays spanning less than one week between the calendar date objects pointed to by a and b. This member is modified only after the values of the cal_year, cal_mon, and cal_mday members have been determined.

    cal_hour
    Set to the absolute difference in whole hours spanning less than one day between the calendar date objects pointed to by a and b.

    cal_min
    Set to the absolute difference in whole minutes spanning less than one hour between the calendar date objects pointed to by a and b.

    cal_sec
    Set to the absolute difference in whole seconds spanning less than one minute between the calendar date objects pointed to by a and b.

    cal_nsec
    Set to the absolute difference in nanoseconds spanning less than one second between the calendar date objects pointed to by a and b.

    cal_dst
    Set to the absolute difference in seconds between the Daylight Saving Time settings of the calendar date objects pointed to by a and b.

    If the cal_year member of either of the calendar objects pointed to by a or b has a value equal to the _CAL_YR_ERROR constant, the the cal_year member of the calendar object pointed to by dif is set to a value equal to the _CAL_YR_ERROR constant, indicating that an erroneous date value was encountered, and the function returns a nonzero value.

    If the cal_type member has a value that is not supported by the implementation, an error occurs and the function fails.

    Returns

    The function returns the total number of whole days difference between the date objects, which will be zero if date a is equal to date b, a negative value if date a occurs before date b, or a positive value if date a occurs after date b; or it returns a negative value equal to the LONG_MIN constant if an error occurs or if either of the dates has a cal_year member with a value equal to the _CAL_YR_ERROR constant (indicating an erroneous date). If the difference in whole days cannot be represented as a long int type, the return value is implementation-defined.

    The dif argument may be null, in which case the difference is computed as described above but no broken-down difference is returned. Passing a null pointer for the dif argument thus serves simply to compare the calendar dates pointed to by a and b.

    Rationale/Discussion

    While it is fairly easy to determine the difference between two calendar dates as the number of seconds between the two (using the mktime()/_getcalendartime() and difftime() functions, and assuming that the dates can be converted into valid system time values), it is nontrivial to determine the difference as a broken-down time.

    For example, the difference between 1979-01-01 and 1980-02-02 is exactly one year, one month, and one day when represented as a broken-down time. By the same token, the difference between 1980-01-01 and 1981-02-02 is also exactly one year, one month, and one day. However, the former difference represents a span of 397 (365+31+1) days, while the latter difference represents a span of 398 (366+31+1) days because 1980 is a leap year.

    Example

    The following function determines the broken-down difference between two calendar dates and prints the result to the standard output.

        #include <limits.h>
        #include <stdio.h>
        #include <time.h>
    
        void print_diff(const struct _calendar *a, const struct _calendar *b)
        {
            struct _calendar    dif;
            long                days;
    
            // Compute and print the difference between the two dates
            days = _calendardiff(&dif, a, b);
    
            if (days == 0)
            {
                // The dates are the same
                printf("No difference\n");
            }
            else if (days != LONG_MIN)
            {
                // Print the difference
                printf("%dy %dm %dd and %02d:%02:%02d.%03ld\n",
                    dif.cal_year, dif.cal_mon, dif.cal_mday,
                    dif.cal_hour, dif.cal_min, dif.cal_sec,
                    dif.cal_nsec/1000000);
                printf("%d years and %d weeks\n",
                    dif.cal_year, dif.cal_week);
                printf("%d years and %d days\n",
                    dif.cal_year, dif.cal_yday);
            }
            else
            {
                // An error occurred
                printf("Error\n");
            }
        }
    

    Given the two dates:

        a = 1980-01-01 01:02:16.555 Z
        b = 1982-02-02 02:04:03.000 Z
    

    the output of this function is:

        2y 1m 1d and 01:01:46.445
        2 years and 4 weeks
        2 years and 32 days
    

    Given two identical dates, such as:

        a = 1987-04-23 17:22:37.668 Z
        b = 1987-04-23 17:22:37.668 Z
    

    the output of this function is:

        0y 0m 0d and 00:00:00.000
        0 years and 0 weeks
        0 years and 0 days
    
    In other words, all of the delta members of the difference are zero.


    6.3. The _calendarformat() function

    Synopsis

        #include <time.h>
        int _calendarformat(char *restrict buf, size_t max,
            const char *restrict fmt, const struct _calendar *cal);
    

    Description

    Formats the string pointed to by buf according to the format control string pointed to by fmt so that it will contain a string corresponding to the date stored in the calendar object pointed to by cal. No more than max characters (including a terminating '\0' null character) will be written to buf.

    The formatting string pointed to by fmt contains characters and conversion specifier sequences beginning with a '%' character. The conversion specifiers recognized are the same as for the strftime() function except for the "%Z" specifier, and following specifiers are also recognized:

      %f is replaced by the locale's era name or abbreviation. [cal_era]
      %s is replaced by the nanosecond as a decimal number (0-999999999). An optional sequence of decimal digits may follow the '%', specifying the exact number of digits, including leading zeros, to insert into the formatted string. (Any digits occuring to the right of the last digit inserted are truncated, as per the ISO 8601 rules for representating decimal fractions.) [cal_nsec]
      %Za is replaced by the locale's time zone abbreviation, or by no characters if no time zone is determinable. [cal_zone, cal_dst]
      %Zn is replaced by the locale's full time zone name, or by no characters if no time zone is determinable. [cal_zone, cal_dst]

    Some of the format specifiers in the string pointed to by fmt are affected by the current LC_TIME locale setting.

    Returns

    The function returns the number of characters written to the string pointed to by buf if successful. If an error occurs, a negative value is returned which is the negated index of the character within the string pointed to by fmt where the error was first detected.

    If the cal_year member of the calendar object has a value equal to the _CAL_YR_ERROR constant, indicating that the calendar object represents an erroneous date, the contents of the string pointed to by buf are not modified, and the function returns -1.

    If the cal_type member has a value that is not supported by the implementation, the function fails and returns -1.

    Rationale/Discussion

    This function is equivalent to strftime(), but for the _calendar structure type. It also deals with erroneous date values.

    The "%s" format specifier allows the rightmost least significant digits of the nanosecond count to be truncated (instead of allowing for rounding). This follows the ISO 8601 rules for the representation of decimal fractions and for reduced precision formats. (See section 5.3.1.3 of reference [1].) It also agrees with the practice of truncating portions of date and time values on the right to produce shorter values of reduced precision, e.g., specifying hh:mm as an abbreviated form of hh:mm:ss.

    Example

    The following function formats a calendar date and writes it to the standard output.

        #include <stdio.h>
        #include <time.h>
    
        void print_date(const struct _calendar *cal)
        {
            char    buf[80];
    
            // Format the calendar date
            _calendarformat(buf, sizeof(buf),
                "%a %Y-%m-%d %H:%M:%S.%3s %Za", &cal);
    
            // Print the formatted calendar date
            printf("%s\n", buf);
        }
    

    Example output from this function:

        Tue 2001-09-11 08:50:23.166 EDT
    


    6.4. The _calendarscanf() function

    Synopsis

        #include <time.h>
        int _calendarscanf(const char *restrict buf, const char *restrict fmt,
            struct _calendar *cal, struct _timezone *tz);
    

    +INCOMPLETE

    CONSIDER REMOVING THE 'tz' PARM, and implicitly use _gettimezone() instead.

    Description

    Parses the contents of the string pointed to by buf, which contains a series of characters that form a date (such as one produced by a prior call to the _calendarformat() function), filling the members of the calendar object pointed to by cal and the members of the timezone object pointed to by tz appropriately. The interpretation of the contents of string buf is controlled by the format string fmt.

    The calendar date object pointed to by cal must have been previously initialized by a call to the _getcalendar() function. Specifically, the cal_type member must have a value corresponding to a calendric system that is supported by the implementation.

    The contents of the string pointed to by fmt are composed of whitespace characters, format specifiers (which begin with a "%" character), and regular characters.

    The parsing of the source string pointed to by buf proceeds by scanning from left to right, while proceeding in a corresponding fashion interpreting the formatting control string, attempting to match characters and format specifiers. If a character in the source string does not match the corresponding character or format specifier in the format control string, the parsing fails and an error is returned.

    Whitespace characters in the formatting string match one or more whitespace characters, of any type, in the source string. Regular characters in the formatting string match the exact same character in the source string. Format specifiers match one or more non-whitespace characters in the source string, as expected by the rules described for the _calendarformat() function, except that alphabetic characters are matched without regard to case.

    In addition to handling the format specifiers of the _calendarformat() function, the following specifier is also recognized:

      %? skips any character in the input string. An optional sequence of decimal digits may follow the '%', specifying the exact number of characters to skip; or an optional '*' character may follow the '%', specifying that all non-whitespace characters are to be skipped in the source string up to the next whitespace character or the terminating null character.

    The "%n" and "%t" format specifiers match exactly one whitespace character (a newline or a tab character, respectively) in the source string.

    The "%Za" and "%Zn" format specifiers require special handling, since timezone names and abbreviations might not be unique. The resulting value of the members of the timezone object pointed to by tz are thus implementation-defined, with the exception of the timezone name matching the _TZ_UTC constant, which uniquely specifies the Coordinated Universal Time (UTC) timezone with no Daylight Saving Time in effect.

    Returns

    Returns the number of characters scanned from buf, or a negative value if an error occurs.

    If the scanned source string specifies a date that cannot be represented as a valid calendar date, or if the source string is ill-formed, then the cal_year member of the calendar object will be set to a value equal to the _CAL_YR_ERROR constant, indicating that the calendar object represents an erroneous date. It is unspecified whether any of the other members of the calendar object or the timezone object will be set to meaningful values in such a case.

    If the cal_type member has a value that is not supported by the implementation, the function fails and returns -1.

    Rationale/Discussion

    This function is similar to the strptime() function provided by POSIX.

    The "%?" format specifier is a new invention. It is felt that there is a need to ignore sequences of non-whitespace characters that are otherwise unpredictable when a source string is being parsed.

    The cal_zone member of the calendar object is not modified, so that multiple calendar objects can share (i.e., point to) the same (read-only) timezone object.

    Example

    The following function parses a string containing a formatted date, producing a calendar date and printing its contents to the standard output.

        #include <stdio.h>
        #include <time.h>
    
        void parse_date(const char *buf)
        {
            struct _calendar    cal;
            struct _timezone    tz;
            int                 rc;
    
            // Parse the formatted date string
            rc = _calendarscanf(buf,
                    "%a %Y-%b-%d %2? %H:%M:%S.%3s %Za", &cal, &tz);
            _setcalendarzone(&cal, &tz);
    
            // Print the resulting calendar date
            if (rc < 0)
            {
                printf("error\n");
            }
            else
            {
                printf("%dwd, %dy %dm %dd, %dh %dm %d.%03lds %Za\n",
                    cal.cal_wday,
                    cal.cal_year, cal.cal_mon, cal.cal_mday,
                    cal.cal_hour, cal.cal_min, cal.cal_sec,
                    cal.cal_nsec/1000000);
            }
        }
    

    Given an input string of:

        buf = "Thu 1776-Jul-04 at 09:45:03.074 CDT"
    

    the output from this function is:

        4wd, 1776y 7m 4d, 9h 45m 3.074s CDT
    


    6.5. The _convertcalendar() function

    Synopsis

        #include <time.h>
        int _convertcalendar(struct _calendar *restrict dst,
            const struct _calendar *restrict src);
    

    Description

    Copies the calendar date object pointed to by src to the calendar date object pointed to by dst, converting between different calendric systems as appropriate.

    The calendar object pointed to by dst must have been properly initialized by a prior call to the _getcalendar() function (i.e., its cal_type member must have a properly initialized value.)

    If the calendar object pointed to by src represents an erroneous date (i.e., if its cal_year member has a value equal to the _CAL_YR_ERROR constant), the calendar object pointed to by dst is set to represent an erroneous date also, i.e., its cal_year member is set to a value equal to the _CAL_YR_ERROR constant.

    Returns

    Returns zero if the calendar date was successfully converted, or a negative value if the calendar date could not be converted, or a positive nonzero value if the resulting converted calendar date represents an erroneous date.


    +INCOMPLETE

    CONSIDER ADDING:

    6.6. The _freetimezone() function

    Synopsis

        #include <time.h>
        void _freetimezone(struct _timezone *tz);
    

    AND CHANGING:

        #include <time.h>
        struct _timezone * _make|alloc|gettimezone(const char *tzname);
    


    6.7. The _getcalendar() function

    Synopsis

        #include <time.h>
        int _getcalendar(struct _calendar *cal,
            const char *restrict name, const struct _timezone *restrict tz);
    

    Description

    Modifies the cal_type member of the calendar object pointed to by cal to reflect the calendric system matching the name pointed to by name. Also associates the timezone object pointed to by tz with the calendar object.

    The name argument points to a null-terminated string containing the name of a calendric system. The format and contents of such a string are implementation-defined. All conforming implementations, however, must support at least one calendric system matching the name "Gregorian". (Also see the _CAL_NAME_GREGORIAN constant.)

    The name argument may point to an empty string (""), in which case it specifies the default calendric system of the current locale.

    Except for matching the constant specified above, the manner in which the name argument is matched to the names of calendric systems is implementation-defined.

    The function also sets the cal_zone member to point to the timezone object pointed to by tz, undoing the timezone adjustments made by any previous timezone setting associated with the calendar object and then applying the new timezone settings.

    The tz argument can be null, in which case the calendar object has no timezone settings, i.e., it represents a time relatie to UTC without any DST adjustment.

    This function also sets the cal_year member of the calendar object to a value equal to the _CAL_YR_ERROR constant, indicating that the calendar object does not (yet) represent a valid date.

    All other members of the calendar object are set to zero or null.

    Returns

    The function returns zero on success, or a nonzero value if an error occurs. If no calendric system matching the specified name is supported by the implementation, an error occurs and the calendar object is not modified.

    Rationale/Discussion

    The cal_type member allows implementations to support multiple calendric systems, each having its own unique type number. The value of this member indicates to the calendar library functions what kind of calendric system is utilized by a calendar object, and thus what particular date and time calculation rules to apply to that object.

    It is conceivable that implementations may support more than one variant of the standard Gregorian calendar, e.g., those with different era names ("CE" instead of "AD") or those with gaps reflecting the historical acceptance of the Gregorian calendar in 1582, 1752, 1917, etc. Other variants include Gregorian calendars that observe leap second rules and those that do not.

    Implementations could allow the calendar name to specify the name of a dynamic shared library, allowing programs to load calendric systems dynamically at runtime.

    Multiple calendar objects can share (i.e., point to) the same timezone object.

    Example

    The following code fragment initializes a calendar date object with the Gregorian calendar and the local timezone.

        struct _calendar    cal;
        struct _timezone    tz;
    
        // Initialize the timezone
        if (_gettimezone(&tz, _TZ_LOCAL) != 0)
            printf("Bad timezone initialization\n");
    
        // Initialize the calendar date
        if (_getcalendar(&cal, _CAL_NAME_GREGORIAN, &tz) != 0)
            printf("Bad calendar initialization\n");
    


    6.8. The _getcalendarinfo() function

    Synopsis

        #include <time.h>
        const struct _calendarinfo * _getcalendarinfo(const char *name);
    

    Description

    Retrieves a calendar information object corresponding to the calendric system with a name matching the string pointed to by name. The calendar information object contains information about the supported ranges of values for the members of the _calendar structure.

    The name argument points to a null-terminated string containing the name of a calendric system. The format and contents of such a string and the means by which it is matched against the names of calendar information objects are implementation-defined. All conforming implementations, however, must support at least one calendric system with the name "Gregorian". (Also see the _CAL_NAME_GREGORIAN constant.)

    The name argument may point to an empty string (""), in which case it specifies the default calendric system of the current locale.

    Returns

    Returns a pointer to a (static) calendar information object corresponding to the named calendric system, or null if no such object with a matching name is supported by the implementation.

    Rationale/Discussion

    The structure returned by this function provides information about the valid values for various members of the _calendar structure, such as the minimum and maximum year number values for valid dates.

    Implementations could allow the calendar name to specify the name of a dynamic shared library, allowing programs to load calendric systems dynamically at runtime.

    The calendar information objects returned are expected to comprise a reasonably small set of supported calendric systems. They are intended to be treated as though they reside in static memory as non-modifiable data. As such, they do not require the use of malloc() or free() calls.


    6.9. The _getcalendartime() function

    Synopsis

        #include <time.h>
        int _getcalendartime(const struct _calendar *cal, time_t *t);
    

    Description

    Sets the system time pointed to by t to a value corresponding to the date stored in the calendar object pointed to by cal. If t is null, no time value is stored (but the checks on the calendar object are still performed as described below).

    Returns

    The function returns zero if the calendar object can be converted into a valid time_t value.

    If the cal_year member of the calendar object is equal to the _CAL_YR_ERROR constant (indicating that the calendar date is erroneous), the system time pointed to by t is set to a value equal to the _TIME_ERROR constant and the function returns a positive nonzero value.

    If the calendar object represents a date that cannot be represented as a valid time_t value, the system time pointed to by t is not modified and a negative value is returned.

    If the calendar object does not represent a valid date within the calendric system utilized by the calendar object, or if the members of the calendar object have values outside their normal ranges, the behavior is undefined.

    If the cal_type member has a value that is not supported by the implementation, the function fails and returns a negative value.

    Rationale/Discussion

    This function is similar to the mktime() function, converting a calendar object into a time_t value. However, it also handles erroneous time values.

    It is entirely possible that some broken-down calendar date values are not representable as valid time_t values because they fall outside the range of system time values supported by the implementation. In such cases, an error is returned.

    POSIX systems having a 32-bit signed integer time_t type are (currently) not capable of representing system times before 1970-01-01 or after 2038-01-18, and thus will not be able to convert calendar dates outside of this range into valid time_t system time values.

    Example

    The following code fragment converts the current system into a broken-down calendar date and then back into a system time, and then determines the difference between the before and after values.

        #include <stdio.h>
        #include <time.h>
    
        void convert_and_compare(void)
        {
            time_t              now;
            time_t              then;
            struct _calendar    cal;
    
            // Get the current system time
            time(&now);
    
            // Convert the time into a calendar date
            _getcalendar(&cal, "", NULL);
            _setcalendartime(&cal, now);
    
            // Convert the date back into a system time
            _getcalendartime(&cal, &then);
    
            // Display the difference between the two system times
            printf("Difference: %f sec\n", difftime(then, now));
        }
    


    6.10. The _getcalendartype() function

    Synopsis

        #include <time.h>
        const struct _calendarinfo * _getcalendartype(int type);
    

    Description

    Retrieves a calendar information object corresponding to the calendric system of a type equal to type. The calendar information object contains information about the supported ranges of values for the members of the _calendar structure.

    The type argument specifies the numeric type (or index) of a calendric system. The value of such a number is implementation-defined. All conforming implementations, however, must support at least one calendric system with a type number equal to the _CAL_TYPE_GREGORIAN constant.

    Returns

    Returns a pointer to a (static) calendar information object corresponding to the specified calendric system type, or null if no matching calendric information object is supported by the implementation.

    Rationale/Discussion

    This function is similar to the _getcalendarinfo() function, except that it retrieves calendric system information by type number instead of by name.


    6.11. The _gettimezone() function

    Synopsis

        #include <time.h>
        int _gettimezone(struct _timezone *tz, const char *tzname);
    

    Description

    Initializes the timezone object pointed to by tz, filling it with information corresponding to a timezone with a name matching the string pointed to by tzname.

    Conforming implementations are required to support, at a minimum, timezone objects corresponding to the _TZ_UTC constant and the _TZ_LOCAL constant. The former constant specifies the Coordinated Universal Time (UTC) timezone, and the latter specifies the local timezone as determined by the implementation.

    Conforming implementations are also required to support, at a minimum, timezone objects corresponding to the names defined by the following syntax:

        timezone-name:
            [+|-] [h]h [ [:] mm [ [:] ss ] ]
    

    Implementations must recognize timezone names "-2400" through "+2400" (and their variant spellings), representing 49 distinct timezone offsets.

    For example, all of the following timezone names specify a timezone that has a positive five hour offset from UTC:

        5         +5
        05        +05
        500       +500      5:00      +5:00
        0500      +0500     05:00     +05:00
        50000     +50000    5:00:00   +5:00:00
        050000    +050000   05:00:00  +05:00:00
    

    Implementations are free to recognize other timezone names in addition to these required names.

    Other than the required timezone names just mentioned, the method by which timezone names correspond to timezone objects is implementation-defined.

    Returns

    The function returns zero if successful. If no timezones with names corresponding to the string pointed to by tzname are supported by the implementation, the timezone object pointed to by tz is not modified and the function returns a nonzero value.

    Rationale/Discussion

    Timezone names are implementation-specific, although it is hoped that some common naming convention can be achieved. A recommended scheme is the one used by POSIX implementations (e.g., with names like "EST5EDT" to represent "Eastern Standard Time" with a 5 hour offset west of UTC and "Eastern Daylight Time" with a DST adjustment of one hour).

    It is intended that the following function call will work as expected on POSIX compliant systems:

        _gettimezone(&tz, getenv("TZ"));
    

    Timezone naming schemes can be quite complicated. For example, POSIX uses the "TZ" environment variable to specify the local timezone for each user, and employs a fairly complicated syntax, allowing names such as "CST+6CDT,M4.1.0/2,M10.5.0/2", which specifies a DST/non-DST pair of timezone names and UTC offsets as well as the times of the year when the DST changes occur. It is presumed that the _gettimezone() function on POSIX systems will be quite capable of handling such strings, just as the POSIX tzset() function currently does.

    The allocation and deallocation of timezone objects is completely controlled by the programmer. There are thus no requirements for the standard library to call malloc() or free().

    Intimately related to timezones is the application of Daylight Saving Time rules. Timezones thus come in two flavors, those with DST adjustments and those without, which presumably is reflected in their names (e.g., "EST" and "EDT"). Specific DST adjustments are reflected by the value of the cal_dst member of individual calendar date objects.

    Requiring a small set of well-defined timezone names allows portable conforming programs to be written that adjust for the 24 major timezones of the world.

    Example

    The following code fragment initializes a timezone object for a timezone with a positive five hour offset from UTC.

        const struct _timezone      tz;
    
        // Retrieve a timezone for UTC plus 5 hours
        _gettimezone(&tz, "+0500");
    


    6.12. The _gettimezoneoffset() function

    Synopsis

        #include <time.h>
        long _gettimezoneoffset(const struct _calendar *cal);
    

    Description

    Determines the number of milliseconds that the time represented by the calendar object pointed to by cal differs from UTC time.

    The difference is the number of milliseconds that must be added to UTC time to be equal to the calendar object's time setting. For example, a time of 09:00:00 US Central Standard Time (CST) is 6 hours behind the same UTC time of 15:00:00, for a difference of -21,600,000 milliseconds (-6 × 60 × 60 seconds).

    Returns

    The number of milliseconds difference from UTC represented by the particular date of the calendar object and its associated timezone and Daylight Saving Time settings.

    If cal is null, or if the combination of the calendar object's timezone and DST adjustment is invalid, the function returns a value equal to the _TZ_ERROR constant.

    Rationale/Discussion

    This function returns milliseconds offset from UTC because of the complex nature of some historical timezone/DST combinations, some of which differed from UTC time by intervals as small as 0.13 seconds. It is felt that millisecond precision should be sufficient to properly represent most historical timzone offsets.

    Example

    The following code fragment determines the number of minutes that a calendar date differs from UTC time.

        int     tz_min;
    
        // Determine the timezone+DST offset in minutes
        tz_min = (int)_gettimezoneoffset(cal)/1000/60;
    


    6.13. The _mktimezonename() function

    Synopsis

        #include <time.h>
        int _mktimezonename(char *buf, size_t max, const struct _timezone *tz);
    

    Description

    Constructs the name of a timezone corresponding to a timezone object. Such a name is suitable for use as the tzname argument of the _gettimezone() function.

    The character array pointed to by buf is filled with up to max characters (including a terminating null '\0' character) of the resulting timezone name.

    Returns

    The number of characters written into the array pointed to by buf on success (which will not be greater than max), or a negative value if an error occurs.

    If the resulting timezone name would contain more than max characters (including the terminating null '\0' character), the function fails and returns a negative value, the absolute value of which indicates the number of characters required to hold the resulting name.

    Example

    The following function prints the timezone name for a given calendar date object.

        #include <time.h>
        #include <stdio.h>
    
        void print_tzname(const struct _calendar *cal)
        {
            int     len;
    
            // Check for the presence of a timezone setting
            if (cal->cal_zone == NULL)
            {
                printf("none\n");
                return;
            }
    
            // Determine the length of the timezone name
            len = _mktimezonename(NULL, 0, cal->cal_zone);
    
            // Construct a timezone name from the date's timezone(s)
            {
                char    buf[len];
    
                // Retrieve and print the date's timezone name
                if (_mktimezonename(buf, len, cal->cal_zone) < 0)
                    printf("unknown\n");
                else
                    printf("%s\n", buf);
            }
        }
    


    6.14. The _normalizecalendar() function

    Synopsis

        #include <time.h>
        int _normalizecalendar(struct _calendar *cal);
    

    Description

    Normalizes the members of the calendar object pointed to by cal by modifying their values to fall within their normal ranges, while maintaining the same date value represented by the calendar object (if possible). The values of the members of the calendar object are not constrained to fall within their normal ranges upon entry to the function, but will be so constrained upon returning from the function.

    During the normalization process, the following members of the calendar object are modified. The existing values of those marked "ignored" are ignored for the purposes of calculating the adjusted member values. Except where specifically noted, the order in which the members are normalized is implementation-defined.

    cal_era

    cal_year

    cal_mon

    cal_mday

    cal_week  (ignored)
    This member is modified only after the value of the cal_yday member has been normalized.

    cal_yday  (ignored)
    This member is modified only after the values of the cal_mon and cal_mday members have been normalized.

    cal_wday  (ignored)
    This member is modified only after the value of the cal_yday member has been normalized.

    cal_hour

    cal_min

    cal_sec

    cal_nsec

    cal_dst  (ignored)
    This member is modified only after the values of the cal_year and cal_yday members have been normalized.

    If an error occurs, all of the members listed above except for cal_year are normalized to zero.

    An error occurs if the resulting member values do not represent a valid date within the calendric system utilized by the calendar object.

    Returns

    The function returns zero if the calendar object represents a valid calendar date and the members of which (except those marked "ignored" above) do not change values during normalization.

    The function returns a positive nonzero value if the resulting normalized calendar object represents a valid calendar date and one or more members (except those marked "ignored" above) changed value during normalization.

    If the calendar object cannot be normalized to a valid calendar date value, or if the cal_year member of the calendar object is equal to the _CAL_YR_ERROR constant (indicating that the calendar date is erroneous), the function fails and returns a negative value.

    If the cal_type member has a value that is not supported by the implementation, the function fails and returns a negative value.

    Rationale/Discussion

    This function is similar to the mktime() function, normalizing the values of the members of the calendar object. However, it also handles erroneous time values.

    Example

    The following code fragment determines if a particular <year,mon,mday> tuple constitutes a valid (Gregorian) calendar date combination.

        #include <stdbool.h>
        #include <time.h>
    
        bool check_date(int yr, int mon, int day)
        {
            struct _calendar    cal;
    
            // Set the date of a new calendar object
            _getcalendar(&cal, _CAL_NAME_GREGORIAN, NULL);
            cal.cal_year = yr;
            cal.cal_mon =  mon;
            cal.cal_mday = day;
    
            // Verify that the date is already normalized
            return (_normalizecalendar(&cal) == 0);
        }
    


    6.15. The _setcalendartime() function

    Synopsis

        #include <time.h>
        int _setcalendartime(struct _calendar *cal, time_t t);
    

    Description

    Converts the date represented by value t into a broken-down date and time, modifying the members of the calendar object pointed to by cal to correspond to date t.

    An error occurs if t is equal to the _TIME_ERROR constant, and the members of the calendar object are set to zero, except for the cal_type member, which remains unchanged, and the cal_year member which is set to a value equal to the _CAL_YR_ERROR constant.

    An error occurs if t is not a valid system time value and is not equal to the _TIME_ERROR constant, or if t cannot be converted into a valid calendar date value. In such cases, the cal_year member is set to a value equal to the _CAL_YR_ERROR constant.

    It is implementation-defined whether the cal_nsec member of the calendar object will be set to a zero or nonzero value.

    If the cal_type member has a value that is not supported by the implementation, the function fails and returns a nonzero value.

    Returns

    Returns zero on success, or a nonzero value on error.

    Rationale/Discussion

    This function operates similarly to the gmtime() and localtime() functions, converting a time_t value into a calendar date. However, it performs the date conversion with respect to the timezone setting specified by the value of the cal_zone member. It also handles erroneous time values.

    If there is no timezone setting for the calendar object, the converted date represents a "bare" date and time irrespective of any timezone, or in other words, a date and time with respect only to the "zero" or "origin" timezone (UTC).

    Implementations that are not capable of discerning system time with subsecond precision are expected to set the cal_nsec member of the calendar object to zero.

    It is entirely possible that some time_t values are not representable as a broken-down calendar date because they fall outside the range of calendar dates supported by the implementation. For example, POSIX systems having a 32-bit signed integer time_t type are (currently) not capable of representing system times before 1970-01-01 or after 2038-01-18. In such cases, an error is returned.

    Example

    The following function gets the current time and converts it into a broken-down calendar date object.

        #include <stdlib.h>
        #include <time.h>
    
        struct _calendar * get_date(void)
        {
            struct _timezone *  tz;
            struct _calendar *  cal;
    
            // Allocate and initialize a timezone object
            tz = malloc(sizeof(*tz));
            _gettimezone(tz, _TZ_LOCAL);
    
            // Allocate and initialize a calendar date object
            cal = malloc(sizeof(*cal));
            _getcalendar(cal, "", tz);
    
            // Set the calendar date to the current system time
            _setcalendartime(cal, time(NULL));
            return (cal);
        }
    


    6.16. The _setcalendarzone() function

    Synopsis

        #include <time.h>
        int _setcalendarzone(struct _calendar *cal, const struct _timezone *tz);
    

    Description

    Applies the timezone pointed to by tz to the members of the calendar object pointed to by cal, after first undoing any effects of the timezone previously applied to the calendar object.

    Argument tz points to a _timezone structure. The pointer argument may be null, in which case it is treated as if it pointed to a timezone structure equivalent to the UTC timezone unaffected by Daylight Saving Time.

    The function also sets the cal_zone member of the calendar object to point to the timezone object pointed to by tz.

    The remaining members of the calendar object (including the cal_dst member) are set to their normalized values after being adjusting for the new timezone setting.

    If the cal_type member has a value that is not supported by the implementation, the function fails and returns a nonzero value.

    Returns

    The function returns zero on success, or a nonzero value if an error occurs.

    Rationale/Discussion

    Given a calendar object imbued with a specific date and time, and a timezone object for a specific timezone, that timezone object can be "applied to" the calendar object, which modifies the members of the calendar object in the appropriate fashion so that the resulting member values represent the same date and time but in the given timezone. Thus, applying a timezone to a calendar object is equivalent to shifting the original calendar date value from the UTC timezone into another timezone by adding the timezone offset.

    Once a calendar object has had a timezone applied to it, it remembers that particular timezone setting (by setting its cal_zone member).

    Multiple calendar objects can share (point to) the same timezone object, since the cal_zone member points to a const timezone object.

    The effects of applying a timezone to a calendar object can also be undone by calling the _setcalendarzone() function on the object, applying a different (or no) timezone. This is equivalent to shifting a calendar date value from one particular timezone to another.

    Applying a null timezone setting to a calendar object essentially removes all effects of any timezone, thus making the calendar date relative to no specific timezone (making it a date relative to UTC).


    Ah, but my calculations, people say,
    Have squared the year to human Compass, eh?
    If so, by striking from the calendar
    Unborn tomorrow and dead yesterday.                                                       

    — Omar Khayyám, The Rubáiyát, ca. 1100.

    7. Examples

    The following code examples illustrate the use of the various proposed calendar date functions.

    Example 1

    The following function determines the current date and time, converts it into a broken-down calendar date, formats it as a printable string, and writes it to the standard output.

        #include <stdio.h>
        #include <time.h>
    
        void print_time(void)
        {
            struct _timezone    tz;
            struct _calendar    cal;
            char                buf[80];
    
            // Get a timezone for the locale and local timezone
            _gettimezone(&tz, _TZ_LOCAL);
    
            // Get a calendar object for the locale and local timezone
            _getcalendar(&cal, _CAL_NAME_GREGORIAN, &tz);
    
            // Set the calendar date to the current system time
            _setcalendartime(&cal, time(NULL));
    
            // Format the calendar date for printing
            _calendarformat(buf, sizeof(buf),
                "%a %Y-%m-%d %H:%M:%S.%3s %Za", &cal);
    
            // Display the current date and time
            printf("%s\n", buf);
        }
    

    Example output from this function:

        Thu 2002-12-13 09:45:30.166 CST
    

    The function above is almost equivalent to the following function, which uses the existing C99 tm structure functions.

        #include <stdio.h>
        #include <time.h>
    
        void print_time2(void)
        {
            struct tm   ts;
            time_t      now;
            char        buf[80];
    
            // Get the current time
            time(&now);
    
            // Convert the current time to a broken-down time
            ts = *localtime(&now);
    
            // Format the broken-down time for printing
            strftime(buf, sizeof(buf),
                "%a %Y-%m-%d %H:%M:%S.000 %Z", &ts);
    
            // Display the current date and time
            printf("%s\n", buf);
        }
    


    Example 2

    The following function changes the timezone setting of a calendar date from its original timezone to a different one.

        #include <stdbool.h>
        #include <stdlib.h>
        #include <time.h>
    
        bool change_tz(struct _calendar *cal, const char *tzname)
        {
            struct _timezone *  tz;
    
            // Allocate and initialize a new timezone object
            tz = malloc(sizeof(*tz));
            if (_gettimezone(tz, tzname) != 0)
            {
                free(tz);
                return (false);
            }
    
            // Change the timezone settings of the calendar date
            return (_setcalendarzone(cal, tz) == 0);
        }
    


    Example 3

    The following function creates a broken-down date of 2001-09-11 08:50:00 EDT (where EDT is Eastern Daylight Time, 5 hours west of UTC), then adds 60 days to it.

        #include <stdio.h>
        #include <time.h>
    
        void add_60_days()
        {
            struct _timezone    tz;
            struct _calendar    cal;
            char                buf[80];
    
            // Build a date of 2001-09-11 08:50:00 EDT
            _gettimezone(&tz, "EST5EDT");
            _getcalendar(&cal, _CAL_NAME_GREGORIAN, &tz);
            cal.cal_year = 2001;
            cal.cal_mon =  9;
            cal.cal_mday = 11;
            cal.cal_hour = 8;
            cal.cal_min =  50;
    
            // Format and print the date
            _calendarformat(buf, sizeof(buf),
                "%a %Y-%b-%d %H:%M %Za", &cal);
            printf("%s\n", buf);
    
            // Add 60 days to the date and renormalize it
            cal.cal_mday += 60;
            _getcalendartime(&cal, NULL);
    
            // Format and print the new date
            _calendarformat(buf, sizeof(buf),
                "%a %Y-%b-%d %H:%M %Za", &cal);
            printf("%s\n", buf);
        }
    

    Adding 60 days to the original date results in a new date that is no longer in Daylight Saving Time, so the timezone changes from EDT to EST. The output from this function is:

        Tue 2001-Sep-11 08:50 EDT
        Sat 2001-Nov-10 07:50 EST
    


    Example 4

    The following function determines on what day of the week the date 2001-07-04 occurs.

        #include <stdio.h>
        #include <time.h>
    
        void weekday2001(void)
        {
            struct _calendar    cal;
            char                buf[80];
    
            // Build a date of 2001-07-04 12:00:00 UTC
            _getcalendar(&cal, _CAL_NAME_GREGORIAN, NULL);
            cal.cal_year = 2001;
            cal.cal_mon =  7;
            cal.cal_mday = 4;
            cal.cal_hour = 12;
    
            // Determine the weekday by normalizing the date
            _getcalendartime(&cal, NULL);
    
            // Print the resulting day of the week
            _calendarformat(buf, sizeof(buf), "%a", &cal);
            printf("%s\n", buf);
        }
    

    The output from this function is:

        Wed
    


    Example 5

    The following function converts broken-down calendar dates from structure type tm to structure type _calendar.

        #include <time.h>
    
        // Convert a 'tm' struct into a '_calendar' struct
        void tm_to_calendar(struct _calendar *cal, const struct tm *ts)
        {
            int     yr;
            int     wday;
            int     dst;
    
            // Initialize the calendar object
            _getcalendar(cal, _CAL_NAME_GREGORIAN, NULL);
    
            // Set the calendar members
            yr =                ts->tm_year + 1900;
            cal->cal_year =     yr;
            cal->cal_mon =      ts->tm_mon + 1;
            cal->cal_mday =     ts->tm_mday;
            cal->cal_yday =     ts->tm_yday + 1;
            wday =              (ts->tm_wday != 0 ? ts->tm_wday : 7);
            cal->cal_wday =     wday;
            cal->cal_week =     (ts->tm_yday - wday + 7 + 4)/7;
            cal->cal_hour =     ts->tm_hour;
            cal->cal_min =      ts->tm_min;
            cal->cal_sec =      ts->tm_sec;
            dst =               ts->tm_isdst;
            cal->cal_dst =      (dst <= 0 ? 0 : 1);
            cal->cal_era =      (cal->cal_year > 0 ? _CAL_ERA_COMMON : 1);
            cal->cal_nsec =     0;                  // Unknown
            cal->cal_zone =     NULL;               // Unknown
        }
    

    The following function converts broken-down calendar dates from structure type _calendar to structure type tm.

        #include <time.h>
    
        // Convert a '_calendar' struct into a 'tm' struct
        int calendar_to_tm(struct tm *ts, const struct _calendar *cal)
        {
            // Validate the calendar date object
            if (cal->cal_type != _CAL_TYPE_GREGORIAN)
                return (-1);
    
            // Check for special dates
            if (cal->cal_year == _CAL_YR_ERROR)
                return (-1);
    
            // Set the tm members
            ts->tm_year =   cal->cal_year - 1900;
            ts->tm_mon =    cal->cal_mon - 1;
            ts->tm_yday =   cal->cal_yday - 1;
            ts->tm_wday =   cal->cal_wday % 7;
            ts->tm_mday =   cal->cal_mday;
            ts->tm_hour =   cal->cal_hour;
            ts->tm_min =    cal->cal_min;
            ts->tm_sec =    cal->cal_sec;
            ts->tm_isdst =  cal->cal_dst;
            return (0);
        }
    


    The calendar is intolerable to all wisdom, the horror of all astronomy, and a laughing-stock from a mathematician's point of view.

    — Roger Bacon, 1267

    8. Further Discussion

    [<time.h>]
    It has been suggested that the addition of any new date/time handling functions to the ISO C standard library should require the invention of a new standard header file to contain the new definitions (e.g., <calendar.h>). However, it is felt that it makes more sense to add such functions to the standard header file that already exists for the definitions of date/time handling functions, i.e., <time.h>.

    [_calendar]
    It was considered giving the _calendar structure a different name, such as 'tmx', 'tm2', or '_date'. The actual name presented in this proposal is unimportant, since it is a fairly arbitrary choice that can be made by the ISO committee at a later time.

    [namespace]
    All of the identifiers (structure type names, function names, preprocessor macro names, etc.) presented in this proposal begin with an underscore in order to reduce the pollution of the user namespace (since such names are reserved for the implementation). The function names and structure tags all begin with an underscore and contain the word "calendar" or "timezone". This choice of names is arbitrary and can be decided by the ISO committee.

    [cal_vers]
    A previous revision of this proposal defined xx_vers members in each of the proposed structures, which specified the revision number of each structure type. This was supposed to have made it easier to make changes to these structures in future revisions of the standard library, with an eye towards providing binary backward compatibility.

    However, it was felt that these members were a bit overdesigned, having no basis in prior art in the existing ISO C standard, and they were subsequently removed.

    [cal_type]
    It was considered making the cal_type member be a pointer to a _calendarinfo structure instead of an integer type number. However, this scheme has the disadvantage of making it more likely that a program execution error could occur if that member has been corrupted. (Checking for a null value is easy, but checking for a non-null but erroneous pointer value is not a simple matter.)

    While using a pointer would have made it somewhat easier to access the calendric information for a calendar object, it was felt that the use of a simple integer value was safer, and that it was not really all that difficult to use the integer value to retrieve the calendric information (using the _getcalendartype() function).

    [_calendarformat()]
    The eventual names of the _calendarformat() function and the _calendarscanf() function should probably be chosen to reflect their inverse relationship with each other. This is similar to the existing pairing of names for the printf()/scanf() and strftime()/strptime() functions.

    [_gettimezonelist()]
    Previous revisions of this proposal included a _gettimezonelist() function which returned a list of timezone objects representing the timezones supported by the implementation. This function was removed because it was felt that it might be impossible for some implementations to provide a complete list of supported timezones, due to the fact that timezone naming schemes can be quite complicated and produce a large universe of possible names.

    [_calendardiff()]
    The current definition of the _calendardiff() function is deficient in that there is no way to determine the difference between two calendar dates in terms of total whole days (or months, weeks, or seconds) if the difference spans more than one year. One approach is to modify the function definition so that it returns the total number of whole days difference as a signed long int value. The difference in terms of total weeks and seconds could then be easily computed (assuming a calendric system employing linear fixed-sized days and weeks); the difference in total months, however, would still be unobtainable.

    [DST]
    There is not yet a satisfactory method for applying or undoing the effects of Daylight Saving Time on a given calendar date.

    [cal_era]
    It was considered making the cal_era member be a pointer to a constant static string, such as "AD" or "BCE". However, it was felt that it would be more useful and less complicated for the implementation if the member was simply an integer.

    [short]
    It is being considered making the members of the _calendar structure that are type int (with the possible exception of the cal_year member) be defined as type short instead, in order to reduce the total size of the structure. The complicating factor here is how to handle member values that exceed their normal ranges, such as the values they might acquire immediately prior to being normalized by a call to the _normalizecalendar() function.

    [int32_t]
    It was considered defining the various structure members to be type int16_t and int32_t. However, this would complicate the use of the members because it implies the use of the <inttypes.h> header, and it adds the need for explicit casts in many circumstances. Consider how it would complicate the following calls, for example:
        sscanf(buf, "%d %d %d", &c.cal_year, &c.cal_mon, &c.cal_mday);
        printf("%04d-%02d-%02d", c.cal_year, c.cal_mon, c.cal_mday);
    

    [Z]
    A "zero" timezone matching the name "UTC" must be supported by conforming implementations. Should the name "Z" also be provided as a synonym for this timezone?

    [Emacs]
    (Prior art)
    The Emacs format-time-string function provides the same format specifiers as the strftime() function, but also adds a "%s" specifier which is replaced by the number of seconds since 1970-01-01 00:00:00 Z. This conflicts with the "%s" subsecond format specifier of the _calendarformat() function in this proposal; a better choice for Emacs would have been "%N".


    Source Code

    The following source files provide a proof-of-concept implementation of this proposal.

    c0xcalendar.h  Header file
    c0xcalendar.c  Source file - calendar functions
    c0xtimezone.c  Source file - timezone functions
    c0xcaltest.c  Source file - test driver


    Day, n.
    A period of twenty-four hours, mostly misspent. This period is divided into two parts, the day proper and the night, or day improper — the former devoted to sins of business, the latter consecrated to the other sort. These two kinds of social activity overlap.

    — Ambrose Bierce, The Devil's Dictionary, 1911.

    Related Proposals

    [P1]  Additional Constraints on time_t
    Proposal for ISO C0X.
    David R. Tribble <david@tribble.com>, Feb 2001.
    http://david.tribble.com/text/c0xtimet.htm.

    [P2]  Proposal for an ISO C and C++ Extended-Range Time Type
    David R. Tribble <david@tribble.com>, Sep 1999 (abandoned).
    http://david.tribble.com/text/c0xtime.htm.

    [P3]  Proposal for a Smoothed Coordinated Universal Time (UTS)
    Markus Kuhn <mkuhn@acm.org>, Oct 2000.
    http://www.cl.cam.ac.uk/~mgk25/uts.txt.

    [P4]  Proposed new <time.h> for ISO C 200X
    Markus Kuhn <Markus.Kuhn@cl.cam.ac.uk>, Sep 1998.
    http://www.cl.cam.ac.uk/~mgk25/c-time.

    [P5]  Modernized <time.h> API for ISO C
    Paul Eggert <eggert@twinsun.com>, Jan 2000.
    http://www.twinsun.com/tz/timeapi.html.

    [P6]  A proposal for thread-safe time zone information
    Jonathan Lennox <lennox@cs.columbia.edu>, Columbia University, Jun 2001 (Version 2).
    http://www.cl.cam.ac.uk/~mgk25/c-time/proposal-lennox.txt.


    References

    [1]  ISO 8601:2000(E)
    Data elements and interchange formats - Information interchange
    - Representation of dates and times
    Second edition, Dec 2000, ISO/IEC, TC 154.
    Available at http://www.iso.org.
    A draft version in PDF format is at http://xml.coverpages.org/ISO-FDIS-8601.pdf
    (see also http://xml.coverpages.org/related.html#iso8601).

    [2]  ISO/IEC 9899:1999(E)
    Programming Language C
    Second edition, Dec 1999, ISO/IEC.
    Available at http://www.ansi.org.

    [3]  ISO/IEC 9899:1999
    Programming Language C
    Committee Draft, Aug 1998, ISO/IEC N843 (a.k.a. document n2794).
    Available at http://www.dkuug.dk/JTC1/SC22/WG14/www/docs;
    HTML: http://std.dkuug.dk/JTC1/SC22/WG14/www/docs/n843.htm,
    PDF: http://std.dkuug.dk/JTC1/SC22/WG14/www/docs/n843.pdf.gz,
    Postscript: http://std.dkuug.dk/JTC1/SC22/WG14/www/docs/n843.ps.gz.

    [4]  ISO/IEC 14882:1998(E)
    Programming Language C++
    First edition, Sep 1998, ISO/IEC.
    Available at http://www.ansi.org.

    [5]  Java API Reference
    Class java.util.Calendar,
    Class java.util.TimeZone
    Java 2 Standard Edition (SE) 1.4,
    Sun Microsystems, Inc., 2001.
    http://java.sun.com/j2se/1.4/docs/api/java/util/Calendar.html,
    http://java.sun.com/j2se/1.4/docs/api/java/util/TimeZone.html.

    [6]  The Calendar
    The 5000-Year Struggle to Align the Clock and the Heavens
    - and What Happened to the Missing Ten Days
    David Ewing Duncan, 1998.
    Fourth Estate Ltd., London, ISBN 1-85702-979-8.
    Search http://www.amazon.com.

    [7]  Systems of Time
    U.S. Naval Observatory - Time Service Department, Jun 1998.
    http://tycho.usno.navy.mil/systime.html.

    [8]  Leap Seconds
    U.S. Naval Observatory - Time Service Department, May 1999.
    http://tycho.usno.navy.mil/leapsec.html.

    [9]  A Summary of the International Standard Date and Time Notation
    Markus Kuhn <Markus.Kuhn@cl.cam.ac.uk>, May 1996.
    http://www.cl.cam.ac.uk/~mgk25/iso-time.html.

    [10]  The Best of Dates, The Worst Of Dates
    Gilbert Healton <ghealton@exit109.com>, 2002-02-15
    http://www.exit109.com/~ghealton/y2k/yrexamples.html.

    [11]  Year Date and Time Calculation Index - How people and computers use dates and times
    Gilbert Healton <ghealton@exit109.com>.
    http://www.exit109.com/~ghealton/y2k/.

    [12]  When Should We Introduce Leap Second in UTC?
    International Earth Rotation Service (IERS) - Earth Orientation Parameters (EOP), Nov 2001.
    http://hpiers.obspm.fr/eop-pc/earthor/utc/leapsecond.html.

    [13]  Technical Recommendation (Annex) ITU-R TF.460-5
    Recommendation regarding the application of leap seconds to UTC time
    International Telecommunication Union.
    http://www.itu.int.

    [14]  Specifying the Time Zone with TZ
    POSIX-compatible GNU C runtime library
    GNU - Free Software Foundation, May 2001.
    http://www.gnu.org/manual/glibc-2.2.3/html_node/libc_431.html.

    [15]  Global Data and the TZ Environment Variable
    QNX 6.1 C/C++ Runtime Library Documentation.
    http://qdn.qnx.com/support/docs/neutrino/lib_ref/global.html#TheTZEnvironmentVariable.

    [16]  Date and Time Gateway
    Lists the ISO/IEC 9945-1 ANSI/IEEE Std 1003.1 (aka POSIX.1) timezone names.
    http://www.bsdi.com/date.

    [17]  Daylight Saving Time
    The history of daylight saving, from Benjamin Franklin to the present.
    http://webexhibits.org/daylightsaving.

    [18]  Calendars from the Sky
    Institute for Dynamic Educational Advancement (IDEA), by WebExhibits.
    http://webexhibits.org/calendars.

    [19]  ISO 8601 references
    DMOZ, The Open Directory Project.
    http://dmoz.org/Science/Reference/Standards/Individual_Standards/ISO_8601.

    [20]  Frequently Asked Questions about Calendars
    Claus Třndering <claus@tondering.dk>, Oct 2001.
    http://www.tondering.dk/claus/calendar.html.


    Acknowledgments

    My thanks to the people who gave helpful comments on early drafts of this document, especially to the following individuals who emailed me suggestions and corrections or posted comments on the comp.std.c newsgroup:


    Revision History

    1.6, 2002-06-10.
    Added the _calendardelta structure.
    +INCOMPLETE.

    1.5, 2002-04-20.
    Made minor HTML fixes.
    Restricted the value of the cal_dst member to  0 or 1 only.
    Clarified the relationship between the cal_year member and the cal_era member.
    Added the _addcalendarnsecs function. Added the _calendardelta structure.

    1.4, 2002-04-07.
    Made the contents of the _timezone structure completely opaque (implementation-defined).
    Made the cal_zone member a single pointer.
    Added the tz parameter to the _calendarscanf() function.
    Added the _normalizecalendar() function.
    Added the _gettimezoneoffset() function.
    Added the _TZ_ERROR constant.
    Modified the parameters and return values of the _getcalendartime() function.
    Removed the _checkcalendar() function.

    1.3, 2002-03-19.
    Removed the __STDC_CALENDAR_VERSION__ macro.
    Removed the _TIME_MIN and _TIME_MAX constants.
    Removed the _gettimezonelist() function.
    Removed the cal_leap_yr member.
    Removed Example 7, function print_diff(), which exhibited the use of the _calendardiff() function.
    Added the _mktimezonename() function.
    Added the _convertcalendar() function.
    Added the ci_first_time member and the ci_last_time member.
    Added the ci_era_min member.
    Added the minimum set of required timezone names recognized by the _getcalendar() function and the _gettimezone() function.
    Added the _CAL_ERA_COMMON constant.
    Added the "%Za" and "%Zn" format specifiers for the _calendarformat() function and the _calendarscanf() function.
    Renamed the cal_isdst member to cal_dst.
    Replaced the tz_min_west member with the tz_sec_off member.
    Modified the description of the _getcalendar() function and the _getcalendarinfo() function when passed an empty string for the calendric system name argument.
    Improved the description of the _calendarscanf() function. Also changed the order of its parameters to be compatible with the strptime() POSIX function.
    Improved the return values for the _checkcalendar() function.
    Improved the definition of the cal_week member.
    Improved the definition of the tz_dst member.
    Added section numbers.

    1.2, 2002-02-18.
    Changed the tz_sec_west member to tz_min_west.
    Wrote a better definition of the _calendardiff() function.
    Replaced the use of the "GMT" timezone with the "UTC" timezone. Also renamed the _TZ_GMT constant to _TZ_UTC.
    Improved the _gettimezonelist() function.
    Added "Synopsis", "Description", "Returns", and "Example" sections to each function description.
    Added example code to most of the function descriptions.
    Added the "Related Proposals" section.
    Removed the "Other Relevant References" section, merging it into the "References" section.
    Removed all of the structure version members and constants (cal_vers, ci_vers, tz_vers, _CAL_VERS, _CAL_INFO_VERS, _TZ_VERS).

    1.1, 2002-01-28.
    Added the "%f" (era) format specifier in function _calendarformat().

    1.0, 2002-01-24.
    Removed all UNKNOWN and NEVER constants. This simplifies calendar dates, making them either valid or erroneous.
    Removed all OKAY and ERROR constants, since they are not really needed (zero and nonzero values are sufficient).
    Added the ci_wday_1st member of the _calendarinfo structure.
    Removed the _DST_XXX constants.
    Renamed the cal_dst member to cal_isdst.
    Reordered the parameters of the _calendarformat() function to be compatible with strftime().

    0.7, 2002-01-10.
    Added more refinements.
    Minimum range for the cal_sec member is [0,59], i.e., leap seconds are not required.
    Better descriptions for some of the constants.
    Added equivalent code for the _setcalendarzone() function.
    Added a few more topics in the Further Discussion section.

    0.6, 2002-01-07.
    Added ci_xxx_min members to the _calendarinfo structure.

    0.5, 2002-01-01.
    Added remaining descriptions of the ci_xxx members.
    Added more examples.

    0.4, 2001-12-27.
    Added more examples.
    Removed the cal_leap_sec member of the _calendar structure.
    Changed the cal_era member from a pointer to an integer.

    0.3, 2001-12-21.
    Added the _calendarinfo structure and its related functions.
    Removed the cal_name member of the _calendar structure.
    Added the _getcalendartype() function.
    Expanded the Table of Contents.

    0.2, 2001-12-16.
    Added more to the Existing Problems section.
    Added the cal_isdst member.
    Made the cal_zone member a two-element array.
    Combined the _normalizecalendar() function into the _getcalendartime() function.
    Removed the _resetcalendarzone() function.

    0.1, 2001-12-15.
    First draft.


    This document is in the public domain, and is not subject to any copyright restrictions.

    The author can be reached by email at david@tribble.com.
    The author's home web page is at http://david.tribble.com.

    This document: http://david.tribble.com/text/c0xcalendar.html.
    Last modified: Mon Jun 10 22:34:42 CDT 2002