The Problem: time_t is Not Human-Friendly
A raw time_t value like 1747267215 is efficient for computers but meaningless to a user. Linux provides a set of library functions to convert between time_t integers and human-understandable broken-down time structures, and also to printable strings. This post covers all of them.
Keywords in This Post
Think of the conversion functions as bridges between three representations of time:
| From | Direction | To | Function |
|---|---|---|---|
| time_t | → | Printable string (local time) | ctime() |
| time_t | → | struct tm (UTC) | gmtime() |
| time_t | → | struct tm (local time) | localtime() |
| struct tm | → | time_t | mktime() |
| struct tm | → | Printable string | asctime() |
“Broken-down time” means the date and time split into individual integer fields. Think of it like disassembling a date: instead of the single number 1747267215, you have separate fields for year, month, day, hour, minute, second, etc.
#include <time.h>
struct tm {
int tm_sec; /* Seconds: 0 to 60 (60 allows for a leap second) */
int tm_min; /* Minutes: 0 to 59 */
int tm_hour; /* Hours: 0 to 23 */
int tm_mday; /* Day of month: 1 to 31 */
int tm_mon; /* Month: 0 (January) to 11 (December) — note: 0-indexed! */
int tm_year; /* Years since 1900 — add 1900 to get real year */
int tm_wday; /* Day of week: 0 (Sunday) to 6 (Saturday) */
int tm_yday; /* Day in year: 0 (1-Jan) to 365 */
int tm_isdst; /* DST flag: >0 DST in effect, 0 not in effect, <0 unknown */
};
Common student traps with struct tm:
| Field | The Trap | Correct Usage |
|---|---|---|
| tm_year | It is NOT the actual year. Value 125 = year 2025. | actual_year = tm.tm_year + 1900; |
| tm_mon | January is 0, not 1. December is 11. | actual_month = tm.tm_mon + 1; |
| tm_sec | Max is 60 (not 59) to accommodate leap seconds. | Check for value 60 when validating. |
Both functions take a pointer to a time_t and return a pointer to a struct tm. The difference is which timezone the broken-down time is expressed in.
#include <time.h>
struct tm *gmtime(const time_t *timep); /* Returns broken-down UTC time */
struct tm *localtime(const time_t *timep); /* Returns broken-down local time */
/* Both return NULL on error */
#include <time.h>
#include <stdio.h>
int main(void)
{
time_t t = time(NULL);
struct tm *utc_tm;
struct tm *local_tm;
utc_tm = gmtime(&t);
printf("UTC : %04d-%02d-%02d %02d:%02d:%02d\n",
utc_tm->tm_year + 1900,
utc_tm->tm_mon + 1,
utc_tm->tm_mday,
utc_tm->tm_hour,
utc_tm->tm_min,
utc_tm->tm_sec);
local_tm = localtime(&t);
printf("Local : %04d-%02d-%02d %02d:%02d:%02d\n",
local_tm->tm_year + 1900,
local_tm->tm_mon + 1,
local_tm->tm_mday,
local_tm->tm_hour,
local_tm->tm_min,
local_tm->tm_sec);
return 0;
}
Warning — Shared static buffer: Both gmtime() and localtime() return a pointer to the same internal static buffer. If you call gmtime() and then localtime() without copying the result, the first call’s result is overwritten. Always copy the struct if you need both:
struct tm gm_copy = *gmtime(&t);
Or use the thread-safe variants: gmtime_r() and localtime_r().
ctime() is the fastest route from a time_t to a printable string. No intermediate structures needed. It returns a 26-character string in a fixed format, automatically adjusted for the local timezone.
#include <time.h>
char *ctime(const time_t *timep);
/* Returns pointer to a static string, or NULL on error */
/* Example output: "Wed May 15 10:30:00 2025\n\0" (always includes newline) */
#include <time.h>
#include <stdio.h>
int main(void)
{
time_t t = time(NULL);
/* ctime output already ends in \n so no need for extra \n in printf */
printf("Current time: %s", ctime(&t));
return 0;
}
Limitation: You cannot control the format of the output from ctime(). It always produces the same fixed format. For custom date/time formatting, use strftime() (covered in the next post).
mktime() is the inverse of localtime(). You fill in a struct tm with a specific date/time, and mktime() returns the corresponding time_t (seconds since Epoch). This is very useful for date arithmetic.
#include <time.h>
time_t mktime(struct tm *timeptr);
/* Returns time_t on success, or (time_t)-1 on error */
mktime() auto-normalises out-of-range fields.
If you set tm_hour = 25, mktime doesn’t fail — it carries over 1 extra hour and sets the day to tomorrow. This makes date arithmetic easy:
#include <time.h>
#include <stdio.h>
int main(void)
{
struct tm t = {0};
/* Build: 1 January 2025 */
t.tm_year = 2025 - 1900; /* Years since 1900 */
t.tm_mon = 0; /* January (0-indexed) */
t.tm_mday = 1;
t.tm_hour = 0;
t.tm_min = 0;
t.tm_sec = 0;
t.tm_isdst = -1; /* Let mktime figure out DST */
time_t jan1 = mktime(&t);
/* What date is 100 days later? Use the auto-normalise trick */
t.tm_mday += 100; /* tm_mday = 101 — out of range, but OK */
time_t future = mktime(&t);/* mktime normalises it correctly */
printf("100 days after Jan 1: %s", ctime(&future));
return 0;
}
The tm_isdst field tells mktime() how to handle Daylight Saving Time:
| tm_isdst value | Meaning |
|---|---|
| 0 | Treat this as standard time; ignore DST |
| > 0 | Treat this as DST time |
| -1 | Recommended: let mktime() determine if DST is in effect |
asctime() converts a struct tm to the same fixed-format string as ctime(). The difference is the input — ctime() takes a time_t; asctime() takes a struct tm *. Use it after gmtime() to print UTC time without having to format it manually.
#include <time.h>
#include <stdio.h>
int main(void)
{
time_t t = time(NULL);
/* Print time in UTC using gmtime + asctime */
struct tm *utc = gmtime(&t);
printf("UTC time: %s", asctime(utc));
/* Print local time using localtime + asctime */
struct tm *local_t = localtime(&t);
printf("Local : %s", asctime(local_t));
return 0;
}
Shared buffer warning (same as gmtime/localtime): All of ctime(), gmtime(), localtime(), and asctime() may share the same underlying static buffer. Calling one after another can overwrite previous results. Copy your struct or string if you need to preserve results across multiple calls.
Up Next in This Series
Part 5 covers strftime() and strptime() — for custom-formatted date/time output and parsing.
