C time.h Library
The <time.h> header is the C standard library’s toolkit for working with dates, calendar time, and elapsed processing time. It gives you a small set of types and functions to read the current date and time, break a timestamp down into year/month/day/hour fields, format it for display, and measure how long parts of your program take to run. Almost every non-trivial C program that logs events, schedules work, or benchmarks performance eventually reaches for time.h.
Overview / How it works
Under the hood, most systems track time as a single number: the count of seconds that have elapsed since the Unix Epoch, midnight, January 1, 1970 (UTC). C exposes this as the opaque type time_t. It’s typically a signed integer (commonly a 64-bit integer on modern systems, though older systems used 32 bits, which caused the famous “Year 2038 problem” for 32-bit signed types). Because time_t is just a number, it’s easy to store, compare, and subtract, but it is not human-readable by itself.
To make a timestamp human-readable, C provides struct tm, which breaks a time_t into individual calendar fields: year, month, day, hour, minute, second, day of week, and day of year. Converting between the two representations happens through functions like localtime() (which also adjusts for your system’s local timezone and daylight saving rules) and gmtime() (which gives you the same breakdown in UTC, ignoring timezone). The reverse conversion, from a filled-in struct tm back to a time_t, is done by mktime().
Separately, time.h also offers clock(), which measures approximate processor time used by your program (not wall-clock time), reported in units of CLOCKS_PER_SEC per second. This is useful for rough performance measurements of CPU-bound code, though it does not account for time spent waiting on I/O or other processes.
A key internal detail: functions like localtime(), gmtime(), asctime(), and ctime() typically return a pointer into a single, statically allocated struct tm or character buffer owned by the library. Every call overwrites the same memory. This means the pointer returned by one call becomes invalid (or silently changes) the moment you call any of these functions again, and none of them are safe to use from multiple threads simultaneously without extra synchronization.
Syntax
#include <time.h>
time_t t = time(NULL); /* current calendar time */
struct tm *local = localtime(&t); /* break down to local time fields */
struct tm *utc = gmtime(&t); /* break down to UTC fields */
time_t back = mktime(local); /* struct tm -> time_t */
double secs = difftime(t2, t1); /* seconds between two time_t values */
clock_t c = clock(); /* processor time used so far */
char buf[64];
strftime(buf, sizeof(buf), "%Y-%m-%d", local); /* format struct tm as text */
time_t time(time_t *arg)— returns the current calendar time as seconds since the Epoch. PassNULLif you don’t need the out-parameter, or pass the address of atime_tto also store the result there.struct tm *localtime(const time_t *t)— converts atime_tto a broken-down local time. Returns a pointer to a statically allocated struct (reused on every call).struct tm *gmtime(const time_t *t)— same aslocaltime()but in UTC, with no timezone adjustment.time_t mktime(struct tm *tm)— converts a broken-down local time back into atime_t. Also normalizes out-of-range fields (e.g.tm_mday = 32rolls into the next month).double difftime(time_t t2, time_t t1)— returnst2 - t1in seconds as adouble. Prefer this over raw subtraction for portability.clock_t clock(void)— returns processor time used by the program; divide byCLOCKS_PER_SECto get seconds.size_t strftime(char *s, size_t max, const char *fmt, const struct tm *tm)— formats astruct tminto a text buffer using format specifiers similar toprintf.char *ctime(const time_t *t)/char *asctime(const struct tm *tm)— quick, fixed-format (25-character, English, US-style) time strings ending in a newline.
struct tm fields
| Field | Meaning | Range |
|---|---|---|
tm_sec |
Seconds after the minute | 0-60 (61 allows a leap second) |
tm_min |
Minutes after the hour | 0-59 |
tm_hour |
Hours since midnight | 0-23 |
tm_mday |
Day of the month | 1-31 |
tm_mon |
Months since January | 0-11 (0 = January!) |
tm_year |
Years since 1900 | e.g. 126 for 2026 |
tm_wday |
Days since Sunday | 0-6 |
tm_yday |
Days since Jan 1 | 0-365 |
tm_isdst |
Daylight saving flag | >0, 0, or <0 |
Examples
Example 1: Getting and printing the current time
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now;
time(&now);
printf("Seconds since Epoch: %ld\n", (long)now);
printf("Current time: %s", ctime(&now));
return 0;
}
Output (values will differ every time you run it, since it reflects the real current time):
Seconds since Epoch: 1784419200
Current time: Sat Jul 18 12:00:00 2026
time(&now) stores the raw Epoch count in now. ctime() takes the address of that time_t and produces a ready-to-print, fixed-format string — note that ctime()‘s string already ends with a newline, so the printf format string doesn’t add another one.
Example 2: Breaking down and formatting time with struct tm and strftime()
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now = time(NULL);
struct tm *local = localtime(&now);
char buffer[64];
strftime(buffer, sizeof(buffer), "%A, %B %d, %Y - %H:%M:%S", local);
printf("Formatted date/time: %s\n", buffer);
printf("Year: %d, Month: %d, Day: %d\n",
local->tm_year + 1900, local->tm_mon + 1, local->tm_mday);
return 0;
}
Output (exact date/time text will vary depending on when you run it):
Formatted date/time: Saturday, July 18, 2026 - 14:32:07
Year: 2026, Month: 7, Day: 18
localtime() fills a struct tm with local calendar fields. strftime() then renders it as text using format codes (%A full weekday name, %B full month name, %Y 4-digit year, and so on). Note the + 1900 and + 1 corrections: tm_year counts from 1900, and tm_mon counts months from 0.
Example 3: Measuring elapsed CPU time with clock()
#include <stdio.h>
#include <time.h>
int main(void) {
clock_t start = clock();
long sum = 0;
for (long i = 0; i < 200000000L; i++) {
sum += i % 7;
}
clock_t end = clock();
double elapsed = (double)(end - start) / CLOCKS_PER_SEC;
printf("Sum: %ld\n", sum);
printf("Time taken: %f seconds\n", elapsed);
return 0;
}
Output (the sum is fixed, but the elapsed time depends on your CPU speed and compiler optimizations):
Sum: 599999994
Time taken: 0.550000 seconds
clock() returns clock ticks used by the process. Calling it once before and once after the work, subtracting, and dividing by CLOCKS_PER_SEC converts the tick count into seconds. This measures CPU time, not real-world wall-clock time — if the process is paused or waiting on disk/network I/O, that time is not counted.
Under the hood: how a timestamp becomes text
- The operating system tracks time internally (often as seconds and nanoseconds since the Epoch, using a hardware clock or NTP-synced software clock).
time()asks the OS for the current value and returns it as atime_t, an opaque arithmetic type — internally just an integer count of seconds.localtime()takes that integer and, using the system’s timezone database, works out what “wall clock” date and time it corresponds to where the program is running, filling in each field of astruct tm(year, month, day, and so on) and applying daylight saving adjustments if applicable.strftime()walks your format string character by character. When it hits an ordinary character, it copies it. When it hits a%specifier, it looks up the matching field in thestruct tmand writes the formatted text (padding numbers, spelling out month/weekday names from the current locale, etc.) into your buffer.- If the destination buffer isn’t big enough,
strftime()returns0and leaves the buffer’s contents undefined — always size buffers generously and check the return value in production code.
Common Mistakes
Mistake 1: Forgetting tm_mon is zero-based.
/* Wrong: sets month to July when tm_mon = 6 means July, not tm_mon = 7 */
struct tm t = {0};
t.tm_mon = 7; /* this is actually August! */
t.tm_mday = 18;
t.tm_year = 2026 - 1900;
Because tm_mon counts from 0 (January = 0), setting it to 7 actually means August. To represent July, use 6:
struct tm t = {0};
t.tm_mon = 6; /* July */
t.tm_mday = 18;
t.tm_year = 2026 - 1900;
Mistake 2: Reusing the pointer from localtime()/ctime() after a second call.
/* Wrong: both pointers alias the same static buffer */
time_t t1 = time(NULL);
struct tm *a = localtime(&t1);
time_t t2 = t1 + 3600;
struct tm *b = localtime(&t2);
/* by now, *a has ALSO changed, because a and b point to the same memory! */
printf("%d vs %d\n", a->tm_hour, b->tm_hour); /* prints the same value twice */
Fix it by copying the struct out immediately after each call, before making the next call:
time_t t1 = time(NULL);
struct tm a = *localtime(&t1); /* copy the struct, not just the pointer */
time_t t2 = t1 + 3600;
struct tm b = *localtime(&t2);
printf("%d vs %d\n", a.tm_hour, b.tm_hour);
Mistake 3: Using clock() to measure wall-clock/real-world elapsed time. clock() reports CPU time consumed, which can be much less than real elapsed time if the program sleeps or waits on I/O. For measuring real elapsed time (e.g. how long a network request took), use a wall-clock API such as time() for second-level precision, or platform-specific high-resolution timers for finer precision.
Best Practices
- Always use
difftime()instead of subtracting twotime_tvalues directly — it’s portable even iftime_t‘s underlying representation isn’t a simple integer. - Copy the
struct tmreturned bylocaltime()/gmtime()into your own variable immediately if you need to keep it around or call these functions again before you’re done with it. - Check
strftime()‘s return value; a return of0means the buffer was too small and its contents are unreliable. - Prefer
strftime()overasctime()/ctime()for anything beyond quick debugging — the fixed format of the latter two can’t be localized or customized, and they can overflow on some non-standard years. - Remember
tm_yearis years since 1900 andtm_monis zero-based; add/subtract accordingly every time you touch these fields. - Use
clock()only for coarse, CPU-bound benchmarking within a single run; don’t rely on it for cross-run wall-clock comparisons or multi-threaded timing.
Practice Exercises
- Write a program that prints the current date in the format
YYYY-MM-DDusingstrftime(). - Write a program that builds a
struct tmfor a specific date you choose (e.g. December 25, 2026), converts it to atime_twithmktime(), and then usesdifftime()to print how many days remain until that date from “now”. - Write a program that times how long it takes to sort an array of 100,000 random integers (using any sorting approach) with
clock(), printing the elapsed time in seconds.
Summary
time_tstores calendar time as seconds since the Unix Epoch;struct tmbreaks it into readable fields.time()gets the current time;localtime()/gmtime()convert it to broken-down local/UTC time;mktime()converts back.strftime()is the flexible, locale-aware way to format dates and times as text; prefer it overctime()/asctime()for anything beyond quick debugging.difftime()safely computes the number of seconds between twotime_tvalues.clock()measures approximate CPU time used by your process, useful for rough performance benchmarking, but it is not wall-clock time.- Functions like
localtime()andctime()return pointers to shared static storage — copy the data out before calling them again. tm_yearis years since 1900 andtm_monis zero-based; both are common sources of off-by-one bugs.
