From b7df596f8a2a71d25dd3df7c59ca1e301ca42e6b Mon Sep 17 00:00:00 2001 From: apio Date: Thu, 13 Apr 2023 21:09:27 +0200 Subject: [PATCH] libc: Add strftime() --- libc/CMakeLists.txt | 1 + libc/include/time.h | 3 + libc/src/strftime.cpp | 140 ++++++++++++++++++++++++++++++++++++++++++ libc/src/time.cpp | 6 +- 4 files changed, 145 insertions(+), 5 deletions(-) create mode 100644 libc/src/strftime.cpp diff --git a/libc/CMakeLists.txt b/libc/CMakeLists.txt index e3f9374d..fb4e591c 100644 --- a/libc/CMakeLists.txt +++ b/libc/CMakeLists.txt @@ -7,6 +7,7 @@ set(SOURCES src/unistd.cpp src/errno.cpp src/string.cpp + src/strftime.cpp src/fcntl.cpp src/assert.cpp src/atexit.cpp diff --git a/libc/include/time.h b/libc/include/time.h index a4e33333..8f4291aa 100644 --- a/libc/include/time.h +++ b/libc/include/time.h @@ -42,6 +42,9 @@ extern "C" /* Build a string representation of UNIX time. */ char* ctime_r(const time_t* tp, char buf[26]); + /* Format a string representation of broken-down time using a format string. */ + size_t strftime(char* buf, size_t max, const char* format, const struct tm* tm); + #ifdef __cplusplus } #endif diff --git a/libc/src/strftime.cpp b/libc/src/strftime.cpp new file mode 100644 index 00000000..b0a8c0e7 --- /dev/null +++ b/libc/src/strftime.cpp @@ -0,0 +1,140 @@ +#include +#include +#include +#include +#include + +struct strftime_state +{ + char* const buf; + const size_t max; + size_t pos; +}; + +static bool try_put_char(strftime_state& state, char c) +{ + if (state.pos == state.max) return false; + + state.buf[state.pos++] = c; + + return true; +} + +static bool try_put_string(strftime_state& state, const char* s) +{ + if (state.pos + strlen(s) == (state.max - 1)) return false; + + memcpy(state.buf + state.pos, s, strlen(s)); + + state.pos += strlen(s); + + return true; +} + +static bool try_format(strftime_state& state, const char* format, ...) +{ + va_list ap; + va_start(ap, format); + + char buf[1024]; + vsnprintf(buf, sizeof(buf), format, ap); + + va_end(ap); + + return try_put_string(state, buf); +} + +static const char* abday_names[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; +static const char* day_names[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; +static const char* abmon_names[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; +static const char* mon_names[] = { "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" }; + +extern "C" +{ + size_t strftime(char* buf, size_t max, const char* format, const struct tm* tm) + { + strftime_state state = { .buf = buf, .max = max, .pos = 0 }; + + while (*format) + { + if (*format != '%') + { + if (!try_put_char(state, *format)) return 0; + + format++; + continue; + } + else + { + format++; + switch (*format) + { + case 'a': + if (!try_put_string(state, abday_names[tm->tm_wday])) return 0; + break; + case 'A': + if (!try_put_string(state, day_names[tm->tm_wday])) return 0; + break; + case 'b': + if (!try_put_string(state, abmon_names[tm->tm_mon])) return 0; + break; + case 'B': + if (!try_put_string(state, mon_names[tm->tm_mon])) return 0; + break; + case 'c': todo(); + case 'd': + if (!try_format(state, "%.2d", tm->tm_mday)) return 0; + break; + case 'H': + if (!try_format(state, "%.2d", tm->tm_hour)) return 0; + break; + case 'I': { + int hour = tm->tm_hour % 12; + if (hour == 0) hour = 12; + if (!try_format(state, "%.2d", hour)) return 0; + break; + } + case 'j': todo(); + case 'm': + if (!try_format(state, "%.2d", tm->tm_mon + 1)) return 0; + break; + case 'M': + if (!try_format(state, "%.2d", tm->tm_min)) return 0; + break; + case 'p': + if (tm->tm_hour >= 12) + { + if (!try_put_string(state, "PM")) return 0; + } + else + { + if (!try_put_string(state, "AM")) return 0; + } + break; + case 'S': + if (!try_format(state, "%.2d", tm->tm_sec)) return 0; + break; + case 'x': todo(); + case 'X': todo(); + case 'y': + if (!try_format(state, "%.2d", (tm->tm_year + 1900) % 100)) return 0; + break; + case 'Y': + if (!try_format(state, "%d", tm->tm_year + 1900)) return 0; + break; + default: fprintf(stderr, "strftime: unexpected conversion specifier %%%c\n", *format); exit(255); + } + + format++; + continue; + } + } + + state.buf[state.pos] = '\0'; + + return state.pos; + } +} diff --git a/libc/src/time.cpp b/libc/src/time.cpp index d97663f0..14db8a58 100644 --- a/libc/src/time.cpp +++ b/libc/src/time.cpp @@ -85,9 +85,6 @@ static void time_to_struct_tm(time_t time, struct tm* result) result->tm_sec = (int)time; } -const char* wday_names[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; -const char* month_names[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; - extern "C" { int clock_gettime(clockid_t id, struct timespec* ts) @@ -132,8 +129,7 @@ extern "C" char* asctime_r(const struct tm* tm, char buf[26]) { - string_format(buf, 26, "%s %s %.2d %.2d:%.2d:%.2d %4d\n", wday_names[tm->tm_wday], month_names[tm->tm_mon], - tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, tm->tm_year + 1900); + strftime(buf, 26, "%a %b %d %H:%M:%S %Y\n", tm); return buf; }