ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
time.c
Go to the documentation of this file.
1
6
7/* Storage capacities in elements; text buffers include the terminating NUL. */
8#define UTIL_TIME_TEMP_BUFFER_SIZE 64
9
12
13#undef SOKOL_TIME_INCLUDED
14#define SOKOL_TIME_IMPL
15#include <ascii-chat-deps/sokol/sokol_time.h>
16#include <ascii-chat/common.h>
18#include <libplatform/lifecycle.h>
19#include <libplatform/rwlock.h>
20#include <libplatform/compiler.h>
21#include <libplatform/system.h>
22#include <libplatform/terminal.h>
23#include <stdlib.h>
24#include <string.h>
25#include <stdio.h>
26#include <time.h>
27#include <ctype.h>
28#include <libplatform/atomic.h>
29
30// ============================================================================
31// Global State
32// ============================================================================
33
37static struct {
40 lifecycle_t lifecycle;
41} g_timer_manager = {
42 .timers = NULL,
43 .lifecycle = LIFECYCLE_INIT,
44};
45
49static bool g_sokol_time_initialized = false;
50
51// ============================================================================
52// Human-Readable Time Formatting Helper
53// ============================================================================
54
74static int format_human_readable_time_ago(double total_seconds, int minutes, int hours, int days, int months, int years,
75 const char *suffix, char *buffer, size_t buffer_size) {
76 int written = 0;
77
78 if (total_seconds < 3) {
79 written = safe_snprintf(buffer, buffer_size, "a moment %s", suffix);
80 } else if (total_seconds < 10) {
81 written = safe_snprintf(buffer, buffer_size, "a few seconds %s", suffix);
82 } else if (total_seconds < 45) {
83 written = safe_snprintf(buffer, buffer_size, "%d seconds %s", (int)total_seconds, suffix);
84 } else if (total_seconds < 90) {
85 written = safe_snprintf(buffer, buffer_size, "a minute %s", suffix);
86 } else if (minutes < 45) {
87 written = safe_snprintf(buffer, buffer_size, "%d minutes %s", minutes, suffix);
88 } else if (minutes < 90) {
89 written = safe_snprintf(buffer, buffer_size, "an hour %s", suffix);
90 } else if (hours < 22) {
91 written = safe_snprintf(buffer, buffer_size, "%d hours %s", hours, suffix);
92 } else if (hours < 36) {
93 written = safe_snprintf(buffer, buffer_size, "a day %s", suffix);
94 } else if (days < 25) {
95 written = safe_snprintf(buffer, buffer_size, "%d days %s", days, suffix);
96 } else if (days < 45) {
97 written = safe_snprintf(buffer, buffer_size, "a month %s", suffix);
98 } else if (months < 11) {
99 written = safe_snprintf(buffer, buffer_size, "%d months %s", months, suffix);
100 } else if (months < 18) {
101 written = safe_snprintf(buffer, buffer_size, "a year %s", suffix);
102 } else {
103 written = safe_snprintf(buffer, buffer_size, "%d years %s", years, suffix);
104 }
105
106 return written;
107}
108
109// ============================================================================
110// Core Monotonic Timing Implementation
111// ============================================================================
112
114 // Use platform abstraction for cross-platform sleep
115 // Convert nanoseconds to microseconds (1 us = 1000 ns)
116 unsigned int usec = (unsigned int)(ns / 1000);
117 if (usec == 0 && ns > 0) {
118 usec = 1; // Minimum 1 microsecond
119 }
120 platform_sleep_us(usec);
121}
122
124 // Handle wraparound (defensive - uint64_t won't wrap in practice at nanosecond resolution)
125 // but this is safe and handles any theoretical edge cases
126 if (end_ns >= start_ns) {
127 return end_ns - start_ns;
128 }
129 // Wraparound case (extremely unlikely)
130 return (UINT64_MAX - start_ns) + end_ns + 1;
131}
132
133// ============================================================================
134// Conversion Functions (moved from inline in header to avoid circular deps)
135// ============================================================================
136
137uint64_t time_timespec_to_ns(const struct timespec *ts) {
138 if (!ts) {
139 SET_ERRNO(ERROR_INVALID_PARAM, "null ts");
140 return 0;
141 }
142 // Use __uint128_t to prevent overflow during multiplication of large time values
143 __uint128_t result = (__uint128_t)ts->tv_sec * NS_PER_SEC_INT;
144 if (result > UINT64_MAX) {
145 SET_ERRNO(ERROR_INVALID_PARAM, "timespec value would overflow");
146 return UINT64_MAX;
147 }
148 return (uint64_t)result + (uint64_t)ts->tv_nsec;
149}
150
151// ============================================================================
152// Timer System Implementation
153// ============================================================================
154
156 if (lifecycle_is_initialized(&g_timer_manager.lifecycle)) {
157 return true;
158 }
159
160 // Initialize sokol_time
161 stm_setup();
162
163 // Initialize rwlock and lifecycle together
164 g_timer_manager.lifecycle.sync_type = LIFECYCLE_SYNC_RWLOCK;
165 g_timer_manager.lifecycle.sync.rwlock = &g_timer_manager.rwlock;
166 if (!lifecycle_init(&g_timer_manager.lifecycle, "timer_manager")) {
167 SET_ERRNO(ERROR_PLATFORM_INIT, "Failed to initialize timer system (already initialized by another thread)");
168 return true; // Another thread already initialized it
169 }
170
171 g_timer_manager.timers = NULL;
172
173 log_dev("Timer system initialized");
174 return true;
175}
176
178 if (!lifecycle_is_initialized(&g_timer_manager.lifecycle)) {
179 return;
180 }
181
182 rwlock_wrlock(&g_timer_manager.rwlock);
183
184 // Free all timer records
185 int timer_count = 0;
186 timer_record_t *current, *tmp;
187 HASH_ITER(hh, g_timer_manager.timers, current, tmp) {
188 HASH_DEL(g_timer_manager.timers, current);
189 SAFE_FREE(current->name);
190 SAFE_FREE(current);
191 timer_count++;
192 }
193
194 g_timer_manager.timers = NULL;
195
196 rwlock_wrunlock(&g_timer_manager.rwlock);
197
198 /* Shutdown lifecycle and destroy rwlock together */
199 lifecycle_shutdown(&g_timer_manager.lifecycle);
200
201 log_debug("Timer system cleaned up (freed %d timers)", timer_count);
202}
203
204bool timer_start(const char *name) {
205 if (!lifecycle_is_initialized(&g_timer_manager.lifecycle)) {
206 SET_ERRNO(ERROR_INVALID_STATE, "Timer system not initialized");
207 return false;
208 }
209
210 if (!name) {
211 SET_ERRNO(ERROR_INVALID_PARAM, "Timer name is NULL");
212 return false;
213 }
214
215 rwlock_wrlock(&g_timer_manager.rwlock);
216
217 // Check if timer already exists - this is an error, timers must be unique
218 timer_record_t *existing = NULL;
219 HASH_FIND_STR(g_timer_manager.timers, name, existing);
220
221 if (existing) {
222 rwlock_wrunlock(&g_timer_manager.rwlock);
223 SET_ERRNO(ERROR_INVALID_STATE, "Timer '%s' already exists - timers must have unique names", name);
224 return false;
225 }
226
227 // Create new timer record
229 if (!timer) {
230 rwlock_wrunlock(&g_timer_manager.rwlock);
231 SET_ERRNO(ERROR_MEMORY, "Failed to allocate timer record");
232 return false;
233 }
234
235 // Use platform-safe string duplication
236 size_t name_len = strlen(name) + 1;
237 timer->name = SAFE_MALLOC(name_len, char *);
238 if (!timer->name) {
239 SAFE_FREE(timer);
240 rwlock_wrunlock(&g_timer_manager.rwlock);
241 SET_ERRNO(ERROR_MEMORY, "Failed to allocate timer name");
242 return false;
243 }
244 SAFE_STRNCPY(timer->name, name, name_len);
245
246 timer->start_ticks = stm_now();
247
248 // Add to hashtable
249 HASH_ADD_KEYPTR(hh, g_timer_manager.timers, timer->name, strlen(timer->name), timer);
250
251 rwlock_wrunlock(&g_timer_manager.rwlock);
252 return true;
253}
254
255double timer_stop(const char *name) {
256 if (!lifecycle_is_initialized(&g_timer_manager.lifecycle)) {
257 SET_ERRNO(ERROR_INVALID_STATE, "Timer system not initialized");
258 return -1.0;
259 }
260
261 if (!name) {
262 SET_ERRNO(ERROR_INVALID_PARAM, "Timer name is NULL");
263 return -1.0;
264 }
265
266 rwlock_wrlock(&g_timer_manager.rwlock);
267
268 // Find timer
269 timer_record_t *timer = NULL;
270 HASH_FIND_STR(g_timer_manager.timers, name, timer);
271
272 if (!timer) {
273 rwlock_wrunlock(&g_timer_manager.rwlock);
274 log_dev("Timer '%s' not found (may have already been stopped)", name);
275 return -1.0;
276 }
277
278 // Calculate elapsed time in nanoseconds for maximum precision
279 uint64_t end_ticks = stm_now();
280 uint64_t elapsed_ticks = stm_diff(end_ticks, timer->start_ticks);
281 double elapsed_ns = stm_ns(elapsed_ticks);
282
283 // Format duration for human-readable logging
284 char duration_str[TIME_DURATION_BUFFER_SIZE];
285 time_pretty((uint64_t)elapsed_ns, -1, duration_str, sizeof(duration_str));
286
287 // Log the result (dev level - only shown with --verbose)
288 log_dev("Timer '%s': %s", name, duration_str);
289
290 // Save pointers to free BEFORE removing from hashtable
291 char *timer_name = timer->name;
292
293 // Remove from hashtable (HASH_DEL only removes from hash, doesn't free memory)
294 HASH_DEL(g_timer_manager.timers, timer);
295
296 // Free timer->name first (this was allocated separately)
297 SAFE_FREE(timer_name);
298
299 // Nullify the timer->name field so debug_free doesn't try to access it
300 // when freeing the timer structure itself
301 timer->name = NULL;
302
303 // Free the timer record itself
304 SAFE_FREE(timer);
305
306 rwlock_wrunlock(&g_timer_manager.rwlock);
307 return elapsed_ns; // Return nanoseconds
308}
309
311 return lifecycle_is_initialized(&g_timer_manager.lifecycle);
312}
313
314// ============================================================================
315// Time Formatting Implementation
316// ============================================================================
317
318int format_uptime_hms(int hours, int minutes, int seconds, char *buffer, size_t buffer_size) {
319 if (!buffer || buffer_size == 0) {
320 return -1;
321 }
322
323 // Validate components
324 if (hours < 0 || minutes < 0 || minutes >= 60 || seconds < 0 || seconds >= 60) {
325 return -1;
326 }
327
328 // Format as HH:MM:SS with zero-padding
329 int written = safe_snprintf(buffer, buffer_size, "%02d:%02d:%02d", hours, minutes, seconds);
330
331 if (written < 0 || (size_t)written >= buffer_size) {
332 return -1;
333 }
334
335 return written;
336}
337
338// ============================================================================
339// Pretty Time Formatting with Spaces and Configurable Precision
340// ============================================================================
341
347static int strip_trailing_zeros(char *str) {
348 if (!str || !*str)
349 return 0;
350
351 int len = (int)strlen(str);
352 if (len <= 0)
353 return len;
354
355 // Find decimal point
356 char *decimal = strchr(str, '.');
357 if (!decimal)
358 return len; // No decimal point
359
360 // Keep the unit suffix while removing zeros from the fractional digits.
361 char *suffix = decimal + 1;
362 while (*suffix >= '0' && *suffix <= '9')
363 ++suffix;
364 char *end = suffix;
365 while (end > decimal + 1 && end[-1] == '0')
366 --end;
367 if (end == decimal + 1)
368 end = decimal;
369 memmove(end, suffix, strlen(suffix) + 1);
370
371 return (int)strlen(str);
372}
373
374int time_pretty(uint64_t nanoseconds, int decimals, char *buffer, size_t buffer_size) {
375 if (!buffer || buffer_size == 0) {
376 return -1;
377 }
378
379 // Handle zero
380 if (nanoseconds == 0) {
381 int written = safe_snprintf(buffer, buffer_size, "0ns");
382 return (written < 0 || (size_t)written >= buffer_size) ? -1 : written;
383 }
384
385 // Determine decimals for this range if not explicitly set
386 int actual_decimals = decimals;
387 if (decimals == -1) {
388 if (nanoseconds < NS_PER_MIN_INT) {
389 actual_decimals = (nanoseconds < NS_PER_SEC_INT) ? 3 : 2; // ns/µs/ms or seconds
390 } else {
391 actual_decimals = 0; // colon notation uses whole seconds
392 }
393 }
394
395 // Clamp decimals to valid range
396 if (actual_decimals < 0)
397 actual_decimals = 0;
398 if (actual_decimals > 9)
399 actual_decimals = 9;
400
401 int written = 0;
402
403 // Nanoseconds (< 1µs)
404 if (nanoseconds < NS_PER_US_INT) {
405 written = safe_snprintf(buffer, buffer_size, "%lluns", (unsigned long long)nanoseconds);
406 }
407 // Microseconds (< 1ms)
408 else if (nanoseconds < NS_PER_MS_INT) {
409 double us = (double)nanoseconds / NS_PER_US;
410 char temp_buf[UTIL_TIME_TEMP_BUFFER_SIZE];
411 // Use a direct snprintf with large enough buffer first
412 snprintf(temp_buf, sizeof(temp_buf), "%.*fµs", actual_decimals, us);
413 strip_trailing_zeros(temp_buf);
414 written = (int)strlen(temp_buf);
415 if (written > 0 && written < (int)buffer_size) {
416 SAFE_STRNCPY(buffer, temp_buf, buffer_size);
417 } else {
418 written = -1;
419 }
420 }
421 // Milliseconds (< 1s)
422 else if (nanoseconds < NS_PER_SEC_INT) {
423 double ms = (double)nanoseconds / NS_PER_MS;
424 char temp_buf[UTIL_TIME_TEMP_BUFFER_SIZE];
425 snprintf(temp_buf, sizeof(temp_buf), "%.*fms", actual_decimals, ms);
426 strip_trailing_zeros(temp_buf);
427 written = (int)strlen(temp_buf);
428 if (written > 0 && written < (int)buffer_size) {
429 SAFE_STRNCPY(buffer, temp_buf, buffer_size);
430 } else {
431 written = -1;
432 }
433 }
434 // Seconds (< 1 minute)
435 else if (nanoseconds < NS_PER_MIN_INT) {
436 double s = (double)nanoseconds / NS_PER_SEC;
437 char temp_buf[UTIL_TIME_TEMP_BUFFER_SIZE];
438 snprintf(temp_buf, sizeof(temp_buf), "%.*fs", actual_decimals, s);
439 strip_trailing_zeros(temp_buf);
440 written = (int)strlen(temp_buf);
441 if (written > 0 && written < (int)buffer_size) {
442 SAFE_STRNCPY(buffer, temp_buf, buffer_size);
443 } else {
444 written = -1;
445 }
446 }
447 // Minutes omit fractions; days omit seconds.
448 else if (nanoseconds < NS_PER_HOUR_INT) {
449 uint64_t total_seconds = nanoseconds / NS_PER_SEC_INT;
450 written = safe_snprintf(buffer, buffer_size, "%llu:%02llu", (unsigned long long)(total_seconds / 60),
451 (unsigned long long)(total_seconds % 60));
452 } else if (nanoseconds >= NS_PER_DAY_INT) {
453 uint64_t total_minutes = nanoseconds / NS_PER_MIN_INT;
454 written = safe_snprintf(buffer, buffer_size, "%llud %llu:%02llu", (unsigned long long)(total_minutes / (24 * 60)),
455 (unsigned long long)((total_minutes / 60) % 24), (unsigned long long)(total_minutes % 60));
456 }
457 // Hours use H:MM:SS.
458 else {
459 uint64_t total_seconds = nanoseconds / NS_PER_SEC_INT;
460 uint64_t hours = total_seconds / 3600;
461 uint64_t remaining = total_seconds % 3600;
462 uint64_t minutes = remaining / 60;
463 uint64_t seconds = remaining % 60;
464
465 written = safe_snprintf(buffer, buffer_size, "%llu:%02llu:%02llu", (unsigned long long)hours,
466 (unsigned long long)minutes, (unsigned long long)seconds);
467 }
468
469 if (written < 0 || (size_t)written >= buffer_size) {
470 return -1;
471 }
472
473 return written;
474}
475
476int time_pretty_now(int decimals, char *buffer, size_t buffer_size) {
477 uint64_t ns = time_get_ns();
478 return time_pretty(ns, decimals, buffer, buffer_size);
479}
480
481// ============================================================================
482// Human-Readable Time Formatting (moment.js style)
483// ============================================================================
484
485int time_human_readable_unsigned(uint64_t nanoseconds, char *buffer, size_t buffer_size) {
486 if (!buffer || buffer_size == 0) {
487 return -1;
488 }
489
490 // Convert nanoseconds to seconds for threshold checking
491 double total_seconds = (double)nanoseconds / NS_PER_SEC;
492 int minutes = (int)(total_seconds / 60);
493 int hours = minutes / 60;
494 int days = hours / 24;
495 int months = days / 30; // Approximate
496 int years = months / 12;
497
498 int written =
499 format_human_readable_time_ago(total_seconds, minutes, hours, days, months, years, "ago", buffer, buffer_size);
500
501 if (written < 0 || (size_t)written >= buffer_size) {
502 return -1;
503 }
504
505 return written;
506}
507
508int time_human_readable(int64_t nanoseconds, char *buffer, size_t buffer_size) {
509 if (!buffer || buffer_size == 0) {
510 return -1;
511 }
512
513 // Determine direction (past vs future)
514 bool is_future = nanoseconds < 0;
515 uint64_t abs_ns = is_future ? (uint64_t)(-nanoseconds) : (uint64_t)nanoseconds;
516
517 // Convert nanoseconds to seconds for threshold checking
518 double total_seconds = (double)abs_ns / NS_PER_SEC;
519 int minutes = (int)(total_seconds / 60);
520 int hours = minutes / 60;
521 int days = hours / 24;
522 int months = days / 30; // Approximate
523 int years = months / 12;
524
525 const char *suffix = is_future ? "from now" : "ago";
526 int written =
527 format_human_readable_time_ago(total_seconds, minutes, hours, days, months, years, suffix, buffer, buffer_size);
528
529 if (written < 0 || (size_t)written >= buffer_size) {
530 return -1;
531 }
532
533 return written;
534}
535
536int time_human_readable_now(char *buffer, size_t buffer_size) {
537 int64_t ns = (int64_t)time_get_ns();
538 return time_human_readable(ns, buffer, buffer_size);
539}
540
541// ============================================================================
542// Adaptive Sleep Implementation
543// ============================================================================
544
546 if (!state || !config) {
547 return;
548 }
549
550 // Copy configuration
551 state->config = *config;
552
553 // A multiplier of 1.0 preserves the configured interval. Clamp it for
554 // configurations whose allowed range does not include the baseline.
555 state->current_speed_multiplier = 1.0;
556 if (state->current_speed_multiplier < config->min_speed_multiplier) {
557 state->current_speed_multiplier = config->min_speed_multiplier;
558 }
559 if (state->current_speed_multiplier > config->max_speed_multiplier) {
560 state->current_speed_multiplier = config->max_speed_multiplier;
561 }
562
563 state->last_sleep_ns = (uint64_t)(config->baseline_sleep_ns / state->current_speed_multiplier);
564}
565
566uint64_t adaptive_sleep_calculate(adaptive_sleep_state_t *state, size_t queue_depth, size_t target_depth) {
567 if (!state) {
568 return 0;
569 }
570
571 const adaptive_sleep_config_t *cfg = &state->config;
572
573 // Determine if we need to speed up or slow down
574 double desired_multiplier;
575
576 if (queue_depth > target_depth) {
577 // Queue is building up - speed up processing
578 // The more items in queue, the faster we should process
579 size_t backlog = queue_depth - target_depth;
580
581 // Calculate desired speed based on backlog
582 // Each item above target increases speed proportionally
583 // This ensures we drain the queue faster the more backed up we are
584 double backlog_factor = 1.0 + ((double)backlog / (double)(target_depth + 1));
585 desired_multiplier = cfg->min_speed_multiplier * backlog_factor;
586
587 // Clamp to max speed
588 if (desired_multiplier > cfg->max_speed_multiplier) {
589 desired_multiplier = cfg->max_speed_multiplier;
590 }
591
592 // Ramp up gradually based on speedup_rate
593 double delta = desired_multiplier - state->current_speed_multiplier;
594 state->current_speed_multiplier += delta * cfg->speedup_rate;
595
596 } else {
597 // Queue is at or below target - slow down to baseline
598 desired_multiplier = 1.0;
599 if (desired_multiplier < cfg->min_speed_multiplier) {
600 desired_multiplier = cfg->min_speed_multiplier;
601 }
602 if (desired_multiplier > cfg->max_speed_multiplier) {
603 desired_multiplier = cfg->max_speed_multiplier;
604 }
605
606 // Ramp down gradually based on slowdown_rate
607 double delta = desired_multiplier - state->current_speed_multiplier;
608 state->current_speed_multiplier += delta * cfg->slowdown_rate;
609 }
610
611 // Ensure we stay within configured bounds
612 if (state->current_speed_multiplier < cfg->min_speed_multiplier) {
613 state->current_speed_multiplier = cfg->min_speed_multiplier;
614 }
615 if (state->current_speed_multiplier > cfg->max_speed_multiplier) {
616 state->current_speed_multiplier = cfg->max_speed_multiplier;
617 }
618
619 // Calculate actual sleep time: baseline / speed_multiplier
620 // Higher multiplier = shorter sleep = faster processing
621 uint64_t sleep_ns = (uint64_t)(cfg->baseline_sleep_ns / state->current_speed_multiplier);
622
623 // Store for debugging
624 state->last_sleep_ns = sleep_ns;
625
626 return sleep_ns;
627}
628
629void adaptive_sleep_do(adaptive_sleep_state_t *state, size_t queue_depth, size_t target_depth) {
630 uint64_t sleep_ns = adaptive_sleep_calculate(state, queue_depth, target_depth);
631
632 if (sleep_ns > 0) {
633 platform_sleep_ns(sleep_ns);
634 }
635}
636
637/* ============================================================================
638 * Time Format Validation and Formatting Implementation
639 * ============================================================================ */
640
645static const char SUPPORTED_SPECIFIERS[] = "YmmdHMSaAbBjwuIpZzcxXFTsGgV";
646
647bool time_format_is_valid_strftime(const char *format_str) {
648 if (!format_str) {
649 return false;
650 }
651
652 for (const char *p = format_str; *p; p++) {
653 if (*p == '%') {
654 p++;
655 if (!*p) {
656 /* Unterminated % at end of string */
657 log_error("Invalid time format: unterminated %% at end");
658 return false;
659 }
660
661 if (*p == '%') {
662 /* Escaped %% - this is valid, just skip it */
663 continue;
664 }
665
666 /* Optional: flag characters (-, 0, +, space) */
667 if (*p == '-' || *p == '0' || *p == '+' || *p == ' ') {
668 p++;
669 if (!*p) {
670 log_error("Invalid time format: flag character without specifier");
671 return false;
672 }
673 }
674
675 /* Optional: width specifier (digits or *) */
676 if (*p == '*' || isdigit((unsigned char)*p)) {
677 while (*p && (isdigit((unsigned char)*p) || *p == '*')) {
678 p++;
679 }
680 if (!*p) {
681 log_error("Invalid time format: width specifier without specifier character");
682 return false;
683 }
684 }
685
686 /* Optional: precision specifier (.digits or .*) */
687 if (*p == '.') {
688 p++;
689 if (!*p) {
690 log_error("Invalid time format: precision specifier incomplete");
691 return false;
692 }
693 while (*p && (isdigit((unsigned char)*p) || *p == '*')) {
694 p++;
695 }
696 if (!*p) {
697 log_error("Invalid time format: precision specifier without specifier character");
698 return false;
699 }
700 }
701
702 /* Optional: modifier characters (E for %Ex, O for %Ox) */
703 if (*p == 'E' || *p == 'O') {
704 p++;
705 if (!*p) {
706 log_error("Invalid time format: modifier character without specifier");
707 return false;
708 }
709 }
710
711 /* Validate that the final character is a supported specifier */
712 if (!strchr(SUPPORTED_SPECIFIERS, *p)) {
713 log_error("Invalid time format: unsupported specifier %%%c (at position %ld)", *p, (p - format_str));
714 return false;
715 }
716 }
717 }
718
719 return true;
720}
721
722int time_format_now(const char *format_str, char *buf, size_t buf_size) {
723 if (!format_str || !buf || buf_size < 2) {
724 SET_ERRNO(ERROR_INVALID_PARAM, "invalid arguments - format_str=%p, buf=%p, buf_size=%zu", format_str, buf,
725 buf_size);
726 return 0;
727 }
728
729 /* Get current wall-clock time in nanoseconds */
730 uint64_t ts_ns = time_get_realtime_ns();
731
732 /* Extract seconds and nanoseconds */
733 time_t seconds = (time_t)(ts_ns / NS_PER_SEC_INT);
734
735 /* Convert seconds to struct tm */
736 struct tm tm_info;
737 platform_localtime(&seconds, &tm_info);
738
739 /* Format using strftime */
740 size_t len = strftime(buf, buf_size, format_str, &tm_info);
741
742 /* Check for strftime errors or buffer overflow */
743 if (len == 0) {
744 /* strftime returned 0 - either error or exact buffer fill */
745 log_debug("strftime returned 0 for format: %s", format_str);
746 return 0;
747 }
748
749 if (len >= buf_size - 1) {
750 /* Buffer would overflow - strftime filled entire buffer or hit exact size */
751 log_error("buffer too small (need %zu, have %zu)", len + 1, buf_size);
752 return 0;
753 }
754
755 return (int)len;
756}
757
758asciichat_error_t time_format_safe(const char *format_str, char *buf, size_t buf_size) {
759 /* Check NULL pointers */
760 if (!format_str) {
761 return SET_ERRNO(ERROR_INVALID_STATE, "format_str is NULL");
762 }
763
764 if (!buf) {
765 return SET_ERRNO(ERROR_INVALID_STATE, "buf is NULL");
766 }
767
768 /* Check minimum buffer size */
769 if (buf_size < 64) {
770 return SET_ERRNO(ERROR_INVALID_STATE, "buffer too small (minimum 64 bytes, got %zu)", buf_size);
771 }
772
773 /* Validate format string */
774 if (!time_format_is_valid_strftime(format_str)) {
775 return SET_ERRNO(ERROR_INVALID_STATE, "invalid time format: %s", format_str);
776 }
777
778 /* Format the time */
779 int result = time_format_now(format_str, buf, buf_size);
780 if (result <= 0) {
781 return SET_ERRNO(ERROR_INVALID_STATE, "strftime formatting failed");
782 }
783
784 return ASCIICHAT_OK;
785}
Synchronized per-thread pending error stacks and value snapshots. SET_ERRNO pushes,...
#define SET_ERRNO(code,...)
⚙️ Common definitions, error codes, macros, and types shared throughout the application
Error and exit codes - unified status values (0-255).
int buffer_size
Size of circular buffer.
Definition grep.c:68
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:423
#define SAFE_FREE(ptr)
Definition common.h:377
#define SAFE_MALLOC(size, cast)
Definition common.h:265
unsigned long long uint64_t
Definition common.h:63
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
@ ERROR_INVALID_STATE
@ ERROR_PLATFORM_INIT
Definition error_codes.h:55
@ ERROR_MEMORY
Definition error_codes.h:51
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_INVALID_PARAM
#define log_dev(...)
Log a DEV message (most verbose, development only).
Definition log/log.h:581
#define log_error(...)
Log an ERROR message.
Definition log/log.h:634
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:595
double timer_stop(const char *name)
Stop a named timer and return elapsed time.
Definition time.c:255
int time_pretty_now(int decimals, char *buffer, size_t buffer_size)
Format current monotonic time as pretty duration.
Definition time.c:476
bool timer_start(const char *name)
Start a named timer.
Definition time.c:204
bool time_format_is_valid_strftime(const char *format_str)
Validate strftime format string against known safe specifiers.
Definition time.c:647
#define NS_PER_MIN_INT
Definition time.h:184
#define NS_PER_SEC_INT
Definition time.h:169
uint64_t adaptive_sleep_calculate(adaptive_sleep_state_t *state, size_t queue_depth, size_t target_depth)
Calculate adaptive sleep time based on queue depth.
Definition time.c:566
int time_pretty(uint64_t nanoseconds, int decimals, char *buffer, size_t buffer_size)
Format nanoseconds as a compact duration with configurable sub-minute precision.
Definition time.c:374
int time_format_now(const char *format_str, char *buf, size_t buf_size)
Format current time using strftime format string.
Definition time.c:722
uint64_t time_timespec_to_ns(const struct timespec *ts)
Convert struct timespec to nanoseconds.
Definition time.c:137
#define NS_PER_HOUR_INT
Definition time.h:185
#define TIME_DURATION_BUFFER_SIZE
Definition time.h:40
void timer_system_destroy(void)
Cleanup the timing system.
Definition time.c:177
#define NS_PER_MS_INT
Definition time.h:168
int time_human_readable_unsigned(uint64_t nanoseconds, char *buffer, size_t buffer_size)
Format elapsed time as human-readable relative duration (unsigned version).
Definition time.c:485
#define NS_PER_MS
Definition time.h:159
void adaptive_sleep_init(adaptive_sleep_state_t *state, const adaptive_sleep_config_t *config)
Initialize adaptive sleep state with configuration.
Definition time.c:545
int time_human_readable(int64_t nanoseconds, char *buffer, size_t buffer_size)
Format nanoseconds as human-readable relative duration (moment.js style).
Definition time.c:508
uint64_t time_elapsed_ns(uint64_t start_ns, uint64_t end_ns)
Calculate elapsed time with wraparound safety.
Definition time.c:123
#define NS_PER_US_INT
Definition time.h:167
int format_uptime_hms(int hours, int minutes, int seconds, char *buffer, size_t buffer_size)
Format uptime as HH:MM:SS string.
Definition time.c:318
void time_sleep_ns(uint64_t ns)
Configuration for adaptive sleep behavior.
Definition time.c:113
bool timer_is_initialized(void)
Check if timing system is initialized.
Definition time.c:310
bool timer_system_init(void)
Initialize the timing system.
Definition time.c:155
void adaptive_sleep_do(adaptive_sleep_state_t *state, size_t queue_depth, size_t target_depth)
Calculate sleep time and immediately sleep for that duration.
Definition time.c:629
#define NS_PER_US
Definition time.h:158
struct timer_record timer_record_t
Individual timer record for a named timing operation.
#define NS_PER_DAY_INT
Definition time.h:186
asciichat_error_t time_format_safe(const char *format_str, char *buf, size_t buf_size)
Safe wrapper for time formatting with validation and error codes.
Definition time.c:758
int time_human_readable_now(char *buffer, size_t buffer_size)
Format current monotonic time as human-readable relative duration.
Definition time.c:536
#define NS_PER_SEC
Definition time.h:160
#define rwlock_wrunlock(lock)
Release a write lock (with debug tracking in debug builds).
Definition rwlock.h:357
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Platform-safe snprintf wrapper.
asciichat_error_t platform_localtime(const time_t *timer, struct tm *result)
Platform-safe localtime wrapper.
#define rwlock_wrlock(lock)
Acquire a write lock (with debug tracking in debug builds).
Definition rwlock.h:319
🔤 String Manipulation and Shell Escaping Utilities
Configuration for adaptive sleep behavior.
Definition time.h:691
double min_speed_multiplier
Minimum speed (max sleep) - usually 1.0 (baseline speed).
Definition time.h:693
double max_speed_multiplier
Maximum speed (min sleep) - e.g., 4.0 = process 4x faster.
Definition time.h:694
uint64_t baseline_sleep_ns
Normal sleep time in nanoseconds (when queue is at target).
Definition time.h:692
double speedup_rate
Ramp-up rate when queue builds (0.0-1.0, higher = faster ramp).
Definition time.h:695
double slowdown_rate
Ramp-down rate when queue empties (0.0-1.0, higher = faster ramp).
Definition time.h:696
Runtime state for adaptive sleep.
Definition time.h:707
Read-write lock type (POSIX: pthread_rwlock_t with debug tracking).
Definition rwlock.h:63
uint64_t start_ticks
Start time in sokol ticks.
Definition time.h:262
char * name
Timer name (heap-allocated, unique key).
Definition time.h:261
rwlock_t rwlock
Read-write lock for thread-safe access (uthash requires external locking).
Definition time.c:39
lifecycle_t lifecycle
Lifecycle state machine for init/shutdown synchronization.
Definition time.c:40
timer_record_t * timers
Hash table of active timers (uthash head pointer).
Definition time.c:38
#define UTIL_TIME_TEMP_BUFFER_SIZE
Definition time.c:8
⏱️ High-precision timing utilities using sokol_time.h and uthash