Proven C Book한국어 GitHub

74 Time — <time.h>

What to know first

chapter 46, Structs · values bundled in a struct
chapter 61, The terrain of the standard library · the contract the standard settles

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

Since the standard settles unusually little here, the later part of the close reading is the right place. To recognize that “the standard does not say” is itself the cause of the traps, you need to have seen enough headers where the standard did say. Gaps show only against a contrast.

By the end of this chapter

The header the standard settled unusually little of. The standard says neither what 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

  1. Why do so many types exist for time — would one count of seconds not do?

74.1 Three representations of time

type or functionwhatto know
time_tcalendar time (usually seconds since 1970)★ the standard settles only “an arithmetic type”
struct tmsplit into year, month, day, hour, minute, secondthe field rules are a trap
clock_tthe CPU time the process useddivide by CLOCKS_PER_SEC
struct timespecseconds + 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.

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.

specifiermeaningnote
%Y, %m, %dyear, month, daythe ISO date is %Y-%m-%d
%H, %M, %Shour, minute, second24-hour
%F, %T%Y-%m-%d, %H:%M:%SC99
%z, %Ztime-zone offset and namelocale- and platform-dependent
%sepoch seconds★ not standard (a POSIX extension)
%c, %x, %Xlocale notationfor 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 dowhat to usewhat to beware of
the current timetime(NULL)in seconds. it can go backwards
splitting it uplocaltime/gmtime + copy at oncethe static buffer
date arithmeticadd to the fields and mktimedo not calculate seconds by hand
to a stringstrftimea return of 0 = failure
differencedifftimet2 - t1 is not portable
measuring elapsed timethe platform’s monotonic clocktime and localtime forbidden
storing and transmittingUTC + ISO 8601do not store local time
not to be usedasctime, ctimestatic 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.