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.
This is the last of three installments on the functions declared in <time.h>. (See "The Header <time.h>," CUJ January 1993 and "Time Conversion Functions ," CUJ February 1993.) I conclude by describing the functions that convert encoded times to human-readable text strings.
Standard C provides an extraordinary ability to capture times and dates. It also lets you convert freely between scalar and component forms. That lets you do arithmetic on components such as days or months, leaving to the library the hard work of correcting for calendar irregularities. The Standard C library even lets you switch between local and universal (UTC or GMT) time zones, correcting as needed for Daylight Savings Time. (All that assumes that the library has some way of learning time zone information from the environment, of course.)
The icing on the cake is the ability to convert times to human-readable form. C has long had the functions ctime and asctime to perform this useful service. Committee X3J11 added the more general function strftime. It lets you pick the time and date components you wish to include. It also adapts to the current locale. Thus Standard C now encourages programs that print time information in the most useful form for each culture. (Again, that assumes that the library has a nontrivial implementation of the locale machinery.)
The topic for this month is the remaining functions declared in <time.h> those that convert times to text strings. They are not as hairy as the conversion functions I described last month, but they have their own complexities.
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.3.1 The asctime function
Synopsis
#include <time.h> char *asctime(const struct tm *timeptr);Description
The asctime function converts the broken-down time in the structure pointed to by timeptr into a string in the form
Sun Sep 16 01:03:52 1973\n\0using the equivalent of the following algorithm.
char *asctime(const struct tm *timeptr) { static const char wday_name[7][3] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; static const char mon_name[12][3] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; static char result[26]; sprintf(result, "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n", wday_name[timeptr->tm_wday], mon_name[timeptr->tm_mon], timeptr->tm_mday, timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec, 1900 + timeptr->tm_year); return result; }Returns
The asctime function returns a pointer to the string.
7.12.3.2 The ctime function
Synopsis
#include <time.h> char *ctime(const time_t *timer);Description
The ctime function converts the calendar time pointed to by timer to local time in the form of a string. It is equivalent to
asctime (localtime(timer))Returns
The ctime function returns the pointer returned by the asctime function with that broken-down time as argument. Forward references: the localtime function (7.12.3.4).....
7.12.3.5 The strftime function
Synopsis
#include <time.h> size_t strftime(char *s, size_t maxsize, const char *format, const struct tm *timeptr);Description
The strftime function places characters into the array pointed to by s as controlled by the string pointed to by format. The format shall be a multibyte character sequence, beginning and ending in its initial shift state. The format consists of zero or more conversion specifiers and ordinary multibyte characters. A conversion specifier consists of a % character followed by a character that determines the behavior of the conversion specifier. All ordinary multibyte characters (including the terminating null character) are copied unchanged into the array. If copying takes place between objects that overlap, the behavior is undefined. No more than maxsize characters are placed into the array. Each conversion specifier is replaced by appropriate characters as described in the following list. The appropriate characters are determined by the LC_TIME category of the current locale and by the values contained in the structure pointed to by timeptr."%a" is replaced by the locale's abbreviated weekday name.
"%A" is replaced by the locale's full weekday name.
"%b" is replaced by the locale's abbreviated month name.
"%B" is replaced by the locale's full month name.
"%c" is replaced by the locale's appropriate date and time representation.
"%d" is replaced by the day of the month as a decimal number (01-31).
"%H" is replaced by the hour (24-hour clock) as a decimal number (00-23).
"%I" is replaced by the hour (12-hour clock) as a decimal number (01-12).
"%j" is replaced by the day of the year as a decimal number (001-366).
"%m is replaced by the month as a decimal number (01-12).
"%M" is replaced by the minute as a decimal number (00-59).
"%p" is replaced by the locale's equivalent of the AM/PM designations associated with a 12-hour clock.
"%S" is replaced by the second as a decimal number (00-61).
"%U" is replaced by the week number of the year (the first Sunday as the first day of week 1) as a decimal number (00-53).
"%w" is replaced by the weekday as a decimal number (0-6), where Sunday is 0.
"%W" is replaced by the week number of the year (the first Monday as the first day of week 1) as a decimal number (00-53).
"%x" is replaced by the locale's appropriate date representation.
"%X" is replaced by the locale's appropriate time representation.
"%y" is replaced by the year without century as a decimal number (00-99).
"%Y" is replaced by the year with century as a decimal number.
"%Z" is replaced by the time zone name or abbreviation, or by no characters if no time zone is determinable.
"%%" is replaced by %.
If a conversion specifier is not one of the above, the behavior is undefined.
Returns
If the total number of resulting characters including the terminating null character is not more than maxsize, the strftime function returns the number of characters placed into the array pointed to by s not including the terminating null character. Otherwise, zero is returned and the contents of the array are indeterminate.
The Function strftime
The one complicated function declared in <time.h> (from the outside, at least) is strftime. You use it to generate a text representation of a time and date from a struct tm under control of a format string. In this sense, it is modeled after the print functions declared in <stdio. h>. It differs in two important ways:
For example, the code fragment:
- strftime does not accept a variable argument list. It obtains all time and date information from one argument.
- The behavior of strftime can vary considerably among locales. The locale category LC_TIME can, for example, specify that the text form of all dates follow the conventions of the French culture.
char buf[100]; strftime(buf, sizeof buf, "%A, %x", localtime(&t0));might store in buf any of:
Sunday, 02 Dec 1979 dimanche, le 2 decembre 1979 Weekday 0, 02/12/79If your goal is to display times and dates in accordance with local custom, then strftime gives you just the flexibility you need. You can even write multi-byte-character sequences between the conversion specifiers. That lets you convert dates to Kanji and other large character sets.Here are the conversion specifiers defined for strftime. I follow each with an example of the text it produces. The examples are all from P.J. Plauger and Jim Brodie, ANSI and ISO Standard C, Microsoft Press, 1992. All assume the "C" locale and the date and time Sunday, 2 December 1979 at 06:55:15 AM EST:
- %a the abbreviated weekday name (Sun)
- %A the full weekday name (Sunday)
- %b the abbreviated month name (Dec)
- %B the full month name (December)
- %c the date and time (Dec 2 06:55:15 1979)
- %d the day of the month (02)
- %H the hour of the 24-hour day (06)
- %I the hour of the 12-hour day (06)
- %j the day of the year, from 001 (335)
- %m the month of the year, from 01 (12)
- %M the minutes after the hour (55)
- %p the AM/PM indicator (AM)
- %S the seconds after the minute (15)
- %U the Sunday week of the year, from 00 (48)
- %w the day of the week, from 0 for Sunday (0)
- %W the Monday week of the year, from 00 (47)
- %x the date (Dec 2 1979)
- %X the time (06:55:15)
- %y the year of the century, from 00 (79)
- %Y the year (1979)
- %Z the time zone name, if any (EST)
- %% the per cent character (%)
Using the Formatting Functions
Note that the two functions that return a value of type pointer to char return a pointer to a static data object. 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 one of these shared data objects if you need the value beyond a conflicting function call.asctime (The asc comes from ASCII, which is now a misnomer.) Use this function to generate the text form of the date represented by the argument (which points to a broken-down time). The function returns a pointer to a null-terminated string that looks like "Sun Dec 2 06:55:15 1979\n". This is equivalent to calling strftime with the format "%a %c\n" in the "C" locale. Call asctime if you want the English-language form regardless of the current locale. Call strftime if you want a form that changes with locale. See the warning about shared data objects, above.
ctime ctime(pt) is equivalent to the expression asctime(localtime(pt)). You use it to convert a calendar time directly to a text form that is independent of the current locale. See the warning about shared data objects, above.
strftime This function generates a null-terminated text string containing the time and date information that you specify. You write a format string argument to specify a mixture of literal text and converted time and date information. You specify a broken-down time to supply the encoded time and date information. The category LC_TIME in the current locale determines the behavior of each conversion.
Implementing the Conversion Functions
These functions convert encoded times to text strings in various ways. All depend, in the end, on the internal function _Strftime to do the actual conversion. What varies is the choice of locale. The function asctime (and, by extension, the function ctime) convert times by a fixed format, following the conventions of the "C" locale regardless of the current state of the locale category LC_TIME. The function strftime, on the other hand, lets you specify a format that directs the conversion of a broken-down time. It follows the conventions of the current locale. Thus, one of the arguments to _Strftime specifies the locale-specific time information (of type_Tinfo) to use.Listing 1 shows the file asctime.c. It defines the function asctime that formats a broken-down time the same way irrespective of the current locale. The file also defines the data object _Times that specifies the locale-specific time information. And it defines the internal data object ctinfo, which replicates the time information for the "C" locale.
Listing 2 shows the file ctime.c. The function ctime simply calls localtime, then asctime, to convert its time_t argument. Thus, it always follows the conventions of the "C" locale.
Listing 3 shows the file strftime.c. The function strftime calls _Strftime, using the locale-specific time information stored in_Times. Thus, its behavior changes with locale.
Listing 4 shows the file xstrftim.c. It defines the internal function _Strftime that does all the work of formatting time information. _Strftime uses the macro PUT, defined at the top of the file xstrftim.c, to deliver characters. The macro encapsulates the logic needed to copy generated characters, count them, and limit the number delivered.
The internal function _Mbtowc, declared in <stdlib.h>, parses the format as a multibyte string using state memory of type _Mbstate that you provide on each call.
Listing 5 shows the file xgentime.c It defines the function _Gentime that performs the actual conversions for _Strftime. The function _Gentime consists primarily of a large switch statement that processes each conversion separately.
Each conversion determines a pointer p to a sequence of characters that gives the result of the conversion. It also stores a signed integer count at *pn. A positive count instructs _Strftime to generate the designated sequence of characters.
One source of generated characters is the function _Get_time, which selects a field from one of the strings in the locale-specific time information. Another is the internal function getval, also defined in the file xgentime.c, which generates decimal integers. getval stores characters in the accumulator provided by _Strftime.
Note that _Gentime includes a nonstandard addition. The conversion specifier %D converts the day of the month with a leading space in place of a leading 0. That's what asctime insists on.
_Gentime returns a negative count to instruct _Strftime to "push down" a format string for a locale-specific conversion. Three conversions change with locale: %c, %x, and %X. (The conversion %x, for example, becomes the format string "%b %d %Y" in the "C" locale.) You express these conversions as format strings that invoke the other conversions. Note that the function_Strftime supports only one level of format stacking.
The other internal function in the file xgentime.c is wkyr. It counts weeks from the start of the year for a given day of the year. The week can begin on Sunday (wstart is 0) or Monday (wstart is 1). The peculiar logic avoids negative arguments for the modulus and divide operators.
Conclusion
For over two and a half years, I have used this column for a single purpose to provide a guided tour of the Standard C library. Along the way, I got out a book of the same name. About half these installments have been excerpts from the completed book. With this installment, I bring to a close that guided tour.Starting next month, I'll pick up on other standards activities in the C community. That's more than you might think. When I started this column, I was a member of one ANSI standards committee. Now I find myself attending meetings for two ANSI committees and three ISO committees. Don't think I do it because it's unmitigated fun. I just think standards activities are too important these days to ignore. I'll do my best to keep you informed too.
This article is excerpted in part from P.J. Plauger, The Standard C Library, (Englewood Cliffs, N.J.: Prentice-Hall, 1992).