Time Functionality in the Standard C Library
Articles and Tips: article
Senior Software Engineer
Novell Server-library Development
rbateman@novell.com
01 Aug 2000
This AppNote discusses how ANSI and POSIX time funcionality works in the standard C Library.
Copyright 2000 by Novell, Inc. All rights reserved. No part of this document may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying and recording, for any purpose without the express written permission of Novell.
All product names mentioned are trademarks of their respective companies or distributors.
- Introduction
- Definitions
- Standard Time Functionality
- Printing Out Time Using asctime and strftime
- More Time Calls
- Using tzset and Time Zone Information
- Global Time Data
- Reentrant Time Functions
- Questions of NetWare and DOS Time
- Conclusion
Introduction
The purpose of this article is to outline how ANSI and POSIX time functionality works--a topic that confused me for many years even though I'm the one who owned the code in the library. Now that I've ported, modified, rewritten, and even written a new one from scratch over the years, I know that the array of time functions in the library is nothing short of bewildering because, even though the list is fairly short, the functionality isn't immediately obvious.
I'm going to mention Greenwich, England in this article since that town hosts the midday meridian. You might think as I once did that there was some great, compelling reason for the choice of that village, perhaps because of its role as the nearest naval center to the capital of the world's most powerful seafaring nation at the time, or the presence of the Old Royal Observatory that dates back to 1675 and Charles II with its involvement over the next one hundred years in solving the problem of longitude calculations at sea (essentially a time-keeping problem): it was the British maritime meridian in that calculation. No, despite all this, I am assured by Derek Allen of the Greenwich Foundation (greenwichfoundation.org.uk), who was polite enough to answer my query on this point in a most timely fashion, that there was no intrinsic geographical reason for the choice made quite arbitrarily at a scientific conference as late as 1883. The choice was no doubt influenced by Great Britain's then supreme position as a maritime power, but there were two or three alternatives offered, including by the French, but Greenwich was chosen by the vote of a mostly scientific body.
Definitions
As a specific aid, I have created a glossary of terms and types that are important in this article. You may occasionally refer back to these to understand subsequent statements. The terms are not arbitrarily chosen; they are those used by the documents that drive the standard in the industry, specifically the ANSI 9989 (1990) and ISO/IEC 9989 (1999) as well as POSIX 1996.
In this article, I have bolded terms (including function and type names) the first time I use them.
Calendar time
Calendar time as better explained in the article, is the number of seconds since the Epoch in coordinated universal time (UTC).
clock_t
Type clock_t is an expression of time elapsed since an epoch such as application load or OS boot. It is usually a scalar and usually measured in ticks.
Daylight Savings Time
Daylight savings time (called summer time in most countries) is local time during a part of the year when that locale finds it advantageous to advance the clock artificially, usually by one hour. The reasoning behind this usually includes "adding" an hour of daylight in the evening (when it is most useful) and "subtracting" an hour of sun-up in the morning (to sleep one hour less past dawn). Most, but not all locales of the world are plagued by this phenomenon. Rules exist for many locales but have not always done so. Until the 1980s, for example, when to go on and off summer time was an annual subject of Parliament debate and no rule could describe it. In most of the United States, a simple on-off rule is in force (first Sunday of April, last Sunday of October), but Arizona, for example, eschews any notion of daylight savings time.
Epoch
The Epoch is a moment designated as the earliest useful time and date stamp supported by the implementation. For the Macintosh, this is 1 January 1900, for DOS, 1980, and for UNIX and our present discussion since it is about ANSI/POSIX interfaces, it is 1 January 1970.
GMT (Greenwich Mean Time)
GMT or Greenwich Mean Time is time at the Greenwich Meridian.
Library
The library refers to either CLib or LibC (or both) versions of Standard C in this article. There are some differences in implementation and when these exist, they are pointed out.
Library Context
Library context is a concern in this discussion. Because they were originally defined for a statically-linked, user-level library, many time functions use notions of static data that now finds their representation in per-thread or per-application (NLM) storage that can only be managed effectively for known threads. Knowing enough about a calling thread to manage this is known as thread- or library context. Threads that aren't created under library care (such as system threads including call-backs, interrupt service routines, and work-to-dos, or foreign application threads), cannot call all time functions effectively.
Local Time
Local time is the time for the current, geographical locale usually adjusted for daylight or summer time.
struct tm
Type struct tm is a representation that holds time in broken-out parts (seconds, minutes, hours, months, years, etc.) usually specific to the locale that yielded it. It is also known as broken-down time.
Summer Time
Summer time is the term many countries (Britain and France for example) use instead of the American expression daylight savings time.
Ticks
Ticks is a platform-arbitrary measurement of time elapsed. It is constant such that a formula relating the number of ticks to an elapsed second is possible.
time_t
Type time_t is the basic expression of calendar time, usually a scalar and usually an expression of seconds since an Epoch.
Time Zone
Time zone is the recognition of the problem of longitude across the face of the planet, namely that the cadastral position of the Sun isn't the same for every locale around the world at precisely the same moment in time. There are roughly, as may be imagined, twenty-four time zones separated by one hour from each other around the world, but practically speaking, many more exist that calculate their current time offset from UTC as fractional (minus 5:30 UTC, etc.). Other time zones even overlap; for example, Britain is almost due north of the geographic mass of France and yet, it is usually an hour behind the latter.
UTC (Universal Time Coordinated)
UTC or Universal Time Coordinated is the hour at the time meridian and, as such, is equivalent to Greenwich Mean Time (GMT) without regard for daylight or summer offset.
Standard Time Functionality
Standard time functionality is the object of the standard (ANSI) header time.h. It is included thus:
#include <time.h<
The nucleus of the standard time interface is the data type time_t. ANSI says we're free to represent it any way we want.
Under the influence of UNIX, most represent it using 4 bytes (long) whose values indicate the number of seconds since the Epoch or 1 January 1970 (a Thursday as I recall; the rest of you aren't old enough to remember that). We've used 4 bytes most everywhere in the industry since the preponderate word size moved from 16 to 32 bits. We're safe with that width now because it will hold up until past the middle of this new century by which time we'll have moved on to 64-bit, then bigger machines. The natural word size of the implementing platform will tend to be time_t. A 64-bit representation alone would push the utility of time_t out beyond the time when programmers start to get paid in gold-pressed latinum!
What you absolutely need to remember about time_t is that it holds seconds since the Epoch in Universal Time Coordinated (UTC). This is, outside of summer or daylight-savings time considerations in Britain, the same hour enjoyed by those that live in a famous village near London named Greenwich. It is very important to understand that time_t, as used in the Standard C library, never indicates local time, a point of confusion to many, I know. UTC held in time_t data is also referred to as calendar time.
typedef long time_t;
There are three standard functions that get calendar time, interpret it for locale, and convert it back to calendar time. These are time, localtime, and mktime.
Using time
The way to get standard, calendar time is to call the time function:
time_t time( time_t *timer );
This puts calendar time into the location pointed at by timer and returns it also as the value of the function. This value is useful for the next operation.
Using localtime
The second data type associated with getting time is the broken-down time structure or struct tm. This structure consists of fields defined in time.h as follows:
int tm_sec; //seconds after the minute [0..59]int tm_min; // minutes after the hour [0..59]int tm_hour; // hours since midnight [0..23]int tm_mday; // days of the month [1..31]int tm_mon; // months since January [0..11] --zero-basedint tm_year; // years since 1900 [0..INF]int tm_wday; // days since Sunday [0..6] --zero-basedint tm_yday; // days since first January [0..365]int tm_isdst; // summer time (0 in effect, 0 not, -1 unknown)
struct tm *localtime( const time_t *timer );
which loads a static, per-thread broken-down time structure (struct tm) with data to express the time contained by timer. According to the library's algorithm, once the structure is filled out to match timer, it becomes an easy matter to massage it into a form more agreeable to the inhabitants of the local meat space by adjusting the hour for offset from UTC and refining the result by folding in daylight savings time if applicable and in effect. localtime is, therefore, the second of the three all-important functions among the library time interfaces.
Now, a word about static data, these functions, and the libraries that furnish them. Many rely on per-thread (logically static) data. Each thread has its own copy of this data so that other threads may not corrupt it before it can be used. This is true in CLib.NLM starting with version v4.11 and also of LibC.NLM.
To summarize what we've introduced, by far the most common use of these functions, the following pseudocode prints a time stamp on the standard output stream:
#include <time.h<#include <stdio.h<<<#define ERROR_STR "Calendar time is not available< in this implementation!"<{< time_t t;< struct tm *tmp;<< t = time(NULL);<< if (t == (time_t) -1)< fprintf(stderr, ERROR_STR);<< tmp = localtime(<t);<< printf("Time: %d/%d/%d %02d:%02d%02d\n",< tmp-<tm_mday, tmp-<tm_year, tmp-<tm_hour,< tmp-<tm_min, tmp-<tm_sec);<}<
Using mktime
Before discussing more time functions, let's talk about the last member of the time function triumvirate, mktime. With time and localtime, mktime finishes the principal time manipulation functionality of the library. mktime turns a local expression of time--in a broken-down time structure, such as localtime returns--into a value expressed by time_t and, consequently, adjusted back to UTC.
time_t mktime( struct tm *timeptr );
This function is extremely useful because it is powerful enough to take any cock-eyed input, normalize it, and feed it into its mechanism to generate a calendar time value. This means that a given time may be manufactured and even manipulated arithmetically. For instance, let's say we put together a statement of time for Monday, 17 September 1973, at 1:03:52 in the morning, thus:
struct tm tm;memset(tm, 0, sizeof(struct tm));tm.tm_sec = 52;tm.tm_min = 3;tm.tm_hour = 1;tm.tm_mday = 16;tm.tm_mon = 8;tm.tm_year = 73;tm.tm_isdst = -1;
We don't have to perform
tm.tm_wday = 1;
because mktime will normalize the structure to set that field correctly. Similarly, we don't have to set the daylight-savings time flag (hereafter referred to as summer time for convenience and accuracy) because mktime will fill it in. However, we should place a -1 in this field to signify explicitly that we don't know what it is or whether the locale even supports summer time. mktime will use the rules in effect for the present locale to determine this.
Now, let's say that, for some application purpose, we observe the passage of twenty seconds and add these twenty seconds to the seconds field of the broken-down time structure:
tm.tm_sec += 20;
At this point, we've got 72 seconds in the tm_sec field which, given the existence of tm_min and tm_hour, seems anomalous. mktime will correct this by bumping tm_min to 4 and resetting tm_sec to 12.
The fact that mktime returns a UTC-adjusted expression as the return value of the function seems almost secondary in this light, but it is the normalization of the struct tm argument that is the by-product of this function. time_t seems a good way to express an hour in such a way as to communicate it anywhere in the world to any locale. A locale may convert that expression to a broken-down time structure adjusted appropriately that will indicate the exact (and same) moment in that locale corresponding with the original 'event' in the source locale.
Thus, we have the following relationship between the three main functions:
Figure 1: Relationship between three main functions: time, localtime, and mktime.
While the entry point in this cycle is usually time, it need not be. What is important is to understand that the time function produces what localtime needs to produce what mktime needs to produce, again, what time produced in the first place. A fourth function, gmtime, has been added to this diagram to situate it respective to the others and will be discussed in a moment, but first, let's discuss printing and formatting the time values we have.
Printing Out Time Using asctime and strftime
Both asctime and strftime perform formatted output that would be in fact localized if Novell did that sort of thing; it's only beginning to happen in LibC. The first of the two functions, asctime puts time out in the format illustrated by the following example:
Sun Sep 16 01:03:52
The example contains implicit comments on the construction of the string. asctime fills in one of those static, per-thread strings already discussed. strftime is considerably more supple and permits a printf-style tailoring of the output. ctime is a short-cut function to get to the output of asctime without having to go through a call to localtime, at least not explicitly. ANSI obliges the library to implement ctime as if the application made the following statement:
return asctime(localtime(timer));
More Time Calls
Other important time calls include gmtime, difftime, and clock.
Using gmtime
From the function signature (name and argument), it would stand to reason that calling gmtime results in something similar to calling localtime. This is true in that the resulting broken-down time structure is such a representation of the calendar (UTC) time. But, what purpose can the result serve? Nothing more than displaying UTC, for to plug the result back into mktime as one might with the result of localtime yields nothing useful (see diagram). The output of gmtime cannot be used meaningfully as the input to any other function.
struct tm *gmtime( const time_t *timer );
Using difftime
Implemented as both a macro and a function, difftime is able to determine the difference between two times without any possible loss of precision. You may have wondered why, when a macro in-lines code and avoids function-call overhead, one might want a function version of the interface in question. Consider, therefore, the case of qsort or bsearch to which a pointer to a function for sorting or comparison must be passed creating the need for a function to exist however unlikely difftime might appear in this context.
double difftime( time_t t1, time_t t2 );
Using clock
Using clock doesn't really fit in the context of the discussion of standard time functions in that it has little to do with time of day. Rather, it is the means by which code may determine arbitrary points along a temporal continuum. clock returns the amount of time used by the program since an arbitrary epoch and in implementation-arbitrary units.
clock_t clock( void );
In the case of CLib (and LibC) and NetWare, the value is expressed in ticks since a point near to the time the application was loaded. During initialization of an application, the library makes a call to the OS to get the number of ticks since boot and records this in application global space. When the application calls the library via clock, it calls the OS again and subtracts the originally recorded value (at application initialization) from it placing the result in what it returns from clock as clock_t. To obtain the number of seconds from this value, a definition is available:
#define CLOCKS_PER_SEC
Using this identifier in a program invokes a function that returns the number of ticks per second because there is risk that this relationship change from release to release or hardware platform to hardware platform.
Using tzset and Time Zone Information
Time zone information is extremely complex in the library and has been the whole source of errors ever found within CLib. It consists of complex rules and protocols that help localtime and mktime perform their conversion functions. Many time functions, as mandated by the POSIX standard, call tzset before performing any work. This ensures that an accurate representation of the rules and protocols exist before doing the work.
void tzset( void );
Actually, since NetWare 4.11, the library optimizes this procedure to ensure that system console set parameters and the OS synchronized clock structure aren't interrogated and evaluated so frequently as once every time a call to tzset is made. The library remembers how long since the last time this rather extensive information was ferreted out and just reuses the copy it got. For this reason, a change in time zone at the system console may not be detected by the library (and hence, effective in applications) for a period not to exceed ten minutes.
POSIX says that the time zone settings for the host platform may be overridden by environment variables. In particular, setting "TZ" in an application's environment will redirect tzset in making it stop using the start-up settings in AutoExec.NCF.
To understand more about how environment variables affect the distinction of time zone information, read the standards documents themselves. In general, LibC's code is more respectful of the standards and makes fewer of the undocumented assumptions that CLib's long history has led the latter to make. The LibC implementation is, therefore, somewhat closer to what Open Source porters expect.
Global Time Data
In addition to the usual function calls, there are some pieces of global data specified by POSIX (ANSI specifies no such thing) and some Novell has added in imitation of other implementations or by reason of utility. While these interfaces are used as if static data, they are really function calls that return pointers to per-thread or per- NLM data for reading (not so much for writing to).
char *tzname[2];time_t timezone;int daylight;time_t daylightOffset;
The two strings held in tzname are the name of the local time zone and the name of that time zone while summer time is in effect. For example, in Provo, this is "MST" and "MDT." This "variable" exists in LibC; most of the remaining ones discussed here do not since one of the LibC's goals is a return to standards.
timezone
The number of seconds offset from UTC to reach the time zone of the current locale.
daylight
A Boolean value noting whether the current locale supports a summer time difference.
daylightOffset
The number of seconds offset from the current locale to summer time (usually 3600 or one hour). It is the amount to add to the current time to derive daylight-savings.
daylightOnOff
A Boolean stating whether the locale is currently on summer time.
Reentrant Time Functions
To get around the problem of reentrancy in time functions, POSIX set forth a number of functions that don't rely on static data. For example, asctime makes use of a per-thread string that gets changed every time it is called with a different struct tm. asctime_r does not suffer from this problem since a pointer to the string to be used is passed in. Here are all of the time functions that fall into this category:
char *asctime_r( const struct tm *timeptr, char*string );char *ctime_r( const time_t *timer, char*string );struct tm *gmtime_r( const time_t *timer, struct tm *timeptr );struct tm *localtime_r( const time_t *timer, struct tm *timeptr );
Nevertheless, all these so-called reentrant functions still assume static data representations. The data in question is in the notion of time zone information.
However, tzset static information is an example of data that POSIX says may be defaulted in the case of the reentrant version of time functions it mandates. The library is allowed to make assumptions if the problem cannot be worked around, as it cannot in the case of missing library context. POSIX says, for example, that tzset need not be called in the reentrant time functions it mandates. This means that calls to these reentrant functions may use the library's notion of time zone if the calling thread has no context from which to get information changed with respect to the system notions by the application.
Questions of NetWare and DOS Time
There are additional problems in using time on a NetWare server (from a NetWare- loadable Module or NLM). These lie in the fact that NetWare's file system owes some allegiance to DOS, specifically in the way time is kept. Two functions convert values that can be retrieved from NetWare API calls or set using more of these calls. For CLib development, these are done with some fairly complicated data structures:
#include <nwtime.h<<time_t _ConvertDOSTimeToCalendar(< LONG<DOSDateAndTime );<void _ConvertTimeToDOS( time_t calendarTime,< struct _DOSDate *filDateP, < struct _DOSTime *filTimeP );<
For LibC, DOS date and time are held in an unsigned long and the interface has been greatly simplified:
#include <time.h<<struct dos_tm<{< unsigned short bisecond : 5;// two-second< increments only< unsigned short minute : 6;// 0-59< unsigned short hour : 5;// 0-23< unsigned short day : 5;// 1-31< unsigned short month : 4;// 1-12< unsigned short years : 7;// years since 1980< (limit: 0- 119)<};<<union dos_tm_u<{< unsigned long long_dt;// scalar for argument passing< struct dos_tm struct_dt;<};<<time_t dos2calendar( < unsigned long dosTimeAndDate );<unsigned long calendar2dos( time_t timer );<
Conclusion
These interfaces are easier to use if the concepts detailed here are understood, particularly the distinction between calendar and local time as well as how to derive one from the other. Given that time_t is part and parcel of other interfaces such as stat, the functionality from the time.h header is very useful. It is hoped that this article will be a beginning point in learning how to use this functionality.
* Originally published in Novell AppNotes
Disclaimer
The origin of this information may be internal or external to Novell. While Novell makes all reasonable efforts to verify this information, Novell does not make explicit or implied claims to its validity.