Time APIs in C: From time() to timespec_get()

Time measurement in computing has evolved from simple second-based timestamps to high-precision APIs. This guide covers the main time functions available in C, focusing on timespec_get() - a C11 standard function and its implementation in real-time systems.

Why Multiple Time APIs?

Time measurement serves different purposes: file timestamps, process scheduling, timeouts, performance measurement, and real-time applications. Each use case has different precision and reliability requirements, leading to multiple APIs optimized for specific needs.

Core Time APIs

time() - The Basic Function

The time() function is the simplest way to get current time:

#include <time.h>

time_t current_time = time(NULL);
printf("Seconds since epoch: %ld\n", current_time);

Properties:

  • Precision: Seconds only
  • Standard: C89
  • Return Type: time_t
  • Use Cases: Basic timestamps, file times

Limitations:

  • No sub-second precision
  • Limited to UTC time
  • Y2038 problem on 32-bit systems

gettimeofday() - Microsecond Precision

POSIX added gettimeofday() for better precision:

#include <sys/time.h>

struct timeval tv;
gettimeofday(&tv, NULL);

printf("Seconds: %ld, Microseconds: %ld\n", 
       tv.tv_sec, tv.tv_usec);

Properties:

  • Precision: Microseconds (10^-6 seconds)
  • Standard: POSIX (deprecated in POSIX.1-2008)
  • Structure: struct timeval
  • Timezone Support: Optional timezone parameter

Improvements:

  • Microsecond precision
  • Timezone awareness

Limitations:

  • Deprecated in favor of clock_gettime()
  • Platform-specific timezone handling
  • Not monotonic (can go backwards)

clock_gettime() - The Current Standard

clock_gettime() is the modern standard for time retrieval:

#include <time.h>

struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);

printf("Seconds: %ld, Nanoseconds: %ld\n", 
       ts.tv_sec, ts.tv_nsec);

Clock Types:

  • CLOCK_REALTIME: Wall clock time (can be adjusted)
  • CLOCK_MONOTONIC: Monotonic time (unaffected by clock adjustments)
  • CLOCK_PROCESS_CPUTIME_ID: Process CPU time
  • CLOCK_THREAD_CPUTIME_ID: Thread CPU time

Properties:

  • Precision: Nanoseconds (10^-9 seconds)
  • Standard: POSIX.1-2001
  • Structure: struct timespec
  • Multiple Clock Sources: Different clock types available

timespec_get(): The C11 Standard Function

What is timespec_get()?

C11 introduced timespec_get() as a portable, high-precision time function that works without POSIX extensions:

#include <time.h>

struct timespec ts;
int result = timespec_get(&ts, TIME_UTC);

if (result == TIME_UTC) {
    printf("Current time: %ld.%09ld seconds since epoch\n", 
           ts.tv_sec, ts.tv_nsec);
}

Why timespec_get()?

timespec_get() addresses several issues:

  1. Standard Library: Part of ISO C standard, not just POSIX
  2. Portability: Works on both POSIX and non-POSIX systems
  3. Precision: Nanosecond precision like clock_gettime()
  4. Simplicity: Single time base (UTC) reduces complexity

Function Signature and Usage

int timespec_get(struct timespec *ts, int base);

Parameters:

  • ts: Pointer to struct timespec to store the result
  • base: Time base specification (currently only TIME_UTC)

Return Value:

  • Returns base on success
  • Returns 0 on failure

Implementation: RTEMS Integration

The timespec_get() implementation in RTEMS shows how standard library functions work with system calls. Here’s the implementation:

/* From cpukit/posix/src/timespecget.c */

int timespec_get(struct timespec *ts, int base)
{
  if( !ts ){
    return 0;
  }

  if ( base  != TIME_UTC ) {
    return 0;
  }

  _TOD_Get(ts);
  return base;
}

How It Works

The implementation is straightforward:

  1. Null Check: Verify the ts parameter is valid
  2. Base Check: Only TIME_UTC is supported (per C11 standard)
  3. Get Time: Use RTEMS’ _TOD_Get() function
  4. Return: Return the base parameter on success, 0 on failure

RTEMS Time Infrastructure

The implementation uses RTEMS’ time system:

// _TOD_Get() is implemented as:
static inline void _TOD_Get(struct timespec *tod)
{
  _Timecounter_Nanotime(tod);
}

This provides:

  • Hardware Abstraction: Works across different hardware
  • Nanosecond Precision: Native nanosecond resolution
  • Real-time Guarantees: Deterministic behavior for real-time systems

The timespec Structure

All modern high-precision time APIs use the struct timespec:

struct timespec {
    time_t tv_sec;  // Seconds since epoch
    long tv_nsec;   // Nanoseconds (0-999,999,999)
};

This structure provides:

  • Range: Same as time_t for seconds
  • Precision: Nanosecond granularity

Comparing Time APIs

Precision Comparison

Function Precision Standard Monotonic Option
time() 1 second C89 No
gettimeofday() 1 microsecond POSIX (deprecated) No
clock_gettime() 1 nanosecond POSIX Yes
timespec_get() 1 nanosecond C11 No

Choosing the Right API

Use Cases

For Basic Timestamps:

// Simple logging
time_t now = time(NULL);
printf("Log entry at %s", ctime(&now));

For High-Precision Measurements:

// Performance measurement
struct timespec start, end;
timespec_get(&start, TIME_UTC);
expensive_operation();
timespec_get(&end, TIME_UTC);

long duration_ns = (end.tv_sec - start.tv_sec) * 1000000000L + 
                   (end.tv_nsec - start.tv_nsec);

For Monotonic Timing (POSIX systems):

// Timeout implementation
struct timespec timeout_start;
clock_gettime(CLOCK_MONOTONIC, &timeout_start);

while (!condition_met()) {
    struct timespec now;
    clock_gettime(CLOCK_MONOTONIC, &now);
    
    if ((now.tv_sec - timeout_start.tv_sec) > TIMEOUT_SECONDS) {
        return TIMEOUT_ERROR;
    }
    // Continue waiting...
}

For Portable High-Precision Code:

// Cross-platform timing
#ifdef __STDC_VERSION__ >= 201112L
    struct timespec ts;
    if (timespec_get(&ts, TIME_UTC) == TIME_UTC) {
        // Use high-precision time
    }
#else
    // Fallback to time()
    time_t t = time(NULL);
#endif

Implementation Notes

Handling Clock Changes

System clock adjustments can cause timing issues:

// Robust timeout implementation
int wait_with_timeout(int timeout_ms) {
    struct timespec deadline;
    timespec_get(&deadline, TIME_UTC);
    
    // Add timeout to current time
    deadline.tv_sec += timeout_ms / 1000;
    deadline.tv_nsec += (timeout_ms % 1000) * 1000000;
    
    // Handle nanosecond overflow
    if (deadline.tv_nsec >= 1000000000) {
        deadline.tv_sec++;
        deadline.tv_nsec -= 1000000000;
    }
    
    while (!condition_ready()) {
        struct timespec now;
        timespec_get(&now, TIME_UTC);
        
        // Check if deadline passed
        if (now.tv_sec > deadline.tv_sec ||
            (now.tv_sec == deadline.tv_sec && now.tv_nsec >= deadline.tv_nsec)) {
            return TIMEOUT;
        }
        
        usleep(1000); // Sleep 1ms
    }
    
    return SUCCESS;
}

Conclusion

The evolution from time() to timespec_get() reflects growing precision requirements in modern computing. While clock_gettime() remains preferred on POSIX systems due to its flexibility, timespec_get() provides a portable solution for high-precision applications.

Choose based on your requirements:

  • Portability: Choose timespec_get() for cross-platform code
  • Precision: Both timespec_get() and clock_gettime() provide nanosecond precision
  • Monotonic Guarantees: Use clock_gettime(CLOCK_MONOTONIC) for timing measurements
  • Simplicity: Use time() for basic timestamp needs