Knowledge Base

Everything you need to know and understand to develop V2X applications.

denm-generator.py
msg = self.generate_denm()
future = self.send_request(msg)
future.add_done_callback(self.request_completed)

ITS Time API Developer Guide

V2X messages need time — but not “PC time” and not even plain UTC. They need ETSI ITS time, which is a TAI-based time scale starting at 2004-01-01 00:00:00 UTC and used in ETSI ITS-G5 / C-ITS messages (see ETSI TS 102 894-2).

On the cube:evk this ITS time is derived from GNSS → chrony → system clock → cube:time, and you can read it from your own applications using either the C API or the C++ API.

This chapter explains:

  1. What ITS time is and why we use it
  2. How to check if the cube is synchronized
  3. How to read the current ITS timestamp (C and C++)
  4. How to get the current TAI–UTC offset

1. Background: ITS time on cube:evk

As described in the Time Synchronization chapter, the device runs chrony and locks the system clock to the GNSS PPS. This gives us:

  • a stable TAI time base
  • a known TAI–UTC offset (leap seconds)
  • and therefore a correct ITS time (TAI seconds since 2004-01-01)

The CLI tool:

cube-timectl show

shows exactly the same information that you can get from code:

  • current ITS timestamp
  • whether the system time is synchronized
  • current TAI–UTC offset
  • leap second status

The library cube:time is just the programmatic way to get this.


2. C API (time.h)

If you’re writing C or simple C++ code, use the two helper functions from time.h:

/**
* \brief get current ITS timestamp in milliseconds
* \return milliseconds since ITS epoch start (-1 if not synchronized)
*/
int64_t cube_time_now_ms();
/**
* \brief get current ITS timestamp in microseconds
* \return microseconds since ITS epoch start (-1 if not synchronized)
*/
int64_t cube_time_now_us();

Example

#include <cube/time.h>
#include <stdio.h>
int main(void)
{
int64_t its_ms = cube_time_now_ms();
if (its_ms < 0) {
printf("ITS time not synchronized yet\n");
return 1;
}
printf("ITS time: %lld ms since 2004-01-01T00:00:00Z\n", (long long)its_ms);
return 0;
}

Key points:

  • return value < 0not synchronized (GNSS / chrony not locked yet)
  • value is since ITS epoch (2004-01-01), not UNIX epoch (1970-01-01)
  • perfect for filling ITS timestamps in messages in pure C

3. C++ API (clock.hpp)

For modern C++ code we provide a proper C++ clock that behaves like a standard C++ clock:

struct cube::time::EtsiItsClock {
static time_point now();
static std::optional<time_point> maybe_now();
static bool is_synchronized();
static duration utc_offset() noexcept;
static time_point from_system_clock(std::chrono::system_clock::time_point);
};

This gives you:

  • now() — get ITS time (assumes we are synchronized)
  • maybe_now() — get ITS time only if synchronized
  • is_synchronized() — quick boolean check
  • utc_offset() — how far ITS is currently offset from UTC
  • from_system_clock(...) — convert a system_clock::time_point to ITS

Example: check and print ITS time

#include <cube/time/clock.hpp>
#include <chrono>
#include <iostream>
int main()
{
using namespace std::chrono;
if (!cube::time::EtsiItsClock::is_synchronized()) {
std::cout << "ITS clock is NOT synchronized yet\n";
return 0;
}
auto now_its = cube::time::EtsiItsClock::now();
auto since_epoch = duration_cast<milliseconds>(now_its.time_since_epoch());
std::cout << "ITS time: " << since_epoch.count()
<< " ms since 2004-01-01T00:00:00Z (TAI)\n";
return 0;
}

Example: safe access (no crash when unsynced)

if (auto maybe_its = cube::time::EtsiItsClock::maybe_now()) {
using std::chrono::duration_cast;
using std::chrono::seconds;
auto its_s = duration_cast<seconds>(maybe_its->time_since_epoch()).count();
std::cout << "ITS time: " << its_s << " s\n";
} else {
std::cout << "ITS time not available (not synchronized)\n";
}

4. Getting the TAI–UTC Offset

Some V2X stacks (like cube-tiny-stack) need to know the current TAI–UTC offset (leap seconds) to format or validate timestamps.

For that there is a tiny C++ helper:

#include <cube/time/tai.hpp>
#include <chrono>
#include <iostream>
int main()
{
auto offset = cube::time::tai_utc_offset();
std::cout << "Current TAI-UTC offset: "
<< offset.count()
<< " seconds\n";
}

This should match what you see in:

cube-timectl show

and what chrony currently believes.


5. Converting from system_clock

Sometimes you already have a timestamp in std::chrono::system_clock (e.g. log entry, sensor timestamp) and you just want to convert it to ITS.

That’s what this is for:

auto sys_now = std::chrono::system_clock::now();
auto its_now = cube::time::EtsiItsClock::from_system_clock(sys_now);

This applies the current ITS–UTC offset and returns a proper ITS time point.


6. Notes

  • ITS epoch is 2004-01-01, not 1970-01-01
  • ITS uses TAI seconds; UTC has leap seconds, TAI does not
  • That’s why we must track TAI–UTC
  • If PPS/GNSS disappears, the clock may temporarily stay synchronized — you can check this via cube-timectl show

In Summary

  • Use C API if you just need “ITS now” as an integer.
  • Use C++ API if you work with chrono and want safe optional timestamps.
  • Both APIs expose the same information as you see in cube-timectl show.
  • This is the same time base the V2X stack uses — so your app and the radio stay in the same time universe.
Previous
GNSS and IMU
Next
RPC