Time Conversion Functions in Linux

Time Conversion Functions in Linux
Chapter 10 Series — Part 4: ctime, gmtime, localtime, mktime & struct tm
struct tm
Key Structure
4
Functions Covered
Two-way Conversion

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

struct tm broken-down time ctime() gmtime() localtime() mktime() asctime() DST

🔗 The Conversion Map

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()

📈 struct tm — Broken-Down Time

“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.

🌐 gmtime() vs localtime()

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() — Quick One-Liner Conversion

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() — Reverse: struct tm → time_t

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() — struct tm to Printable String

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.

Next: strftime() & strptime() → All Linux Posts

Leave a Reply

Your email address will not be published. Required fields are marked *