P.J. Plauger is senior editor of The C Users Journal. He is convenor of the ISO C standards committee, WG14, and active on the C++ committee, WG21. His latest books are The Standard C Library, published by Prentice-Hall, and ANSI and ISO Standard C (with Jim Brodie), published by Microsoft Press. You can reach him at pjp@plauger.com.
Introduction
Last month, I discussed the basic functions for obtaining scalar estimates of times. These include both elapsed execution time (type clock_t) and calendar time (type time_t). (See "The Header <time.h>," CUJ January 1993.) With just those facilities, you can obtain time stamps and measure small and large time intervals.But that's not the end of it. The Standard C library also lets you represent times in terms of their more familiar clock and calendar components (type struct tm). Moreover, the component representation can be in terms of either local time or UTC (formerly GMT). It can even keep track of whether Daylight Savings Time is in effect.
My goal this month is to lead you through the code that converts between scalar and structured times. If you've ever worked with calendar computations, you know that this is hard code to write and debug. That makes it rather hard to understand as well. Be prepared to take an occasional deep breath.
What the C Standard Says
7.12.3 Time conversion functions
Except for the strftime function, these functions return values in one of two static objects: a broken-down time structure and an array of char. Execution of any of the functions may overwrite the information returned in either of these objects by any of the other functions. The implementation shall behave as if no other library functions call these functions.
7.12.2.3 The mktime function
Synopsis
#include <time.h> time_t mktime(struct tm *timeptr);Description
The mktime function converts the broken-down time, expressed as local time, in the structure pointed to by timeptr into a calendar time value with the same encoding as that of the values returned by the time function. The original values of the tm_wday and tm_yday components of the structure are ignored, and the original values of the other components are not restricted to the ranges indicated above.139 On successful completion, the values of the tm_wday and tm_yday components of the structure are set appropriately, and the other components are set to represent the specified calendar time, but with their values forced to the ranges indicated above; the final value of tm_mday is not set until tm_mon and tm_year are determined.
Returns
The mktime function returns the specified calendar time encoded as a value of type time_t. If the calendar time cannot be represented, the function returns the value (time_t)-1.
Example
What day of the week is July 4, 2001?
#include <stdio.h> #include <time.h> static const cha[ *const wday[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "-unknown-" }; struct tm time_str; /*...*/ time_str.tm_year = 2001 - 1900; time_str.tm_mon = 7 - 1; time_str.tm_mday = 4; time_str.tm_hour = 0; time_str.tm_min = 0; time_str.tm_sec = 1; time_str.tm_isdst = -1; if (mktime(&time_str) == -1) time_str. tm_wday = 7; printf("%s\n", wday [time_str.tm_wday]); ....7.12.3.3 The gmtime function
Synopsis
#include <time.h> struct tm *gmtime(const time_t *timer);Description
The gmtime function converts the calendar time pointed to by timer into a broken-down time, expressed as Coordinated Universal Time (UTC).
Returns
The gmtime function returns a pointer to that object, or a null pointer if UTC is not available.
7.12.3.4 The local time function
Synopsis
#include <time.h> struct tm *localtime(const time_t *timer);Description
The local time function converts the calendar time pointed to by timer into a broken-down time, expressed as local time.
Returns
The localtime function returns a pointer to that object.Footnotes
139. Thus, a positive or zero value for tm_isdst causes the mktime function to presume initially that Daylight Saving Time, respectively, is or is not in effect for the specified time. A negative value causes it to attempt to determine whether Daylight Saving Time is in effect for the specified time.
Using the Conversion Functions
Note that the two functions that return a value of type pointer to struct tm return a pointer to a static data object of this type. Thus, a call to one of these functions can alter the value stored on behalf of an earlier call to another (or the same) function. Be careful to copy the value stored in this shared data object if you need the value beyond a conflicting function call.gmtime (The gm comes from GMT, which is now a slight misnomer.) Use this function to convert a calendar time to a broken-down UTC time. The member tm_isdst should be zero. If you want local time instead, use localtime, below. See the warning about shared data objects, above.
localtime Use this function to convert a calendar time to a broken-down local time. The member tm_isdst should reflect whatever the system knows about Daylight Savings Time for that particular time and date. If you want UTC time instead, use gmtime, above. See the warning about shared data objects, above.
mktime This function first puts its argument, a broken-down time, in canonical form. That lets you add seconds, for example, to the member tm_sec of a broken-down time. The function increases tm_min for every 60 seconds it subtracts from tm_sec until tm_sec is in the interval [0, 59]. The function then corrects tm_min in a similar way, then each coarser division of time through tm_year. It determines tm_wday and tm_yday from the other fields. Clearly, you can also alter a broken-down time by minutes, hours, days, months, or years just as easily.
mktime then converts the broken-down time to an equivalent calendar time. It assumes the broken-down time represents a local time. If the member tm_isdst is less than zero, the function endeavors to determine whether Daylight Savings Time was in effect for that particular time and date. Otherwise, it honors the original state of the flag. Thus, the only reliable way to modify a calendar time is to convert it to a broken-down time by calling localtime, modify the appropriate members, then convert the result back to a calendar time by calling mktime.
Implementing the Conversion Functions
Listing 1 shows the file gmtime.c. The function gmtime is the simpler of the two functions that convert a calendar time in seconds (type time_t) to a broken-down time (type struct_tm). It simply calls the internal function _Ttotm. The first argument is a null pointer to tell _Ttotm to store the broken-down time in the communal static data object. The third argument is zero to insist that Daylight Savings Time is not in effect.Listing 2 shows the file xttotm.c. It defines the function _Ttotm that tackles the nasty business of converting seconds to years, months, days, and so forth. The file also defines the function _Daysto that _Ttotm and other functions use for calendar calculations.
_Daysto counts the extra days beyond 365 per year. To do so, it must determine how may leap days have occurred between the year you specify and 1900. The function also counts the extra days from the start of the year to the month you specify. To do so, it must sometimes determine whether the current year is a leap year. The function recognizes that 1900 was not a leap year. It doesn't bother to correct for the non-leap years 1800 and earlier, or for 2100 and later. (Other problems arise within just a few decades of those extremes anyway.)
_Daysto handles years before 1900 only because the function mktime can develop intermediate dates in that range and still yield a representable time_t value. (You can start with the year 2000, back up 2,000 months, and advance 2 billion seconds, for example.) The logic is carefully crafted to avoid integer overflow regardless of argument values. Also, the function counts excess days rather than total days so that it can cover a broader range of years without fear of having its result overflow.
_Ttotm uses _Daysto to determine the year corresponding to its time argument secsarg. Since the inverse of _Daysto is a nuisance to write, _Ttotm guesses and iterates. At worst, it should have to back up one year to correct its guess. Both functions use the macro MONTAB, defined at the top of the file, to determine how many days precede the start of a given month. The macro also assumes that every fourth year is a leap year, except 1900.
The isdst (third) argument to _Ttotm follows the convention for the isdst member of struct tm:
Thus, _Ttotm will loop at most once. It calls the function _Isdst only if it needs to determine whether to loop. Even then, it loops only if _Isdst concludes that Daylight Savings Time is in effect.
- If isdst is greater than zero, Daylight Savings Time is definitely in effect. _Ttotm assumes that its caller has made any necessary adjustment to the time argument secsarg.
- If isdst is zero, Daylight Savings Time is definitely not in effect. _Ttotm assumes that no adjustment is necessary to the time argument secsarg.
- If isdst is less than zero, the caller doesn't know whether Daylight Savings Time is in effect. _Ttotm should endeavor to find out. If the function determines that Daylight Savings Time is in effect, it advances the time by one hour (3,600 seconds) and recomputes the broken-down time.
Listing 3 shows the file xisdst.c. The function _Isdst determines the status of Daylight Savings Time (DST). _Times._Isdst points at a string that spells out the rules. (I'11 show the file asctime.c next month. It contains the definition of _Times.)
_Isdst works with the rules in encoded form. Those rules are not current the first time you call the function or if a change of locale alters the last encoded version of the string _Times._Isdst. If that string is empty, _Isdst looks for rules appended to the time-zone information _Times._Tzone. It calls _Getzone as necessary to obtain the time-zone information. It calls _Gettime to locate the start of any rules for DST. The function _Getdst then encodes the current array of rules, if that is possible.
Given an encoded array of rules, _Isdst scans the array for rules that cover the relevant year. It adjusts the day specified by the rule for any weekday constraint, then compares the rule time against the time that it is testing. Note that the first rule for a given starting year begins not in DST. Successive rules for the same year go in and out of DST.
Listing 4 shows the file xgetdst.c. It defines the function _Getdst that parses the string pointed to by _Times._Isdst to construct the array of rules. The first character of a (non-empty) string serves as a field delimiter, just as with other strings that provide locale-specific time information. The function first counts these delimiters so that it can allocate the array. It then passes over the string once more to parse and check the individual fields.
_Getdst calls the internal function getint to convert the integer subfields in a rule. No overflow checks occur because none of the fields can be large enough to cause overflow. The logic here and in _Getdst proper is tedious but straightforward.
Local Time
Listing 5 shows the file localtim.c. The function localtime calls _Ttotm much like gmtime. Here, however, localtime assumes that it must convert a UTC time to a local time. To do so, the function must determine the time difference, in seconds, between UTC and the local time zone.The file localtim.c also defines the function _Tzoff that endeavors to determine this time difference (tzoff, in minutes). The time difference is not current the first time you call the function or if a change of locale alters the last encoded version of the string _Times._Tzone. If that string is empty, _Tzoff calls the function _Getzone to determine the time difference from environment variables, if that is possible.
However obtained, the string _Times._Tzone takes the form :EST:EDT:+0300. _TzoffS calls the function _Gettime to determine the starting position (p) and length (n) of the third field (#2, counting from zero). The function strtol, declared in <stdlib.h> must parse this field completely in converting it to an encoded integer. Moreover, the magnitude must not be completely insane. (The maximum magnitude is greater than 12*60 because funny time zones exist on either side of the International Date Line.)
Listing 6 shows the file xgettime.c. It defines the function _Gettime that locates a field in a string that specifies locale-specific time information. See the description of _Getdst, above, for how _Gettime interprets field delimiters. If _Gettime cannot find the requested field, it returns a pointer to an empty string.
Listing 7 shows the file xgetzone.c. The function_Getzone calls getenv, declared in <stdlib.h>, to determine the value of the environment variable "TIMEZONE". That value should have the same format as the locale-specific time string _Times. _Tzone, described above (possibly with rules for determining Daylight Savings Time bolted on).
If no value exists for "TIMEZONE", the function _Getzone then looks for the environment variable "TZ". That value should match the UNIX format ESTO5EDT. The internal function reformat uses the value of "TZ" to develop the preferred form in its static buffer.
If _Getzone finds neither of these environment variables, it assumes that the local time zone is UTC. In any event, it stores its decision in the static internal buffer tzone. Subsequent calls to the function return this remembered value. Thus, the environment variables are queried at most once, the first time that _Getzone is called.
Converting to Scalar Time
Listing 8 shows the file mktime.c. The function mktime computes an integer time_t from a broken-down time struct tm. It takes extreme pains to avoid overflow in doing so. (The function is obliged to return the value --1 if the time cannot be properly represented.)The first part of mktime determines a year and month. If they can be represented as type int, the function calls_Daysto to correct for leap days since 1900. mktime then accumulates the time in seconds as type double, to minimize further fretting about integer overflow. If the final value is representable as type time_t, the function converts it to that type. mktime calls _Ttotm to put the broken-down time in canonical form. Finally, the function corrects the time in seconds for Daylight Savings Time and converts it from local time to UTC. (The resultant code reads much easier than it wrote.)
Conclusion
Next month, I conclude my discussion of the time functions. I also conclude my guided tour of the Standard C library. The trip is almost over.This article is excerpted in part from P.J. Plauger, The Standard C Library, (Englewood Cliffs, N.J.: Prentice-Hall, 1992).