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 timeCLOCK_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:
- Standard Library: Part of ISO C standard, not just POSIX
- Portability: Works on both POSIX and non-POSIX systems
- Precision: Nanosecond precision like
clock_gettime() - Simplicity: Single time base (UTC) reduces complexity
Function Signature and Usage
int timespec_get(struct timespec *ts, int base);
Parameters:
ts: Pointer tostruct timespecto store the resultbase: Time base specification (currently onlyTIME_UTC)
Return Value:
- Returns
baseon 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:
- Null Check: Verify the
tsparameter is valid - Base Check: Only
TIME_UTCis supported (per C11 standard) - Get Time: Use RTEMS’
_TOD_Get()function - 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_tfor 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()andclock_gettime()provide nanosecond precision - Monotonic Guarantees: Use
clock_gettime(CLOCK_MONOTONIC)for timing measurements - Simplicity: Use
time()for basic timestamp needs