Time¶
TIME — Time Management
Time-related functions are essential for MCU devices. This section provides definitions and functions for time management: running time, SNTP synchronization, world time, and NTP integration hooks for WSN time-sync protocols.
In MCU, time can be divided into the following types:
-
Running Time: The time from the power-on of the MCU to now.
-
World Time: The time of the time zone where the MCU is located. World time can be represented by standard year, month, day, hour, minute, and second, or it can be represented as a UNIX timestamp.
RUNNING TIME¶
ESP has its own function to get the running time, esp_timer_get_time, which depends on the esp_timer library. This function returns the time from power-on to now, in microseconds.
To facilitate usage, TinyToolbox redefines the data type TinyTimeMark_t and provides a function tiny_get_running_time to get the running time. The time returned by this function is in the unit of int64_t, which is long enough to avoid overflow.
typedef int64_t TinyTimeMark_t;
TinyTimeMark_t tiny_get_running_time(void) { return esp_timer_get_time(); }
Usage reference:
void app_main(void)
{
TinyTimeMark_t running_time = tiny_get_running_time();
ESP_LOGI(TAG_TIME, "Running Time: %lld us", running_time);
}
WORLD TIME¶
Warning
Note that obtaining world time requires a successful network connection. In other words, the function to obtain world time needs to be called after the network connection is successfully established.
NTP TIME SYNCHRONIZATION¶
NTP Time Synchronization
NTP (Network Time Protocol) is a protocol used to synchronize time in computer networks. It can obtain accurate time information through the Internet or local area network. NTP protocol uses UDP for communication, with the default port being 123. NTP servers periodically send time information to clients, and clients adjust their system time based on this information.
Client Server
|-------------------> | T1:Request sent
| |
| <--------------- | T2/T3:Server received & replied
| |
|-------------------> | T4:Client received response
NTP Time Synchronization Principle
NTP time synchronization is based on four timestamps: 1. Timestamp T1 when the client sends the request 2. Timestamp T2 when the server receives the request 3. Timestamp T3 when the server sends the response 4. Timestamp T4 when the client receives the response. Based on these four timestamps, we can calculate Network Delay Delay = (T4 - T1) - (T3 - T2), and Time Offset Offset = ((T2 - T1) + (T3 - T4)) / 2.
ESP32 SNTP Time Synchronization
In ESP32, SNTP (Simple Network Time Protocol) is used. SNTP is a simplified version of NTP, suitable for scenarios where time accuracy is not critical. The time synchronization in ESP32 relies on the esp_sntp library. SNTP's accuracy is usually at the millisecond level, which is sufficient for most application scenarios.
SNTP Initialization (v1.1 — Semaphore-Based)¶
Starting from v1.1, sync_time_with_timezone() uses a FreeRTOS binary semaphore to block the calling task until the SNTP callback fires, instead of the old polling loop. This saves CPU and provides more deterministic synchronization timing.
The callback also fires the NTP sync hook (see below) at the earliest possible moment — immediately when the SNTP response arrives — giving dependent modules (e.g., FTSP) the most accurate local-time anchor.
static void time_sync_notification_cb(struct timeval *tv)
{
ESP_LOGI(TAG_SNTP, "Time synchronized!");
/* Fire the NTP hook while the timestamp is fresh */
if (s_ntp_sync_hook != NULL && tv != NULL && tv->tv_sec > 946684800L)
{
s_ntp_sync_hook((uint32_t)tv->tv_sec);
}
if (s_sntp_sem) {
xSemaphoreGive(s_sntp_sem);
}
}
The initialization also adds a secondary NTP server (time.google.com) as fallback:
static void initialize_sntp(void)
{
ESP_LOGI(TAG_SNTP, "Initializing SNTP");
esp_sntp_setoperatingmode(SNTP_OPMODE_POLL);
esp_sntp_setservername(0, "pool.ntp.org");
esp_sntp_setservername(1, "time.google.com");
esp_sntp_set_time_sync_notification_cb(time_sync_notification_cb);
esp_sntp_init();
}
The full sync_time_with_timezone() function now blocks on the semaphore (30-second timeout) instead of polling:
void sync_time_with_timezone(const char *timezone_str)
{
// ... validation, setenv("TZ", ...) ...
s_sntp_sem = xSemaphoreCreateBinary();
initialize_sntp();
if (xSemaphoreTake(s_sntp_sem, pdMS_TO_TICKS(30000)) != pdTRUE)
{
ESP_LOGW(TAG_SNTP, "NTP sync timeout.");
// ... cleanup ...
return;
}
// ... get time, set RTC, log ...
}
NTP SYNC HOOK (v1.1)¶
A key addition in v1.1 is the NTP sync hook callback mechanism. This allows modules that depend on tiny_toolbox (such as the FTSP time-sync module in tiny_measurement) to receive NTP sync notifications without creating a circular CMake dependency.
/**
* @brief Register a callback invoked after each successful NTP sync.
* @param hook bool (*hook)(uint32_t unix_sec), or NULL to clear.
*/
void tiny_ntp_set_sync_hook(bool (*hook)(uint32_t unix_sec));
Usage:
#include "tiny_time.h"
// FTSP registers its NTP recording function as the hook
tiny_ntp_set_sync_hook(ftsp_ntp_record_sync);
// Later, when sync_time_with_timezone() succeeds,
// ftsp_ntp_record_sync() is called automatically with the Unix timestamp.
NTP SYNC STATE (v1.1)¶
The module maintains an internal NTP sync state that tracks the anchor point for precise world-time calculation:
typedef struct TinyNtpSync_t
{
uint32_t unix_sec; // Unix timestamp in seconds from NTP sync
uint64_t local_us_anchor; // Local running time (microseconds) at sync moment
uint64_t last_sync_time_us; // Last sync time in local microseconds
uint8_t sync_quality; // NTP sync quality indicator (0-100%)
bool is_synced; // Whether NTP sync is valid
} TinyNtpSync_t;
Public functions for querying NTP state:
// Record an NTP sync event manually
bool tiny_ntp_record_sync(uint32_t unix_sec);
// Get current world time with microsecond precision
uint64_t tiny_ntp_get_world_time_us(void);
// Get NTP sync info
bool tiny_ntp_get_sync_info(TinyNtpSync_t *sync_info);
// Check if NTP is currently synced (with 5-minute staleness check)
bool tiny_ntp_is_synced(void);
// Set NTP sync quality
void tiny_ntp_set_quality(uint8_t quality);
// Get current world time as TinyDateTime_t
bool tiny_ntp_get_datetime_us(TinyDateTime_t *dt);
WORLD TIME GETTING¶
The TinyDateTime_t structure stores calendar time with microsecond precision:
typedef struct TinyDateTime_t
{
int year;
int month;
int day;
int hour;
int minute;
int second;
int32_t microsecond; // 0-999999
} TinyDateTime_t;
tiny_get_current_datetime() obtains the current wall-clock time from the system (after SNTP sync):
UNIX TIMESTAMP CONVERSION (v1.1)¶
v1.1 adds full UNIX timestamp conversion utilities:
// Convert Unix seconds to calendar datetime
bool tiny_unix_to_datetime(uint32_t unix_sec, TinyDateTime_t *dt);
// Convert Unix microseconds to calendar datetime (preserves µs precision)
bool tiny_unix_us_to_datetime(uint64_t unix_us, TinyDateTime_t *dt);
// Convert calendar datetime back to Unix seconds
uint32_t tiny_datetime_to_unix(const TinyDateTime_t *dt);
// Validate a calendar datetime
bool tiny_is_valid_datetime(const TinyDateTime_t *dt);
// Leap year check (inline helper)
static inline bool tiny_is_leap_year(uint16_t year);
TIME FORMATTING (v1.1)¶
v1.1 adds human-readable formatting utilities for logging and display:
// ISO 8601: "2026-05-15T10:30:00Z"
int tiny_format_datetime_iso8601(const TinyDateTime_t *dt, char *buf, size_t buflen);
// With microseconds: "2026-05-15 10:30:00.123456"
int tiny_format_datetime_us(const TinyDateTime_t *dt, char *buf, size_t buflen);
// With milliseconds: "2026-05-15 10:30:00.123"
int tiny_format_datetime_ms(const TinyDateTime_t *dt, char *buf, size_t buflen);
// Format Unix seconds directly
int tiny_format_unix_sec(uint32_t unix_sec, char *buf, size_t buflen);
// Format Unix microseconds directly
int tiny_format_unix_us(uint64_t unix_us, char *buf, size_t buflen);
// Auto-scale duration: "45 us" / "2.500 ms" / "1.200 sec" / "3.50 min" / "1.25 hr"
int tiny_format_duration_us(uint64_t duration_us, char *buf, size_t buflen);
```c
/* WORLD CURRENT TIME - GET TIME */
/**
* @name tiny_get_current_datetime
* @brief Get the current time as a TinyDateTime_t struct
* @param print_flag Flag to indicate whether to print the time
* @return TinyDateTime_t structure containing the current date and time
*/
TinyDateTime_t tiny_get_current_datetime(bool print_flag)
{
TinyDateTime_t result = {0}; // Initialize to zero
struct timeval tv;
// Get current time (seconds + microseconds)
if (gettimeofday(&tv, NULL) != 0)
{
ESP_LOGE(TAG_TIME, "Failed to get time of day");
return result; // Return zero-initialized structure on error
}
time_t now = tv.tv_sec;
struct tm timeinfo;
if (localtime_r(&now, &timeinfo) == NULL)
{
ESP_LOGE(TAG_TIME, "Failed to convert time to local time");
return result; // Return zero-initialized structure on error
}
result.year = timeinfo.tm_year + 1900;
result.month = timeinfo.tm_mon + 1;
result.day = timeinfo.tm_mday;
result.hour = timeinfo.tm_hour;
result.minute = timeinfo.tm_min;
result.second = timeinfo.tm_sec;
result.microsecond = (int32_t)tv.tv_usec; // Explicit cast for portability
if (print_flag)
{
ESP_LOGI(TAG_TIME, "Current Time: %04d-%02d-%02d %02d:%02d:%02d.%06d",
result.year, result.month, result.day, result.hour, result.minute,
result.second, result.microsecond);
}
return result;
}
Usage
void app_main(void)
{
// Initialize SNTP and sync time
sync_time_with_timezone("CST-8");
// Get current time
TinyDateTime_t current_time = tiny_get_current_datetime(true);
// Print current time
ESP_LOGI(TAG_TIME, "Current Time: %04d-%02d-%02d %02d:%02d:%02d.%06ld",
current_time.year, current_time.month, current_time.day,
current_time.hour, current_time.minute, current_time.second, current_time.microsecond);
}
Example Output

Danger
The SNTP accuracy when syncing to RTC is at the second level, so the microsecond part when obtaining world time may not be accurate and is for reference only.