Different countries (and sometimes even different regions within a single country) operate on different timezones and DST regimes. Programs that input and output times must take into account the timezone and DST regime of the system on which they are run. Fortunately, all of the details are handled by the C library.
Timezone information tends to be both voluminous and volatile. For this reason, rather than encoding it directly into programs or libraries, the system maintains this information in files in standard formats.
These files reside in the directory /usr/share/zoneinfo
. Each file in this directory contains information about the timezone regime in a particular country or region. These files are named according to the timezone they describe, so we may find files with names such as EST
(US Eastern Standard Time), CET
(Central European Time), UTC
, Turkey
, and Iran
. In addition, subdirectories can be used to hierarchically group related timezones. Under a directory such as Pacific
, we may find the files Auckland
, Port_Moresby
, and Galapagos
. When we specify a timezone for use by a program, in effect, we are specifying a relative pathname for one of the timezone files in this directory.
The local time for the system is defined by the timezone file /etc/localtime
, which is often linked to one of the files in /usr/share/zoneinfo
.
The format of timezone files is documented in the tzfile(5) manual page. Timezone files are built using zic(8), the zone information compiler. The zdump(8) command can be used to display the time as it would be currently according to the timezone in a specified timezone file.
To specify a timezone when running a program, we set the TZ
environment variable to a string consisting of a colon (:
) followed by one of the timezone names defined in /usr/share/zoneinfo
. Setting the timezone automatically influences the functions ctime(), localtime(), mktime(), and strftime().
To obtain the current timezone setting, each of these functions uses tzset(3), which initializes three global variables:
char *tzname[2]; /* Name of timezone and alternate (DST) timezone */ int daylight; /* Nonzero if there is an alternate (DST) timezone */ long timezone; /* Seconds difference between UTC and local standard time */
The tzset() function first checks the TZ
environment variable. If this variable is not set, then the timezone is initialized to the default defined in the timezone file /etc/localtime
. If the TZ
environment variable is defined with a value that can’t be matched to a timezone file, or it is an empty string, then UTC is used. The TZDIR
environment variable (a nonstandard GNU-extension) can be set to the name of a directory in which timezone information should be sought instead of in the default /usr/share/zoneinfo
.
We can see the effect of the TZ
variable by running the program in Example 10-4. In the first run, we see the output corresponding to the system’s default timezone (Central European Time, CET). In the second run, we specify the timezone for New Zealand, which at this time of year is on daylight saving time, 12 hours ahead of CET.
$./show_time
ctime() of time() value is: Tue Feb 1 10:25:56 2011 asctime() of local time is: Tue Feb 1 10:25:56 2011 strftime() of local time is: Tuesday, 01 Feb 2011, 10:25:56 CET $TZ=":Pacific/Auckland" ./show_time
ctime() of time() value is: Tue Feb 1 22:26:19 2011 asctime() of local time is: Tue Feb 1 22:26:19 2011 strftime() of local time is: Tuesday, 01 February 2011, 22:26:19 NZDT
Example 10-4. Demonstrate the effect of timezones and locales
time/show_time.c
#include <time.h> #include <locale.h> #include "tlpi_hdr.h" #define BUF_SIZE 200 int main(int argc, char *argv[]) { time_t t; struct tm *loc; char buf[BUF_SIZE]; if (setlocale(LC_ALL, "") == NULL) errExit("setlocale"); /* Use locale settings in conversions */ t = time(NULL); printf("ctime() of time() value is: %s", ctime(&t)); loc = localtime(&t); if (loc == NULL) errExit("localtime"); printf("asctime() of local time is: %s", asctime(loc)); if (strftime(buf, BUF_SIZE, "%A, %d %B %Y, %H:%M:%S %Z", loc) == 0) fatal("strftime returned 0"); printf("strftime() of local time is: %s\n", buf); exit(EXIT_SUCCESS); }time/show_time.c
SUSv3 defines two general ways in which the TZ
environment variable can be set. As just described, TZ
can be set to a character sequence consisting of a colon plus a string that identifies the timezone in an implementation-specific manner, typically as a pathname for a timezone description file. (Linux and some other UNIX implementations permit the colon to be omitted when using this form, but SUSv3 doesn’t specify this; for portability, we should always include the colon.)
The other method of setting TZ
is fully specified in SUSv3. In this method, we assign a string of the following form to TZ
:
std offset [ dst [ offset ][ , start-date [ /time ] , end-date [ /time ]]]
Spaces are included in the line above for clarity, but none should appear in the TZ
value. The brackets ([]
) are used to indicate optional components.
The std and dst components are strings that define names for the standard and DST timezones; for example, CET and CEST for Central European Time and Central European Summer Time. The offset in each case specifies the positive or negative adjustment to add to the local time to convert it to UTC. The final four components provide a rule describing when the change from standard time to DST occurs.
The dates can be specified in a variety of forms, one of which is M
m
.
n
.
d. This notation means day d (0 = Sunday, 6 = Saturday) of week n (1 to 5, where 5 always means the last d day) of month m (1 to 12). If the time is omitted, it defaults to 02:00:00 (2 AM) in each case.
Here is how we could define TZ
for Central Europe, where standard time is one hour ahead of UTC, and DST—running from the last Sunday in March to the last Sunday in October—is 2 hours ahead of UTC:
TZ="CET-1:00:00CEST-2:00:00,M3.5.0,M10.5.0"
We omitted the specification of the time for the DST changeover, since it occurs at the default of 02:00:00. Of course, the preceding form is less readable than the Linux-specific near equivalent:
TZ=":Europe/Berlin"