74 Time — <time.h>
What to know first
Looking back
Bringing forward a story to be treated in Part XII: calendar time and elapsed time were said to be different things. Then what is used in standard C to measure exactly “whether three seconds have passed”?
A. With the standard alone there is no way to measure it exactly. time is in seconds and can go backwards when the system adjusts the clock, and clock measures not wall-clock time but the CPU time the process used (so it does not grow while waiting for input and output). C11 brought in timespec_get but does not require a monotonic clock. Accurate elapsed measurement is the part of POSIX’s clock_gettime(CLOCK_MONOTONIC, …) or Windows’ QueryPerformanceCounter — that is, of a platform API.
The need for this chapter, and its context
By the end of this chapter
time_t is nor how time zones are handled. We look at the traps that arise in those gaps — the year with 1900 subtracted, the month starting from 0, functions that return a static buffer, and the fact that there is no monotonic clock in the standard for measuring elapsed time.The questions this chapter answers
- Why do so many types exist for time — would one count of seconds not do?
74.1 Three representations of time
| type or function | what | to know |
|---|---|---|
time_t | calendar time (usually seconds since 1970) | ★ the standard settles only “an arithmetic type” |
struct tm | split into year, month, day, hour, minute, second | the field rules are a trap |
clock_t | the CPU time the process used | divide by CLOCKS_PER_SEC |
struct timespec | seconds + nanoseconds (C11) | timespec_get |
Table 75.1
That the standard did not settle what time_t is matters. In most implementations it is seconds since 1970-01-01 UTC, but that is a practice, not a guarantee of the standard. So portable code does not calculate with time_t’s internal value directly but takes the difference with difftime.
Q. Why do so many types exist for time — would one count of seconds not do?
A. Because three different jobs are involved. time_t is an opaque value naming one moment; struct tm is the calendar notation people read (year, month, day, hour, minute, second); clock_t is a scale for measuring elapsed time. Turning a moment into a calendar is a hard computation full of time zones, daylight saving and leap seconds, so the standard keeps the two in separate types and lets localtime and mktime bridge them.
The accidents in practice happen exactly at that border — subtracting two time_t values gives seconds, but adding to the fields of a struct tm directly produces an unnormalised date. Date arithmetic must go through mktime.
74.2 The traps of struct tm
examples-en/ch74/timefns.c
#include <stdio.h>
#include <time.h>
#include <string.h>
int main(void)
{
/* A fixed moment is used so the output is the same every time (a reproducible example) */
struct tm t = {0};
t.tm_year = 2026 - 1900; /* * 1900 subtracted */
t.tm_mon = 8 - 1; /* * counted from 0 */
t.tm_mday = 5;
t.tm_hour = 13; t.tm_min = 45; t.tm_sec = 30;
t.tm_isdst = -1; /* marks that we do not know about daylight saving */
printf("the trap in the struct fields: tm_year=%d (the year 2026), tm_mon=%d (August)\n",
t.tm_year, t.tm_mon);
/* mktime normalises the values and fills in the day of the week */
struct tm norm = t;
time_t stamp = mktime(&norm);
static const char *dow[] = {"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"};
printf("the weekday mktime filled in: %s (tm_wday=%d)\n",
norm.tm_wday >= 0 && norm.tm_wday < 7 ? dow[norm.tm_wday] : "?", norm.tm_wday);
/* strftime takes the buffer size and returns 0 if it does not fit */
char buf[64];
size_t n = strftime(buf, sizeof buf, "%Y-%m-%d %H:%M:%S", &norm);
printf("strftime: [%s] (%zu characters)\n", buf, n);
char tiny[8];
size_t m = strftime(tiny, sizeof tiny, "%Y-%m-%d %H:%M:%S", &norm);
printf("a small buffer: it returns %zu (0 means failure — the contents are unspecified)\n", m);
/* date arithmetic is done with mktime, not by subtracting seconds */
struct tm plus = norm;
plus.tm_mday += 30; /* 30 days later — mktime tidies it up even across a month boundary */
time_t later = mktime(&plus);
strftime(buf, sizeof buf, "%Y-%m-%d", &plus);
printf("30 days later: %s\n", buf);
printf("difftime: %.0f seconds\n", difftime(later, stamp));
/* for measuring elapsed time use clock() or the platform's monotonic clock */
clock_t c0 = clock();
volatile long acc = 0;
for (long i = 0; i < 1000000; i++) acc += i;
clock_t c1 = clock();
printf("time measured with clock(): %s\n",
(double)(c1 - c0) / CLOCKS_PER_SEC >= 0.0 ? "measured (the value differs by machine)" : "?");
return 0;
}
Output
the trap in the struct fields: tm_year=126 (the year 2026), tm_mon=7 (August)
the weekday mktime filled in: Wednesday (tm_wday=3)
strftime: [2026-08-05 13:45:30] (19 characters)
a small buffer: it returns 0 (0 means failure — the contents are unspecified)
30 days later: 2026-09-04
difftime: 2592000 seconds
time measured with clock(): measured (the value differs by machine)
Two fields are famous traps.
tm_yearis the value with 1900 subtracted. 2026 is 126.tm_monis from 0. August is 7.
With tm_mday (from 1), tm_wday (Sunday is 0) and tm_yday (from 0) the rules are all different, so when filling them by hand it is better to keep a table beside you.
mktime does two things — it turns a struct tm into a time_t, and it normalises the struct. In the example, adding 30 to tm_mday exceeded the range and yet it was tidied into 4 September thanks to that. Not doing date arithmetic yourself but using this property is the canonical way.
tm_isdst is easy to forget too. Putting in −1 means “I do not know, judge for yourself”, and putting 0 or 1 in wrongly puts you an hour out.
Counter-example. Carrying around the result of localtime
struct tm *a = localtime(&t1);
struct tm *b = localtime(&t2); /* what a pointed at has been overwritten */
printf("%d %d\n", a->tm_hour, b->tm_hour); /* both are t2's time */localtime, gmtime, ctime and asctime return an internal static buffer (those functions chapter 61 brushed past by name only). The next call overwrites the previous result, and in a program running along several strands they wreck each other’s results.
There are two prescriptions. Copy it immediately on receipt, or use the edition in which the caller gives the buffer (localtime_r and gmtime_r are POSIX, localtime_s is annex K and MSVC). To keep portability with the standard alone, copying is the right answer.
74.3 Printing to a string — strftime
Unlike printf, strftime takes the buffer size and returns 0 if it does not fit. The example’s small buffer is that case. If the return value is 0 the buffer’s content is undetermined, so it must not be used.
asctime and ctime are better not used. Besides returning a static buffer, the form is fixed ("Wed Aug 5 13:45:30 2026\n") and it can overflow when the year exceeds four digits, so C23 marked them for retirement.
| specifier | meaning | note |
|---|---|---|
%Y, %m, %d | year, month, day | the ISO date is %Y-%m-%d |
%H, %M, %S | hour, minute, second | 24-hour |
%F, %T | %Y-%m-%d, %H:%M:%S | C99 |
%z, %Z | time-zone offset and name | locale- and platform-dependent |
%s | epoch seconds | ★ not standard (a POSIX extension) |
%c, %x, %X | locale notation | for humans. not used for machines |
Table 75.2
74.4 Time zones and summer time — what the standard does not handle
The time zones standard C knows are only two, “local” and “UTC”, and there is not even a standard way to change the local time zone (POSIX’s TZ environment variable is the practice). To handle an arbitrary time zone a library is needed.
Summer time is trickier still. On a transition day there arise times that do not exist (the hour skipped in spring) and times that exist twice (the hour repeated in autumn). What mktime returns when given such input is settled by the implementation.
In practice. Real accidents time has called down
Time is a regular in quiet accidents. In 2012 and 2015, when leap seconds were inserted, several server programs burned CPU at 100% or froze — because kernels and applications had not assumed a situation in which “one second comes twice”.
The limit of a 32-bit time_t overflows on 19 January 2038 (the day chapter 27′s overflow appears on a worldwide scale). In embedded and old systems it is an ongoing task even now, and so the transition to a 64-bit time_t has long been under way.
Code that measures elapsed time in local time loses or gains an hour on every summer-time transition day. A log’s timestamps go backwards, a timeout becomes an hour long, a scheduler runs the same job twice. The prescription is always the same — a monotonic clock for elapsed time, UTC for records, local time only when showing it to a human.
Recap
Time in summary.
| what you want to do | what to use | what to beware of |
|---|---|---|
| the current time | time(NULL) | in seconds. it can go backwards |
| splitting it up | localtime/gmtime + copy at once | the static buffer |
| date arithmetic | add to the fields and mktime | do not calculate seconds by hand |
| to a string | strftime | a return of 0 = failure |
| difference | difftime | t2 - t1 is not portable |
| measuring elapsed time | the platform’s monotonic clock | time and localtime forbidden |
| storing and transmitting | UTC + ISO 8601 | do not store local time |
| not to be used | asctime, ctime | static buffer, fixed form, to be retired in C23 |
Table 75.3
We have passed time. The next chapter is the tools used when a program has gone wrong — error numbers, assertions, signals, and non-local jumps.