Formatting times in Standard C++ is much the same as in Standard C, but with rather more machinery involved.
Introduction
The C library has, from its earliest beginnings, provided good support for printing out dates and times. Dennis Ritchie and his friends at Bell Labs had a strong interest in astronomy. They saw to it that Unix, and C as a consequence, knew about Greenwich Mean Time (now UTC) as well as local time. Unix still stores all times in UTC, but displays them as local times for some time zone (which can differ for each user on a multiuser Unix system).
That means that some agency has to know about time zones and Daylight Savings Time, and how to deal with dates and times in all their idiosyncratic glory. The agency in question is the C library, specifically the functions declared in the header <time.h>. C is now on practically every operating system you can name. Few are as smart as Unix concerning times, but most of the C functionality is still present, thanks to the Standard C library.
The header <time.h> defines (among other things) two types:
- time_t, an arithmetic type that can represent a date and time, presumably to the nearest second
- tm, a struct type that can represent a date and time in component form
Various functions declared in the header obtain the current date and time from the system, and convert times between the above forms.
Functions also exist to convert both forms to human-readable text. For example, the function asctime returns a pointer to a null-terminated string of the form:
Sun Dec 2 06:55:15 1979\nIn the early 1970s, such functionality was head and shoulders above what you could find in any other programming language. There was only one small drawback the month and day names were all in English, and the format followed the conventions of the English-speaking world.
By the mid 1980s, the ANSI Standard for C was nearing completion. A European contingent proposed the addition of machinery that could adapt the behavior of the library for different cultures around the world. Most of this machinery ended up in the header <locale.h>, but not all. In particular, the function strftime in <time.h> became the locale-dependent way to convert the time components stored in a tm object to text form. This function has the signature:
size_t strftime(char *s, size_t n, const char *format, const struct tm *tptr);It converts elements from the object *tptr to text sequences under control of the null-terminated format string beginning at format, storing the result in the n character buffer beginning at s. It returns the number of characters actually generated, including the terminating null character.
Table 1 lists all conversion specifiers defined for strftime. Example replacement character sequences in parentheses follow each conversion specifier. All examples are for the "C" locale, using the date and time Sunday, 2 December 1979 at 06:55:15 AM EST.
Function strftime is thus both powerful and flexible, but it is a little annoying to use. You have to guess how large a buffer to give it. If you don't allow enough space, the function returns the number of characters you should have allowed, but doesn't promise to leave the buffer in a known state. In the most general case, you have to perform a trial conversion with a small (or even zero length) buffer, reallocate the buffer to the proper size, and perform the conversion yet again. That's rather low level, particularly in an era where objects know how to allocate and free their own storage automatically.
C++ Locale Facets
The Standard C++ library takes a different approach to locale dependency. It provides for multiple active locales within a program, not just one global locale. Each is encapsulated within a locale object. In particular, every input or output stream based on the iostreams classes contains a locale object. A stream consults its stored locale object to determine any locale-dependent behavior. (See "Standard C/C++: Introduction to Locales," CUJ, October 1997, for a general introduction to this topic.)
A locale in the Standard C library is divided up into half a dozen categories. You can concoct a composite locale where each category reflects the conventions of a different culture. The category LC_TIME, for example, controls the behavior of the function strftime. The nearest analog in the Standard C++ library is a locale facet. A facet is, basically, an object of type locale::facet, as defined in the header <locale>. Each locale object actually consists of an open-ended set of references to objects of different facet types.
The machinery of locales and facets is both complex and sophisticated. It strains the ability of most current C++ compilers. It also takes a lot of explaining. In fact, I've spent most of the past year describing locales and locale facets in these pages. I still have a few installments to go before I cover the entire subject, however superficially.
My aim this month is to describe the template class time_put. An object of a class specialized from this template class converts the components of a tm object to a text sequence, by locale dependent rules, and inserts the elements of the sequence into an output stream. Its put functions are essentially just wrappers for calls to strftime, with the LC_TIME category set to the appropriate locale. Every locale object contains references to objects of type time_put<char> and time_put<wchar_t>. These two objects differ only in the type of the sequence elements they generate.
How can you use time_put to advantage? Listing 1 shows a small example program. It should work with any compiler that includes some version of the Dinkum C++ Library, such as Microsoft Visual C++ V4.2 or later. It demonstrates how you can create a time inserter, by overloading operator<< for output streams. Here, I have defined the class Time_fmt, which stores a tm object and the strftime format string that should be used to convert its components to a text sequence. The default constructor takes a snapshot of the current time and stores it along with the very general (and implementation specific) conversion specifier "%c".
The inserter obtains a reference _Fac to the appropriate time_put object associated with the output stream. It then calls _Fac.put to insert elements in the output stream. Thus, the two lines:
Time_fmt today; cout << today;should write to the standard output stream the current date (and possibly the current time) in the form favored by the underlying Standard C library. In the case of Visual C++, my running example date prints something like:
2/12/79There's more. I also defined the simple class Time_dayname to show how easy it is to print time components in other forms. Its definition is just:
class Time_dayname : public Time_fmt { public: Time_dayname(Time_fmt x) : Time_fmt(*x.valptr(), "%A") {} };This particular class replaces the stored conversion specifier for a Time_fmt object with "%A". The net effect is that you can use a type cast to modify how the time gets displayed, as in:
cout << (Time_dayname)today;which inserts Sunday for the running example. Obviously, you can generate variants of this simple class as needed to display whatever time information you need.
Listing 1 shows a heavy-duty template definition of operator<< for Time_fmt objects. It works with any kind of output stream you care to contrive. (But please note that only the facets corresponding to the element types char and wchar_t occur in all locale objects.) As with earlier examples, I began with an existing template inserter from the header <ostream> and reworked it as needed. It's the only way I know to get all the complexities right for an inserter these days.
I remind you again that the macro _USEFAC(loc, fac) is peculiar to my implementation of the Standard C++ library. It paves over the dialect differences between compilers that support different generations of template processing. The macro expands either to use_facet<fac>(loc) or use_facet(loc, (fac *)0), accordingly. Thus, the macro does what's needed to obtain a reference to a locale facet.
Template Class time_put
Template class time_put, like template class money_put, behaves much like template class num_put. (See "Standard C/C++: The Facets num_put and numpunct," CUJ, January 1998.) As usual, I omit a detailed description of how facets work. See the past columns cited above if you're curious. But I still find it instructive to provide the usual description of how the inserter in Listing 1 makes use of time_put:
- The member function ios_base::getloc obtains a copy of the locale object imbued into the object of class basic_ostreambuf<_E, _Tr>.
- The template function use_facet<_Tput> obtains from its locale object argument a const reference to the facet _Tput (a typedef for time_put<_E, _Iter> here), which should always be present.
- The facet reference _Fac can be used to call one of the two versions of _Fac.put to convert the value pointed to by _Y.valptr(), of type tm *.
- _Fac.put inserts the elements it generates into the stream buffer (obtained by calling _O.rdbuf()) using an output iterator of type _Iter (a typedef for ostreambuf_iterator<_E, _Tr>).
- _Fac.put can, if it chooses, obtain stored formatting information from the object of class basic_ostreambuf<_E, _Tr> (_O), and possibly clear the field width stored there as well, though time_put does neither.
- _Fac.put can also pad as needed by inserting fill characters obtained from the base object by calling _Myios::fill(), though once again time_put does no padding.
Listing 2 shows one way to implement (most of) template class time_put. As usual, I omit most of the implementation-specific magic code, particularly the stuff that initializes a new object. Most of the action, as usual, occurs in the protected virtual member function do_put. (You override this virtual member functions, as needed, in a derived class to produce your own flavor of a time_put facet.)
As I indicated above, the real work is done in good old strftime. The call shown here is to the function _Strftime, which includes the information needed to shift to the appropriate locale before calling strftime. How this is done depends strongly on the capabilities of the underlying Standard C library. Once again, I gloss over details which can be highly intricate, very system dependent, and in any case distracting.
I remind you, as usual, that the macro _WIDEN converts a member of the basic C character set, as type char, to the element type. Typically, this involves little or no work. Similarly, the macro _NARROW converts the other way, or to a null character if that is not possible.
The visible part of a time_put facet, as usual, is the member function put, which comes in two flavors:
iter_type put(iter_type next, ios_base& x, char_type fill, tm *pt, char fmt, char mod = 0) const; iter_type put(iter_type next, ios_base& x, char_type fill, tm *pt, const E *first, const E *last) const;The first member function returns do_put(next, x, fill, pt, fmt, mod). The second member function copies to *next++ any element in the interval [first, last) other than a percent (%). For a percent followed by a character C in the interval [first, last), the function instead evaluates next = do_put(next, x, fill, pt, C, 0) and skips past C. If, however, C is a qualifier character from the set EOQ#, followed by a character C2 in the interval [first, last), the function instead evaluates next = do_put(next, x, fill, pt, C2, C) and skips past C2.
And here is the detailed description of do_put:
virtual iter_type do_put(iter_type next, ios_base& x, char_type fill, tm *pt, char fmt, char mod = 0) const;The virtual protected member function generates sequential elements beginning at next from time values stored in the object *pt, of type tm. The function returns an iterator designating the next place to insert an element beyond the generated output.
The output is generated by the same rules used by strftime, with a last argument of pt, for generating a series of char elements into an array. (Each such char element is assumed to map to an equivalent element of type E by a simple, one-to-one, mapping.) If mod equals zero, the effective format is "%F", where F equals fmt. Otherwise, the effective format is "%MF", where M equals mod.
The parameter fill is not used.
Conclusion
I have the same reservations about the time_put facets that I have for several others, such as money_get and money_put. I'm not sure they're of sufficiently widespread usefulness to warrant the overhead they add to essentially all C++ programs. Two facets are part of every locale object (at least in principle):
time_put<char, ostreambuf_iterator<char, char_traits<char> > > time_put<wchar_t, ostreambuf_iterator<wchar_t, char_traits<wchar_t> > >Both have virtual member functions, as I described above. It is a rare linker that can avoid loading the code for all virtual member functions, since it is difficult to tell whether they're actually called or not. For these facets in particular, those member functions call a host of other functions that your program probably doesn't otherwise need. Not the least of these is strftime, which is nontrivial. A typical implementation must thus load most of the code for both these facets regardless of whether the program ever makes use of them. (My implementation doesn't.)
But the capability is there to print times in all sorts of formats, if you have a need for it. I hope that, someday at least, you do. o
P.J. Plauger is Senior Editor of C/C++ Users Journal and President of Dinkumware, Ltd. He is the author of the Standard C++ Library shipped with Microsoft's Visual C++, v5.0. For eight years, he served as convener of the ISO C standards committee, WG14. He remains active on the C++ committee, J16. His latest books are The Draft Standard C++ Library, Programming on Purpose (three volumes), and Standard C (with Jim Brodie), all published by Prentice-Hall. You can reach him at pjp@plauger.com.