C / STANDARD LIBRARY TOUR
time.h: clocks, timestamps and measuring elapsed time
Stamp events with time(), format them with gmtime and strftime, and measure wall time and CPU time without the usual unit bugs.
What you will learn
- Measure real time with time() plus difftime(), CPU time with clock()
- Convert a time_t with gmtime or localtime, then print it with strftime
- Subtract two clock_t values first, then cast to double and divide by CLOCKS_PER_SEC
- Let mktime normalise out-of-range struct tm fields to do date arithmetic
Understanding time.h: clocks, timestamps and measuring elapsed time
time.h holds two clocks that have almost nothing to do with each other. time(NULL) gives you a time_t: a stamp on the calendar, an arithmetic type whose portable use is to hand it to difftime, gmtime, localtime or mktime rather than to subtract it yourself. clock() gives you a clock_t counting the processor time your own process has consumed, in units of 1/CLOCKS_PER_SEC. That difference is the whole lesson: a program that waits ten seconds for a disk advances the calendar clock by ten seconds and clock() hardly at all, while a program keeping four cores busy can advance clock() faster than the wall clock, because on glibc every thread's CPU time is added in.
A time_t is one number; struct tm is that same instant exploded into fields. gmtime converts it to UTC, localtime applies the timezone and daylight-saving rules from the environment, and mktime converts a filled-in struct tm back into a time_t. mktime also normalises as it goes: set tm_mday to 46 in January and it rewrites the struct as 15 February and fills in tm_wday and tm_yday, which is how you do calendar arithmetic without knowing month lengths or leap-year rules. Watch two details in that struct, tm_year counting from 1900 and tm_mon counting from 0, and remember that gmtime and localtime return a pointer to one shared static object, so copy what you need out of it immediately.
Resolution decides which clock you can actually use. time() ticks once per second, so it can only measure work lasting seconds; clock() is counted in microseconds on POSIX, but the kernel usually updates that accounting in millisecond-sized steps, so a single measurement of a fast function is mostly noise. The fix is repetition: run the work enough times that the measured interval is far larger than the granularity, then divide by the number of runs. And always subtract in the clock's own type before converting to double, because clock_t is an integer type and integer division silently discards the fraction you were trying to measure.
<stdio.h>
<time.h>
int main(void)
{
/* Fixed timestamps, so this program prints the same thing on every run. */
time_t launch = 1000000000; /* on POSIX: seconds since 1970-01-01 UTC */
time_t landing = launch + 5445;
struct tm a, b;
char t1[32], t2[32];
double secs;
int whole;
/* gmtime returns a pointer to one shared static struct tm,
so copy the value out before calling it again. */
a = *gmtime(&launch);
b = *gmtime(&landing);
strftime(t1, sizeof t1, "%Y-%m-%d %H:%M:%S", &a);
strftime(t2, sizeof t2, "%Y-%m-%d %H:%M:%S", &b);
secs = difftime(landing, launch); /* later time first */
whole = (int)secs;
printf("launch : %s UTC\n", t1);
printf("landing: %s UTC\n", t2);
printf("elapsed: %.0f s = %dh %02dm %02ds\n",
secs, whole / 3600, (whole / 60) % 60, whole % 60);
printf("launch fell on day %d of %d, tm_wday %d (0 = Sunday)\n",
a.tm_yday + 1, a.tm_year + 1900, a.tm_wday);
return 0;
}time.h exposes a calendar clock you stamp and format and a counter of processor time your process has used, and choosing the wrong one is what makes timings lie.
Worked examples
Wall clock and CPU clock side by side
Spins for two seconds of real time and shows that clock() tracks the wall clock only because the process never stops using the CPU.
<stdio.h>
<time.h>
int main(void)
{
time_t start = time(NULL);
clock_t cpu_start = clock();
time_t now;
double wall, cpu;
/* Spin, do not sleep, until the calendar clock has moved on two seconds. */
do {
now = time(NULL);
} while (difftime(now, start) < 2.0);
wall = difftime(now, start);
cpu = (double)(clock() - cpu_start) / CLOCKS_PER_SEC;
printf("wall clock: %.0f s\n", wall);
printf("cpu time over half the wall time? %s\n",
cpu > wall / 2.0 ? "yes" : "no");
return 0;
}Example explained
Line 1difftime(now, start) takes the later time first and returns a double, so the code works even where time_t is not a plain second count.
Line 2The do/while never blocks, so the process stays scheduled and clock() climbs almost in step with the calendar clock.
Line 3Replace the spin with anything that blocks, such as sleep or a socket read, and the second line flips to no, because clock() only counts CPU charged to your process.
Date arithmetic with mktime
Uses mktime's normalisation to turn an impossible date into a real one and to add 90 days without touching month lengths.
<stdio.h>
<time.h>
int main(void)
{
struct tm t = {0};
char buf[32];
t.tm_year = 2024 - 1900; /* years since 1900 */
t.tm_mon = 0; /* 0 is January */
t.tm_mday = 46; /* "January 46" */
t.tm_hour = 12;
t.tm_isdst = -1; /* let the library decide about DST */
if (mktime(&t) == (time_t)-1) {
puts("mktime failed");
return 1;
}
strftime(buf, sizeof buf, "%Y-%m-%d %A", &t);
printf("normalised: %s (tm_yday %d)\n", buf, t.tm_yday);
t.tm_mday += 90; /* 90 days later */
t.tm_isdst = -1;
mktime(&t);
strftime(buf, sizeof buf, "%Y-%m-%d %A", &t);
printf("+90 days : %s (tm_yday %d)\n", buf, t.tm_yday);
return 0;
}Example explained
Line 1tm_year is years since 1900 and tm_mon is zero based, so 2024 and January are written as 124 and 0.
Line 2tm_isdst = -1 asks mktime to work out daylight saving itself instead of trusting whatever was in the field.
Line 3mktime rewrites t in place: day 46 of January becomes 15 February, and tm_wday and tm_yday are filled in, which is why strftime can print the weekday.
Line 4Adding 90 to the normalised tm_mday and calling mktime again crosses February's leap day and three month boundaries with no arithmetic of your own.
Subtract clock_t values before dividing
Shows why converting each clock() reading to seconds separately destroys the measurement.
<stdio.h>
<time.h>
int main(void)
{
/* Two readings clock() could plausibly return, 600000 ticks apart. */
clock_t begin = 1234567;
clock_t end = 1834567;
printf("CLOCKS_PER_SEC = %ld\n", (long)CLOCKS_PER_SEC);
printf("divide then subtract = %ld s\n",
(long)(end / CLOCKS_PER_SEC - begin / CLOCKS_PER_SEC));
printf("subtract then divide = %f s\n",
(double)(end - begin) / CLOCKS_PER_SEC);
return 0;
}Example explained
Line 1clock_t is an integer type here, so end / CLOCKS_PER_SEC throws away the fractional second in each reading before the subtraction can see it.
Line 21834567 / 1000000 and 1234567 / 1000000 both truncate to 1, so their difference is 0 seconds.
Line 3Subtracting first preserves all 600000 ticks, and the (double) cast then makes the division produce 0.6.
Line 4Divide by the CLOCKS_PER_SEC macro rather than a literal 1000000, because on Windows the unit is 1000 ticks per second.
Important notes
CLOCKS_PER_SEC states the unit of clock_t, not its accuracy: it is 1000000 on Linux and macOS, yet the accounting typically advances in millisecond-sized jumps.
Neither clock in time.h is guaranteed to move only forwards; an NTP correction can push the calendar clock backwards and make difftime negative, so for a monotonic timer you need POSIX clock_gettime(CLOCK_MONOTONIC).
Common mistakes
Timing a sleep, a file read or a network wait with clock(): it reports close to zero because the process was off the CPU, so genuinely slow code looks instant.
Writing (end - start) / CLOCKS_PER_SEC in integer arithmetic, or dividing each reading before subtracting: every interval shorter than a second prints as 0.
Filling struct tm with tm_year = 2024 and tm_mon = 12: mktime reads that as year 3924 and month 13, so the resulting date is wrong by nearly two thousand years.
Try it yourself
Change, predict, then run
Fill two struct tm values for 1969-07-20 and 2026-01-01, convert both with mktime, and print the number of whole days between them by dividing difftime by 86400. Then print the weekday name of the earlier date using strftime with %A.
Open the C workspaceCheck your understanding
A program spends three seconds blocked waiting for data on a socket and one second in a tight computation, bracketed by both time() and clock(). What do the two measurements report?
- difftime gives about 4 seconds, clock() about 1 second
- both give about 4 seconds
- both give about 1 second
- difftime gives about 1 second, clock() about 4 seconds
Show answer
clock() counts processor time charged to the process, and a blocked process is not running on a processor, so only the one second of computation is counted; difftime on the two time_t stamps measures real time and sees all four seconds. Answering that both give about 4 seconds treats clock() as a wall clock, which is precisely the assumption that makes I/O-bound code look free when you time it with clock().