|
|
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 |
|
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 |
| Conversion functions | |||
|---|---|---|---|
| C99 Library | Proposed | ||
| (a) | localtime() | (c) | setcalendartime() |
| gmtime() | mklongtime() | ||
| mktime() | |||
| (d) | calendarformat() | ||
| (b) | strftime() | calendarscanf() | |
| strptime() POSIX, not ISO C | |||
| (e) | inittimezone() | ||
| mktimezonename() | |||
| (f) | longtimetotime() | ||
| timetolongtime() | |||
[Note]
A newly invented standard header file is proposed in order to minimize the impact that these new names will have on existing code, in contrast to proposing that the new names be added to the existing <time.h> header. The choice of the name of the new header reflects its contents, namely, standard functions dealing with time.
|
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 |
_CAL_ERA_COMMON
_CAL_NAME_GREGORIAN
_CAL_TYPE_GREGORIAN
_CAL_YR_ERROR
Each constant is described in detail below.
Unless specifically stated otherwise, these constants do not necessarily have
to be implemented as preprocessor macros.
#include <stdtime.h>
#define _CAL_ERA_COMMON integer-expression
#include <stdtime.h>
#define _CAL_NAME_GREGORIAN "Gregorian"
#include <stdtime.h>
#define _CAL_TYPE_GREGORIAN integer-expression
#include <stdtime.h>
#define _CAL_YR_ERROR integer-expression
|
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 |
typedef longtime_t
struct calendar
struct calendarinfo
struct timezone
Each type is described in detail below.
#include <stdtime.h>
typedef integer-type longtime_t;
#include <stdtime.h>
struct calendar;
[Notes]The calendar structure contains the following members, in any order. These members together represent a date and time within a particular calendric system. 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. The value ranges shown for each member are the minimum conforming ranges for the calendar type named "Gregorian", but do not necessarily apply to other calendar types.
This structure is derived from the existing tm structure, with added enhancements for better handling of timezones, multiple calendric systems, and subsecond time resolution.
int cal_type; // Calendar type
int cal_era; // Era number
int cal_year; // Year number [1601,2400]
int cal_mon; // Month of the year [1,12]
int cal_week; // Week of the year [1,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,60]
long int cal_nsec; // Nanosecond [0,999999999]
int cal_dsti; // Daylight Saving Time index
int cal_leapsec; // Accumulated leap seconds
const struct timezone *
cal_zone; // Applied timezone and DST
The calendar structure also contains the following members,
in any order.
These members, together with the members defined above,
represent a delta time, i.e., a componentized difference between
two given date and time values with respect to a particular calendric system.
long int cal_nmons; // Total months difference
long int cal_nweeks; // Total weeks difference
long int cal_ndays; // Total days difference
Additional implementation-defined members may exist within this structure
(but 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.)
Such members must not be pointers to non-const objects.
[Notes]
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. All of the members of pointer type are restricted to being null or pointing to const (presumably static) 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 date objects to be easily allocated statically, on the stack, or on the heap without any complicated memory allocation constraints. It also simplifies the semantics of copying date objects, passing them as arguments to functions, and returning them from functions.
[Notes]
DST adjustments are always applied in the context of a specific timezone setting. Thus the combination of the cal_zone and the cal_dsti members specifies a particular timezone setting plus a DST adjustment for a given calendar date object. If the cal_zone member is null, then it is assumed that there is no timezone/DST combination applied to the date object.
[Notes]
For the Gregorian calendar, this member specifies which era is represented by the date object, i.e., AD (CE) or BC (BCE). The combination of this member and the cal_year member specifies a specific year within the calendric system employed by the calendar date object. Since conforming implementations are only required to support a Gregorian calendar with year numbers in the range [1601,2400], they are 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 term "cycle" is a synonym for "era".
[Notes]
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, and to make its semantics consistent across different calendric systems. For other calendric systems, it is implementation-defined how intercalary months are distinguished from other months.
[Notes]
This member fills a void in the capabilities of the current tm structure by allowing for time representations with subsecond precision. This draws on prior art from POSIX and BSD Unix systems, which define the timeval and timespec structures and the gettimeofday() system function, which provide system time values with microsecond and nanosecond precisions.
[Notes]
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 date object, and thus what particular date and time calculation rules to apply to that object. It is possible 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, or other dates. 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.
[Notes]
ISO 8601 rules state that weeks begin on Mondays, and that weekday numbers range from 1 to 7. This member is defined differently than the tm_wday member of the tm structure. It is fairly trivial, however, to convert between equivalent structure member values:
t.tm_wday = c.cal_wday % 7; c.cal_wday = (t.tm_wday == 0 ? 7 : t.tm_wday);
[Notes]Examples The following dates are shown with their corresponding ISO 8601 week and weekday designations and their corresponding {cal_week, cal_wday} pair.
The definition of this member corresponds to the semantics of 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.
2000-01-01: 1999-W52-6 {week=52, wday=6}
2001-12-31: 2002-W01-1 {week=1, wday=1}
2003-12-31: 2004-W01-3 {week=1, wday=3}
2005-01-01: 2004-W53-6 {week=53, wday=6}
[Notes]
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, and to make its semantics consistent across different calendric systems.
[Notes]
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. This is intended to simplify its usage and make its semantics consistent across different calendric systems. 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. For the required proleptic Gregorian calendric system, year numbers are constrained to being positive numbers falling within the common (AD/CE) era. It is expected that negative year values will either be normalized to proper year numbers within a different era (BC/BCE), or will simply be treated as erroneous year numbers with the common era. Mandating that conforming implementations must support years in a range of at least AD 1601 to 2400 seems like a reasonable minimum range, which provides for many applications that must deal with old dates (e.g., still-extant mortgage schedules) while also providing for dates spanning the next few centuries. This also parallels the range supported by the related proposed longtime_t type. This range spans the current and the previous 400-year cycles in the Gregorian calendar, from 1601-01-01 to 2001-01-01 to 2401-01-01. It is expected that quality implementations will support a wider range of years than this. Support for (proleptic) dates as far back as 0001-01-01 seems like a reasonable expectation. Since this proposal mandates support of only the proleptic Gregorian calendric system, calendar date values representing times prior to the adoption of the Gregorian calendar (i.e., prior to 1585 or 1782, depending on the region) are not required to reflect actual historical dates.
[Notes]
This member specifies how the members of the date 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 date 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 Daylight Saving Time setting specified by the cal_dsti member to completely specify the offset from UTC represented by the calendar date object. The technique of storing a timezone within each date object allows for the manipulating of many date 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 date 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 date objects to be copied in a straightforward manner.
#include <stdtime.h>
struct calendarinfo;
int ci_type; // Calendar type number
const char * ci_name; // Calendar name
longtime_t ci_time_min; // Earliest time value
longtime_t ci_time_max; // Latest time value
int ci_era_min; // Minimum era number
int ci_era_max; // Maximum era number
int ci_year_min; // Minimum year number (1601)
int ci_year_max; // Maximum year number (2400)
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_wday1; // 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 (1)
The values shown are the minimum values required for the required default
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".
[Notes]
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.
[Notes]
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 [1601,2400]. 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.
[Notes]
Implementations that support multiple eras may support negative era numbers.
[Notes]
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. (See the ci_leap_sec member.)
[Notes]
Note that this value does not necessarily indicate anything about the complete range of valid calendar dates representable by the calendar type.
[Notes]
Note that this value does not necessarily indicate anything about the complete range of valid calendar dates representable by the calendar type.
[Notes]
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 date 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.
[Notes]
ISO 8601 rules state that weeks begin on Mondays, and that weekday numbers range from 1 to 7.
[Notes]
ISO 8601 rules state that weeks begin on Mondays and that weekday numbers range from 1 to 7.
[Notes]
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.
[Notes]
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.
[Notes]
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 longtime_t values.
[Notes]
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.
#include <stdtime.h>
struct timezone;
|
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 |
calendaradd()
calendardiff()
calendarformat()
calendarscanf()
convertcalendar()
getcalendarinfo()
initcalendar()
mklongtime()
setcalendartime()
setcalendarzone()
Each function is described in detail below.
#include <stdtime.h>
int calendaradd(struct calendar *date, const struct calendar *dif, int add);
cal_era // Total eras difference
cal_year // Total years difference
cal_mon // Months difference less than a year
cal_mday // Days difference less than a month
cal_hour // Hours difference less than a day
cal_min // Minutes difference less than an hour
cal_sec // Seconds difference less than a minute
cal_nsec // Nanoseconds difference less than a second
cal_leapsec // Leap seconds difference
The following members of the delta time are also used, depending on the values
of some of the members above:
cal_nmons // Total months difference,
// if cal_mon and cal_mday are both zero
cal_nweeks // Total weeks difference,
// if cal_mon, cal_mday, and cal_nmons are all zero
cal_ndays // Total days difference,
// if cal_mon, cal_mday, cal_nmons, and cal_nweeks are all zero
Other members may be used if the cal_type member designates
a calendric system other than the standard Gregorian calendar.
The remaining members are ignored.
After the calendar date is modified by the delta time,
its members are normalized so that their values fall within their normal ranges.
#include <stdtime.h>
int add_months(struct calendar *date, int months)
{
struct calendar dif;
// Convert months into a delta time
initcalendar(&dif, "");
dif.cal_year = 0;
dif.cal_nmons = months;
// Add the delta time to the given date
return calendaradd(date, &dif, 0);
}
#include <stdtime.h>
int calendardiff(struct calendar *dif,
const struct calendar *a, const struct calendar *b);
[Notes]Arguments a and b point to a calendar date objects. The resulting delta time is the absolute difference between date a and date b. If the cal_type member of either calendar date object does not designate a calendric system supported by the implementation, the function fails. Undefined behavior results if the cal_type members have different values. (This implies that the difference between two calendar dates can be determined portably only if the date objects represent dates within the same calendric system.) Undefined behavior results if the date objects contain unnormalized member values.
While it is fairly easy to determine the difference between two calendar dates as the number of seconds between the two (using the mktime(), mklongtime(), 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 componentized date value. 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 componentized calendar date. 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 total span of 397 (365+31+1) days, while the latter difference represents a total span of 398 (366+31+1) days because 1980 is a leap year. Hence the need for this function.
[Notes]If the cal_year member of either date object has a value equal to _CAL_YR_ERROR, the function fails. Argument dif points to a componentized delta time object, the members of which are modified to represent the absolute (non-negative) difference between the two calendar dates. This argument may be null, in which case the difference is computed but no componentized 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.) The members of the delta time object pointed to by dif are set to values as described below. 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 normal ranges.
The proper way to compare two dates of different calendar types is to normalize and convert one of the dates into a date within the calendric system of the other date, or to convert both dates into dates within a common calendric system (such as the standard proleptic Gregorian calendar), and then to compare the normalized dates. The resulting delta time can then be converted back into one of the two original calendric systems, if so desired. This assumes that the two calendar date objects are representable as valid dates within both calendric systems, or in other words that the two calendric systems span overlapping date ranges. If this is not the case, then dates within the two disparate calendars cannot be compared.
cal_type // Same calendar type as 'a' and 'b'
cal_era // Total eras difference
cal_year // Total years difference
cal_mon // Months difference of less than a year
cal_week // Weeks difference of less than a year
cal_mday // Days difference of less than a month
cal_yday // Days difference of less than a year
cal_wday // Weekdays difference of less than a week
cal_hour // Hours difference of less than a day
cal_min // Minutes difference of less than an hour
cal_sec // Seconds difference of less than a minute
cal_nsec // Nanoseconds difference of less than a second
cal_nmons // Total months difference
cal_nweeks // Total weeks difference
cal_ndays // Total days difference
cal_dsti // Zero
cal_leapsec // Leap seconds difference
cal_zone // Null
The cal_type member of the delta time object is set to the same
value as the cal_type member of either of the calendar date objects.
If the cal_year member
of either of the date objects pointed to by a or b
has a value equal to the _CAL_YR_ERROR constant,
the cal_year member of the
date 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.
#include <limits.h>
#include <stdio.h>
#include <stdtime.h>
void print_diff(const struct calendar *a, const struct calendar *b)
{
struct calendar dif;
int r;
// Compute the difference between the two dates
r = calendardiff(&dif, a, b);
if (r != INT_MIN)
{
// Print the componentized 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);
printf("%ld months, %ld weeks, %ld days total\n",
dif.cal_nmons, dir.cal_nweeks, dif.cal_ndays);
}
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
25 months, 109 weeks, 763 days total
#include <stdtime.h>
int calendarformat(char *buf, size_t max, const char *fmt,
const struct calendar *date, const struct timezone *zone);
[Notes]Pointer buf points to a character array that will be filled with the formatted date. No more than max characters, including a terminating null character, will be written into array buf. Pointer date points to a calendar date object. If the cal_type member of this object does not designate a calendric system supported by the implementation, the function fails. If the cal_year member of this object has a value equal to _CAL_YR_ERROR, the function fails. Pointer zone points to a timezone object. This may be null, in which case no timezone or Daylight Saving Time adjustment is assumed, i.e., the date object is treated as if it represents a time relative to UTC.
This function is equivalent to strftime() except for the calendar structure type and the additional timezone parameter. It also deals with erroneous date values.
[Notes]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:
This parameter typically points to the same object as date->cal_zone, but is not required to.
| %f | is replaced by the nanosecond as a decimal number (000000000–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 occurring to the right of the last digit inserted are truncated, as per the ISO 8601 rules for representating decimal fractions.) [cal_nsec] | |
| %L | is replaced by the accumulated leap seconds adjustments. [cal_leapsec] | |
| %Q | is replaced by the locale's era name or abbreviation. [cal_era] | |
| %Z | is replaced by the name of the time zone as specified by the zone object, or by no characters if zone is null. [cal_zone, cal_dsti] |
[Notes]Some of the format specifiers in the string pointed to by fmt are affected by the current LC_TIME locale setting.
The "%f" 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 [3].) 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. The "%Z" format specifier takes its value from the timezone object instead from (presumably) global data.
#include <stdio.h>
#include <stdtime.h>
void print_date(const struct calendar *date)
{
char buf[80];
// Format the calendar date
calendarformat(buf, sizeof(buf),
"%a %Y-%m-%d %H:%M:%S.%3f %Z", date, date->cal_zone);
// Print the formatted calendar date
printf("%s\n", buf);
}
Example output from this function:
Tue 2001-09-11 08:50:23.166 EDT
#include <stdtime.h>
int calendarscanf(const char *buf, const char *fmt,
struct calendar *date, struct timezone *zone);
[Notes]Argument date points to a calendar date object. If the cal_type member of this object does not designate a calendric system supported by the implementation, the function fails. Argument zone points to a timezone object. This argument may be null, even if timezone format specifiers are present within the format string fmt. 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:
This function is similar to the strptime() function provided by POSIX.
| %? | 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. |
[Notes]The "%n" and "%t" format specifiers match exactly one whitespace character (a newline or a tab character, respectively) in the source string. The timezone format specifiers cause the source string to be scanned for timezone names. Conforming implementations are required to support, at a minimum, the timezone name "Z" designating UTC, names specified by the following syntax:
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.
timezone-offset:
[+|-] [h]h [ mm ]
A timezone name and/or offset scanned from the source string is copied into
the first element of the tz_z member of the timezone object
if zone is not null.
In addition, the cal_dsti member of the date object is
set to zero.
Additional (non-portable) timezone names and syntax may be supported
by the implementation.
[Notes]
The cal_zone and cal_dsti members of the date object are not modified.
#include <stdio.h>
#include <stdtime.h>
void parse_date(const char *buf)
{
struct calendar date;
struct timezone zone;
int rc;
// Parse the formatted date string
rc = calendarscanf(buf,
"%a %Y-%b-%d %2? %H:%M:%S.%3f %Z", &date, &zone);
setcalendarzone(&date, NULL, &zone);
// Print the resulting calendar date
if (rc >= 0)
{
printf("%dwd, %dy %dm %dd, %dh %dm %d.%03lds %s\n",
date.cal_wday,
date.cal_year, date.cal_mon, date.cal_mday,
date.cal_hour, date.cal_min, date.cal_sec,
date.cal_nsec/1000000,
zone.tz_name);
}
else
printf("error\n");
}
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
#include <stdtime.h>
int convertcalendar(struct calendar *dst, const struct calendar *src);
#include <stdtime.h>
const struct calendarinfo * getcalendarinfo(int type);
[Notes]
The structure returned by this function provides information about the valid range of values for various members of the calendar structure, such as the minimum and maximum year number values for valid dates. 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.
#include <stdtime.h>
const struct calendarinfo * info_by_name(const char *name)
{
struct calendar date;
// Initialize a date object for the given calendric system
if (initcalendar(&date, name) < 0)
return NULL;
// Retrieve the named calendric system info
return getcalendarinfo(date.cal_type);
}
#include <stdtime.h>
int initcalendar(struct calendar *date, const char *name);
[Notes]
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 date 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.
#include <stdlib.h>
#include <stdtime.h>
int init_date(struct calendar *date)
{
struct timezone * zone;
// Initialize the calendar date object
if (initcalendar(date, _CAL_NAME_GREGORIAN) != 0)
{
printf("Bad calendar initialization\n");
return -1;
}
// Initialize the local timezone
zone = malloc(sizeof(*zone));
if (inittimezone(zone, "") != 0)
{
free(zone);
printf("Bad timezone initialization\n");
return -1;
}
// Set the timezone of the date object
date->cal_zone = zone;
return 0;
}
The following function duplicates (clones) the contents of a calendar date
object.
#include <stdlib.h>
#include <stdtime.h>
#include <string.h>
struct calendar * clone_date(const struct calendar *date)
{
struct calendar * dup;
// Duplicate the date object
dup = malloc(sizeof(*date));
memcpy(dup, date, sizeof(*date));
return dup;
}
#include <stdtime.h>
longtime_t mklongtime(struct calendar *date, const struct timezone *zone);
[Notes]Argument date points to a calendar date object. The members of the date object are normalized by modifying their values to fall within their normal ranges, while maintaining the same date value represented by the date object (if possible). The values of the members of the date object are not constrained to fall within their normal ranges upon entry to the function, but are so constrained upon returning from the function. The values of the following members of the structure are used to perform the conversion:
This function provides the same functionality for longtime_t and calendar structure values as function mktime() does for time_t and tm structure values. It has a different interface, however. This function makes use of the longtime_t type, which is defined in another related proposal (see Proposal [P2]).
cal_type
cal_era
cal_year
cal_mon
cal_mday
cal_hour
cal_min
cal_sec
cal_nsec
cal_dsti
cal_leapsec
If the cal_type member of the date object designates a
calendric system different from the standard Gregorian calendar,
other members may be used to perform the conversion.
Otherwise, the remaining members of the structure are ignored for the purposes
of conversion.
If the cal_type member of the date object specifies a
calendar type that is not supported by the implementation, the function fails.
If the cal_year member of the date object has a value equal to
_CAL_YR_ERROR (indicating that the calendar date is invalid),
the function fails.
The cal_leapsec member specifies the number of leap seconds to be
included in the resulting converted long time value.
If this member is INT_MIN, the number of accumulated leap seconds
is determined from the long time value after the conversion takes place,
based on historical leap second insertions and deletions.
If the member is zero, no leap seconds are included in the resulting value.
Any other value specifies an exact number of leap seconds to add
to the resulting long time value.
Argument zone points to a timezone object containing timezone and
Daylight Saving Time adjustments that have been made to the date represented
by the calendar date object.
(This is typically equal to the cal_zone member of the
calendar date object, but is not required to be.)
The timezone and DST adjustments are applied in reverse (i.e., undone)
on the calendar date before it is converted into a long time value.
This argument can be null, in which case no timezone or DST adjustments are
applied to the calendar date, i.e., the conversion is done with respect to UTC.
If the normalized calendar date does not represent a meaningful date,
or cannot be converted into a valid long time value after adjustments have
been applied, the function fails.
[Notes]
It is entirely possible that some calendar date values are not representable as valid longtime_t values because they fall outside the range of system time values supported by the implementation. In such cases, an error is returned.
#include <stdio.h>
#include <stdtime.h>
longtime_t date_to_longtime(const struct calendar *date)
{
struct calendar d2;
longtime_t t;
// Print the calendar date
printf("%04d-%02d-%02d %02d:%02d:%02d.%03ld %s",
date->cal_year, date->cal_mon, date->cal_mday,
date->cal_hour, date->cal_min, date->cal_sec,
date->cal_nsec/1000000);
// Convert the calendar date into a long time
d2 = *date;
t = mklongtime(&d2, d2.cal_zone);
printf("-> %lld\n", (long long)t);
return t;
}
The following code fragment determines if a particular
{year,mon,mday} combination constitutes a valid calendar date
within the current locale's calendric system.
#include <stdbool.h>
#include <stdtime.h>
bool check_date(int yr, int mon, int day)
{
struct calendar date;
// Set the date of a new date object
initcalendar(&date, "");
date.cal_year = yr;
date.cal_mon = mon;
date.cal_mday = day;
// Verify that the date is normalized
if (mklongtime(&date, NULL) == _LONGTIME_ERROR)
return false;
if (date.cal_year != yr ||
date.cal_mon != mon ||
date.cal_mday != day)
return false;
return true;
}
The following code fragment converts the current system into a
calendar date and then back into a system time,
and then determines the difference between the before and after values.
#include <stdio.h>
#include <stdtime.h>
void convert_and_compare(void)
{
longtime_t now;
longtime_t then;
struct calendar date;
// Get the current system time
now = getlongtime();
// Convert the time into a calendar date
initcalendar(&date, _CAL_NAME_GREGORIAN);
setcalendartime(&date, NULL, now);
// Convert the date back into a system time
then = mklongtime(&date, NULL);
// Display the difference between the two system times
printf("Difference: %lld sec\n", (long long int)then - now));
}
The function below prints the long time value corresponding to the date
{2001-07-04 12:00:00 Z}.
#include <stdio.h>
#include <stdtime.h>
#include <string.h>
longtime_t time_2001()
{
struct calendar date;
longtime_t t;
// Set the calendar date
initcalendar(&date, _CAL_NAME_GREGORIAN);
date.cal_era = _CAL_ERA_COMMON;
date.cal_year = 2001;
date.cal_mon = 7;
date.cal_mday = 4;
date.cal_hour = 12;
// Convert the calendar date into a long time
t = mklongtime(&date, NULL);
printf("04 Jul 2001: %lld\n", (long long)t);
return t;
}
The following function adds 60 days to a given date and normalizes it
into a valid calendar date.
#include <stdbool.h>
#include <stdio.h>
#include <stdtime.h>
bool add_60d(struct calendar *date)
{
// Add 60 days to the date and renormalize it
date->cal_mday += 60;
return mklongtime(date, date->cal_zone) != _LONGTIME_ERROR;
}
The following function converts a count of the number of seconds since
{1970-01-01 00:00:00 Z}, sans leap seconds,
into its corresponding calendar date.
#include <stdlib.h>
#include <stdtime.h>
struct calendar * posix_date(long secs)
{
struct calendar * date;
// Initialize a date object
date = malloc(sizeof(struct calendar));
initcalendar(date, _CAL_NAME_GREGORIAN);
// Convert the POSIX seconds count into a date
date->cal_era = _CAL_ERA_COMMON;
date->cal_year = 1970;
date->cal_mday = (int)(secs / (24L*60*60));
secs %= 24L*60*60;
date->cal_hour = (int)(secs / (60*60));
secs %= 60*60;
date->cal_sec = (int)(secs);
// Normalize the date
mklongtime(date, NULL);
return date;
}
#include <stdtime.h>
int setcalendartime(struct calendar *date, const struct timezone *zone,
longtime_t t);
[Notes]
This function operates similarly to the gmtime() and localtime() functions, converting a longtime_t value into a calendar date. However, it performs the date conversion with respect to a specific timezone and DST variants. It also handles erroneous time values. If there is no timezone setting specified, the converted date represents a "bare" date and time irrespective of any timezone, or in other words, a date and time with respect to UTC. It is possible that some longtime_t values are not representable as valid calendar dates because they fall outside the range of dates supported by the implementation. In such cases, the function fails.
#include <stdlib.h>
#include <stdtime.h>
struct calendar * get_date(void)
{
struct timezone * zone;
struct calendar * date;
// Allocate and initialize a timezone object
zone = malloc(sizeof(*zone));
inittimezone(zone, "");
// Allocate and initialize a calendar date object
date = malloc(sizeof(*date));
initcalendar(date, "");
date->cal_zone = zone;
// Set the calendar date to the current system time
setcalendartime(date, zone, getlongtime());
return date;
}
#include <stdtime.h>
int setcalendarzone(struct calendar *date,
const struct timezone *oldzone, const struct timezone *newzone);
[Notes]Argument newzone points to a timezone object, representing the timezone and Daylight Saving Time adjustments to apply to the date object. The pointer argument may be null, in which case no timezone or Daylight Saving Time adjustments are applied, i.e., the timezone object is treated as if it is equivalent to UTC. If the cal_type member of the date object has a value that is not supported by the implementation, the function fails. If the adjusted calendar date cannot be normalized into a meaningful date, the function fails.
This argument is typically the same as the cal_zone member of the date object, i.e., date->cal_zone.
[Notes]
Given a date object imbued with a specific date and time, and a timezone object for a specific timezone, that timezone object can be "applied to" the date object, which modifies the members of the date 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 date object is equivalent to shifting the original calendar date value from the UTC timezone into another timezone by adding the timezone offset. Once a date object has had a timezone applied to it, it remembers that particular timezone setting (by setting its cal_zone member). Multiple date 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 date 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 date 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).
#include <stdtime.h>
void undo_dst(struct calendar *date)
{
int adj;
// Check the date object
if (date == NULL)
return;
if (date->cal_zone == NULL)
return;
// Undo the DST adjustment
if (date->cal_dsti > 0)
{
// Determine the DST adjustment
adj = date->cal_zone.tz_z[date->cal_dsti].z_dst;
date->cal_sec += adj;
// Renormalize the date
mklongtime(date, NULL);
}
}
|
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 |
#include <stdio.h>
#include <stdtime.h>
void print_time(void)
{
struct timezone zone;
struct calendar date;
char buf[80];
// Get a timezone for the locale and local timezone
inittimezone(&zone, "");
// Get a date object for the locale and local timezone
initcalendar(&date, _CAL_NAME_GREGORIAN);
// Set the calendar date to the current system time
setcalendartime(&date, &zone, getlongtime());
// Format the calendar date for printing
calendarformat(buf, sizeof(buf),
"%a %Y-%m-%d %H:%M:%S.%3f %Z", &date, &zone);
// 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 <stdtime.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 calendar date
ts = *localtime(&now);
// Format the calendar date 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);
}
#include <stdbool.h>
#include <stdlib.h>
#include <stdtime.h>
bool change_tz(struct calendar *date, const char *tzname)
{
struct timezone * zone;
// Allocate and initialize a new timezone object
zone = malloc(sizeof(*zone));
if (inittimezone(zone, tzname) != 0)
{
free(zone);
return false;
}
// Change the timezone settings of the calendar date
return setcalendarzone(date, date->cal_zone, zone) == 0;
}
#include <stdio.h>
#include <stdtime.h>
void add_60_days(void)
{
struct timezone zone;
struct calendar date;
char buf[80];
// Build a date of 2001-09-11 08:50:00 EDT
inittimezone(&zone, "EST5EDT");
initcalendar(&date, _CAL_NAME_GREGORIAN);
date.cal_year = 2001;
date.cal_mon = 9;
date.cal_mday = 11;
date.cal_hour = 8;
date.cal_min = 50;
// Format and print the date
calendarformat(buf, sizeof(buf),
"%a %Y-%b-%d %H:%M %Z", &date, &zone);
printf("%s\n", buf);
// Add 60 days to the date and renormalize it
date.cal_mday += 60;
mklongtime(&date, &zone);
// Format and print the new date
calendarformat(buf, sizeof(buf),
"%a %Y-%b-%d %H:%M %Z", &date, &zone);
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
#include <stdio.h>
#include <stdtime.h>
void weekday_1776(void)
{
struct calendar date;
char buf[80];
// Build a date of 1776-07-04 12:00:00 Z
initcalendar(&date, _CAL_NAME_GREGORIAN);
date.cal_year = 1776;
date.cal_mon = 7;
date.cal_mday = 4;
date.cal_hour = 12;
// Determine the weekday by normalizing the date
mklongtime(&date, NULL);
// Print the resulting day of the week
calendarformat(buf, sizeof(buf), "%a", &date, NULL);
printf("%s\n", buf);
}
#include <stdtime.h>
#include <time.h>
// Convert a 'tm' struct into a 'calendar' struct
void tm_to_calendar(struct calendar *date, const struct tm *ts)
{
int v;
/* Fill the contents of 'cal' from 'ts' */
initcalendar(date, _CAL_NAME_GREGORIAN);
// Set the calendar members
v = ts->tm_year + 1900;
date->cal_era = (v > 0 ? _CAL_ERA_COMMON : _CAL_ERA_COMMON+1);
date->cal_year = (v > 0 ? v : -v + 1);
date->cal_mon = ts->tm_mon + 1;
date->cal_mday = ts->tm_mday;
date->cal_yday = ts->tm_yday + 1;
v = (ts->tm_wday != 0 ? ts->tm_wday : 7);
date->cal_wday = v;
date->cal_week = (ts->tm_yday - v + 7+4)/7; // Simplified
date->cal_hour = ts->tm_hour;
date->cal_min = ts->tm_min;
date->cal_sec = ts->tm_sec;
date->cal_nsec = 0; // Unknown, assume zero
v = ts->tm_isdst;
date->cal_dsti = (v < 0 ? -1 : v > 0 ? 1 : 0);
date->cal_zone = NULL; // Unknown, assume UTC
}
#include <stdtime.h>
#include <time.h>
// Convert a 'calendar' struct into a 'tm' struct
int calendar_to_tm(struct tm *ts, const struct calendar *date)
{
int v;
// Validate the calendar date object
if (date->cal_type != _CAL_TYPE_GREGORIAN)
return -1;
// Check for special dates
if (date->cal_year == _CAL_YR_ERROR)
return -1;
// Set the tm members
v = date->cal_year;
v = (date->cal_era == _CAL_ERA_COMMON ? v : -v+1);
ts->tm_year = v - 1900;
ts->tm_mon = date->cal_mon - 1;
ts->tm_mday = date->cal_mday;
ts->tm_yday = date->cal_yday - 1;
ts->tm_wday = date->cal_wday % 7;
ts->tm_hour = date->cal_hour;
ts->tm_min = date->cal_min;
ts->tm_sec = date->cal_sec;
v = 0;
if (date->cal_zone != NULL)
v = date->cal_zone->tz_z[date->cal_dsti].z_dst;
ts->tm_isdst = v;
return 0;
}
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);
longtime_t mklongtime(const struct calendar *date, const timezone *zone);
int setcalendartime(struct calendar *date,
const struct timezone *zone, longtime_t t);
can be replaced with the functionally equivalent definitions:
time_t calendartime(const struct calendar *date,
const timezone *zone);
// Returns (time_t)(-1) on failure.
int setcalendartime(struct calendar *date,
const struct timezone *zone, time_t t);
|
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 |