Skip to content
Home » All Posts » A Comprehensive Guide to Working with Time in C

A Comprehensive Guide to Working with Time in C

Introduction

Time related problems may sound trivial at first but they could be quite tricky and complicated to get right in programming languages such as C or C++. Managing time involves understanding various functions, data structures, and conventions provided by the standard library and other libraries. Whether you’re building a real-time application, handling timestamps in a database, or implementing time-sensitive operations, a solid grasp of time-related concepts in C is essential. In this blog, we will explore the toolsets standard C library provides to us to deal with time.

Unix Timestamp In Seconds

A Unix Timestamp (or epoch) refers to expressing a timestamp by the number of seconds since 1970-01-01T00:00:00. For example, 1712670897 represents the timestamp “2024-04-09T13:54:57” and is always in GMT+0 or UTC time zone. To express in a different time zone, you will have to do further conversion.

It is fairly simple to obtain Unix Timestamp in C code:

#include <stdio.h>
#include <time.h>

int main()
{
    time_t now = time(NULL); /* Get current time in Unix Timestamp */
    printf("Current time in Unix Timestamp: %ld\n", now);
    return 0;
}

Format Unix Timestamp as String

A Unix Timestamp is basically a big number that is good for computers to do time computations, but not straightforward to humans to understand. There are several ways to convert it to different forms of string representations in C. Since time(NULL) returns Unix Timestamp in UTC (GMT+0) time zone, the output of ctime() is also expressed in UTC time zone.

#include <stdio.h>
#include <time.h>

int main()
{
    time_t now = time(NULL);
    printf("Current time as string in UTC (GMT0): %s", ctime(&now));
    return 0;
}

Break Down time_t into Year, Month, Day, Hour…etc

What if you are only interested in the year, or perhaps the month from a Unix Timestamp? What about time zones? What if you would like to format the timestamp in your own string style? In this case, you need to break down a Unix Timestamp into individual components to work with. In C language, you need to convert time_t to a struct_tm structure.

#include <stdio.h>
#include <time.h>

int main() {
    time_t now = time(NULL);
    struct tm *local = localtime(&now);

    printf("Local Time: %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;
}

Compare 2 Timestamps Expressed in Difference Formats

There are several ways to represent a timestamp. Some have time zone, some without, while some have time zone offset and Daylight Saving Time information. These are what makes time problems difficult in programming because you have to be aware of all these to correctly handle time. For example, can you calculate the difference in nanoseconds between these 2 timestamps:

  • 2025-07-27T10:15:30.123456789Z
  • 2025-08-27 18:15:30.223456700+08:00

To compare, we need to normalize both strings to nanoseconds since epoch and compare in numbers instead. Timestamps normally follow ISO8601 standard and we can write a simple parser to handle them. Why in nanoseconds? Nanosecond is the most precise measurement of time. Milliseconds and microseconds are also common but they are not as precise as nanoseconds. Picoseconds (1 ^12), on the other hand, are not common to compare timestamps.

The C code below parses a given timestamp string and outputs nanoseconds since epoch. or 0 on error. The idea is to first use strptime to do a check on the timestamp format and breaks it down into struct tm. This function returns a pointer to the last consumed input character or NULL on format error. If not NULL, the function continue to parse the fractional nanoseconds. The function then checks for Z for UTC or a time zone offset. Lastly, the function converts struct tm to number of seconds since epoch, converts it to nano seconds, offsets it with the time zone offset and adds the fractional nanoseconds to get the final value.

#include <ctype.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

int64_t parse_iso8601_to_ns(const char *s, int *ok) {
    *ok = 0;
    struct tm tm = {0};
    tm.tm_isdst = -1;

    /* Try "YYYY-MM-DDTHH:MM:SS" */
    const char *p = strptime(s, "%Y-%m-%dT%H:%M:%S", &tm);
    if (!p)
    {
        /* If not, try "YYYY-MM-DD HH:MM:SS" */
        p = strptime(s, "%Y-%m-%d %H:%M:%S", &tm);
        if (!p)
        return 0;
    }

    /* Fractional seconds (up to 9 digits -> ns) */
    long nsec = 0;
    if (*p == '.')
    {
        ++p;
        int digits = 0;
        long frac = 0;
        while (isdigit((unsigned char)*p) && digits < 9)
        {
            frac = frac * 10 + (*p - '0');
            ++p;
            ++digits;
        }
        /* Skip any extra digits beyond nanoseconds */
        while (isdigit((unsigned char)*p))
            ++p;
        /* Scale to nanoseconds */
        for (; digits < 9; ++digits)
            frac *= 10;
        nsec = frac;
    }

    /* Time zone: Z or ±HH[:MM] or ±HHMM */
    int tz_off_sec = 0;  /* seconds to add to go from local to UTC (later we subtract) */
    if (*p == 'Z' || *p == 'z')
    {
        ++p; /* UTC */
    }
    else if (*p == '+' || *p == '-')
    {
        int sign = (*p == '-') ? -1 : 1;
        ++p;
        int hh = 0, mm = 0;
        if (isdigit((unsigned char)p[0]) && isdigit((unsigned char)p[1]))
        {
            hh = (p[0]-'0')*10 + (p[1]-'0'); p += 2;
        }
        else
        {
            return 0;
        }
        if (*p == ':')
            ++p;
        if (isdigit((unsigned char)p[0]) && isdigit((unsigned char)p[1]))
        {
            mm = (p[0]-'0')*10 + (p[1]-'0'); p += 2;
        }
        tz_off_sec = sign * (hh * 3600 + mm * 60);
    }

    /* Convert to epoch seconds (as UTC) then apply offset */
    time_t sec = timegm(&tm);
    if (sec == (time_t)-1)
        return 0;

    /* The string says "local time with offset = UTC + offset". */
    /* To get UTC epoch seconds, subtract the offset. */
    int64_t total_ns = ((int64_t)sec - (int64_t)tz_off_sec) * 1000000000LL + (int64_t)nsec;
    *ok = 1;
    return total_ns;
}

Measure a Function Execution Timing

In addition to working with timestamps in string or Unix formats, we can also use timing utilities in C to compute a function’s execution time. This is done by taking a CPU monotonic time before and after a function call and subtracts them to get a time difference. Nanosecond is the most desirable unit of measure for execution times. See example below:

#include <stdio.h>
#include <time.h>
#include <stdint.h>

static inline int64_t timespec_diff_ns(struct timespec a, struct timespec b)
{
    time_t sec  = b.tv_sec  - a.tv_sec;
    long   nsec = b.tv_nsec - a.tv_nsec;
    if (nsec < 0)
    {
        --sec;
        nsec += 1000000000L;
    }
    return (int64_t)sec * 1000000000LL + nsec;
}

int main(void) {
    struct timespec t0, t1;
    clock_gettime(CLOCK_MONOTONIC, &t0);
    /* ... do work ... */
    clock_gettime(CLOCK_MONOTONIC, &t1);

    printf("Δt = %lld ns\n", (long long)timespec_diff_ns(t0, t1));
    return 0;
}

Summary

This blog provides a quick, practical tour of time handling in C (time_t, struct tm, struct timespec), formatting/parsing with strftime/strptime, UTC↔local conversions and I hope you will find them useful in your line of work.

Join the conversation

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