Dates and Times
|
This section documents C23 (ISO/IEC 9899:2024), per ISO/IEC JTC1/SC22/WG14’s freely available working draft N3220, which WG14 documents as differing from the published standard only editorially — the reference these pages are written and verified against. This content was generated with the assistance of AI and should be verified against the WG14 draft and cppreference.com’s C reference before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
<time.h> covers three separate concerns that are easy to confuse: a calendar time (a point in civil time),
a wall-clock timestamp with sub-second resolution, and processor time consumed. Using the wrong one is how
benchmarks end up measuring the wrong thing.
The Three Time Types
| Type | Obtained from | Represents |
|---|---|---|
|
|
A calendar time with (in practice) one-second resolution. Almost universally seconds since the Unix epoch, though C does not require that. |
|
|
Seconds plus nanoseconds. C11’s higher-resolution timestamp. |
|
|
Processor time used by the program, in |
|
|
A broken-down calendar time: year, month, day, hour, minute, second, and more. |
#include <stdio.h>
#include <time.h>
int main(void)
{
// 1. A calendar time, to the second.
time_t now = time(nullptr);
if (now == (time_t)-1) {
fputs("time is unavailable\n", stderr);
return 1;
}
// 2. A timestamp with nanosecond resolution (C11).
struct timespec ts;
if (timespec_get(&ts, TIME_UTC) != TIME_UTC) {
fputs("timespec_get failed\n", stderr);
return 1;
}
// 3. Processor time consumed so far.
clock_t cpu = clock();
printf("time_t = %lld\n", (long long)now);
printf("timespec = %lld.%09ld\n", (long long)ts.tv_sec, ts.tv_nsec);
printf("cpu seconds = %g\n", (double)cpu / CLOCKS_PER_SEC);
printf("CLOCKS_PER_SEC = %ld\n", (long)CLOCKS_PER_SEC);
return 0;
}
time_t is an arithmetic type of unspecified representation, so print it by casting to long long — there is
no printf specifier for it. C23 adds timespec_getres for querying a clock’s resolution, and TIME_MONOTONIC
and TIME_ACTIVE/TIME_THREAD_ACTIVE as optional bases alongside the mandatory TIME_UTC.
Broken-Down Time — struct tm
#include <stdio.h>
#include <time.h>
int main(void)
{
time_t now = time(nullptr);
// Two conversions: UTC or the local time zone.
struct tm utc = *gmtime(&now);
struct tm local = *localtime(&now);
// The field conventions are the classic C gotcha:
printf("UTC: %04d-%02d-%02d %02d:%02d:%02d (yday %d, wday %d, isdst %d)\n",
utc.tm_year + 1900, // years SINCE 1900
utc.tm_mon + 1, // 0 = January
utc.tm_mday, // 1..31, this one is 1-based
utc.tm_hour, // 0..23
utc.tm_min, // 0..59
utc.tm_sec, // 0..60 (60 allows a leap second)
utc.tm_yday, // 0..365
utc.tm_wday, // 0 = Sunday
utc.tm_isdst); // >0 in DST, 0 not, <0 unknown
printf("Local: %04d-%02d-%02d %02d:%02d:%02d\n",
local.tm_year + 1900, local.tm_mon + 1, local.tm_mday,
local.tm_hour, local.tm_min, local.tm_sec);
return 0;
}
Note the gmtime(&now) copy. gmtime and localtime return a pointer to a *single static struct tm, so
the next call overwrites it — copying immediately is the only safe use in any program with more than one
conversion, and mandatory in threaded code. C23 standardizes the reentrant gmtime_r and localtime_r that
POSIX has always had:
#include <stdio.h>
#include <time.h>
int main(void)
{
time_t now = time(nullptr);
struct tm buffer;
// C23 (POSIX for decades): the caller supplies the storage.
struct tm *utc = gmtime_r(&now, &buffer);
if (utc == nullptr) {
return 1;
}
printf("%04d-%02d-%02d\n", utc->tm_year + 1900, utc->tm_mon + 1, utc->tm_mday);
return 0;
}
Constructing and Normalizing a Time
mktime goes the other way — broken-down local time to time_t — and normalizes out-of-range fields,
which makes it C’s date arithmetic:
#include <stdio.h>
#include <time.h>
int main(void)
{
// Build a specific local date: 2026-02-28 12:00:00
struct tm date = {
.tm_year = 2026 - 1900,
.tm_mon = 2 - 1,
.tm_mday = 28,
.tm_hour = 12,
.tm_min = 0,
.tm_sec = 0,
.tm_isdst = -1, // -1: let mktime work out whether DST applies
};
time_t stamp = mktime(&date); // note: mktime MODIFIES date, normalizing it
if (stamp == (time_t)-1) {
fputs("unrepresentable date\n", stderr);
return 1;
}
printf("2026-02-28 -> %lld, weekday %d\n", (long long)stamp, date.tm_wday);
// Date arithmetic by normalization: add 5 days by overflowing tm_mday.
struct tm later = date;
later.tm_mday += 5;
later.tm_isdst = -1;
if (mktime(&later) == (time_t)-1) {
return 1;
}
printf("+5 days = %04d-%02d-%02d\n",
later.tm_year + 1900, later.tm_mon + 1, later.tm_mday); // 2026-03-05
// C23: timegm is the UTC counterpart of mktime (POSIX had it long before).
struct tm utc_date = {
.tm_year = 2026 - 1900, .tm_mon = 0, .tm_mday = 1,
.tm_hour = 0, .tm_min = 0, .tm_sec = 0,
};
time_t utc_stamp = timegm(&utc_date);
printf("2026-01-01T00:00:00Z -> %lld\n", (long long)utc_stamp);
return 0;
}
Adding "one day" as + 86400 seconds is wrong across a DST transition; normalizing tm_mday through mktime
is right, because it re-resolves the offset. Note that mktime interprets its input as local time — always
set tm_isdst = -1 unless you genuinely know the answer.
Formatting with strftime
#include <stdio.h>
#include <time.h>
int main(void)
{
time_t now = time(nullptr);
struct tm utc;
if (gmtime_r(&now, &utc) == nullptr) {
return 1;
}
char buffer[128];
// strftime returns the number of bytes written, or 0 if it did not fit.
if (strftime(buffer, sizeof buffer, "%Y-%m-%dT%H:%M:%SZ", &utc) == 0) {
fputs("format did not fit\n", stderr);
return 1;
}
printf("ISO 8601 : %s\n", buffer);
strftime(buffer, sizeof buffer, "%A, %d %B %Y", &utc);
printf("long : %s\n", buffer);
strftime(buffer, sizeof buffer, "%a %b %e %H:%M:%S %Y", &utc);
printf("asctime : %s\n", buffer);
strftime(buffer, sizeof buffer, "week %V of %G, day %u", &utc);
printf("ISO week : %s\n", buffer);
return 0;
}
The specifiers worth knowing: %Y 4-digit year, %m month, %d day, %H/%M/%S time, %j day of year,
%A/%a weekday name, %B/%b month name, %p AM/PM, %Z zone name, %z numeric offset, %V/%G ISO
week and week-based year, %F (%Y-%m-%d), %T (%H:%M:%S), %s (Unix timestamp, POSIX), %% a literal
percent. %c, %x and %X are locale-dependent — avoid them for anything machine-readable.
asctime and ctime still exist and still return a static buffer with a trailing newline; both are deprecated
in C23. Use strftime.
difftime and Comparing Times
#include <stdio.h>
#include <time.h>
int main(void)
{
struct tm a = { .tm_year = 2026 - 1900, .tm_mon = 0, .tm_mday = 1, .tm_isdst = -1 };
struct tm b = { .tm_year = 2026 - 1900, .tm_mon = 11, .tm_mday = 31, .tm_isdst = -1 };
time_t start = mktime(&a);
time_t end = mktime(&b);
if (start == (time_t)-1 || end == (time_t)-1) {
return 1;
}
// difftime is the ONLY portable way to subtract two time_t values -- time_t
// need not be an integer count of seconds, and plain subtraction can overflow.
double seconds = difftime(end, start);
printf("%.0f seconds = %.1f days\n", seconds, seconds / 86400.0);
return 0;
}
Measuring Elapsed Time
Which clock to use depends on the question:
#include <stdio.h>
#include <time.h>
static void workload(void)
{
volatile double total = 0.0;
for (int i = 1; i < 5000000; ++i) {
total += 1.0 / i;
}
}
int main(void)
{
// WALL-CLOCK time: what the user experiences. Includes time spent blocked.
struct timespec wall_start, wall_end;
if (timespec_get(&wall_start, TIME_UTC) != TIME_UTC) {
return 1;
}
// CPU time: what the process actually consumed. Excludes blocking, and on a
// multi-threaded program may EXCEED the wall time.
clock_t cpu_start = clock();
workload();
clock_t cpu_end = clock();
if (timespec_get(&wall_end, TIME_UTC) != TIME_UTC) {
return 1;
}
double wall = (double)(wall_end.tv_sec - wall_start.tv_sec)
+ (double)(wall_end.tv_nsec - wall_start.tv_nsec) / 1e9;
double cpu = (double)(cpu_end - cpu_start) / CLOCKS_PER_SEC;
printf("wall %.4f s, cpu %.4f s\n", wall, cpu);
return 0;
}
Two caveats that invalidate naive measurements:
-
TIME_UTCis a wall clock, and wall clocks move. NTP adjustments, and manual changes, can make the end time earlier than the start. For durations, use a monotonic clock: C23’s optionalTIME_MONOTONIC, orclock_gettime(CLOCK_MONOTONIC, …)on POSIX andQueryPerformanceCounteron Windows. -
clock()measures processor time, so it under-reports anything I/O-bound and over-reports a program using several cores.
For benchmarking, prefer a purpose-built harness over hand-rolled timing: run the workload many times, discard warm-up, and report a distribution rather than one number. See Performance.
See Also
-
Standard Library Overview — where
<time.h>fits, and the reentrancy problem its static buffers illustrate. -
Performance — measurement methodology and profiling tools.
-
Strings and Text Processing — locales, which
strftimeand%cdepend on. -
Threads —
thrd_sleepand timed waits, which take astruct timespec. -
C++: Dates, Times, and Chrono —
<chrono>gives these same facilities type-safe durations and calendar types.