From d5a6c7f27f3b9fa525f7c2c0c7b6b34de6e1c21d Mon Sep 17 00:00:00 2001 From: apio Date: Sun, 30 Oct 2022 17:47:47 +0100 Subject: [PATCH] libc: Implement strftime() --- libs/libc/src/strftime.cpp | 318 +++++++++++++++++++++++++++++++++++++ libs/libc/src/time.cpp | 8 +- 2 files changed, 319 insertions(+), 7 deletions(-) create mode 100644 libs/libc/src/strftime.cpp diff --git a/libs/libc/src/strftime.cpp b/libs/libc/src/strftime.cpp new file mode 100644 index 00000000..d185f5bf --- /dev/null +++ b/libs/libc/src/strftime.cpp @@ -0,0 +1,318 @@ +#include +#include +#include +#include +#include + +static void printf_strrev(char* arr, int size) +{ + int left = 0; + int right = size - 1; + for (int i = left; i < right; i++) + { + char temp = arr[i]; + arr[i] = arr[right]; + arr[right] = temp; + right--; + } +} + +template static char* printf_unsigned_to_string(IntegerType number, char* arr, int base) +{ + int i = 0; + + if (number == 0) + { + arr[i] = '0'; + arr[i + 1] = '\0'; + return arr; + } + + while (number != 0) + { + IntegerType r = number % (IntegerType)base; + arr[i] = (char)((r > 9) ? (r - 10) + 'a' : r + '0'); + i++; + number /= base; + } + + printf_strrev(arr, i); + + arr[i] = '\0'; + + return arr; +} + +template static char* printf_signed_to_string(IntegerType number, char* arr, int base) +{ + int i = 0, negative = 0; + + if (number == 0) + { + arr[i] = '0'; + arr[i + 1] = '\0'; + return arr; + } + + if (number < 0 && base == 10) + { + number *= -1; + negative = 1; + } + + while (number != 0) + { + IntegerType r = number % base; + arr[i] = (char)((r > 9) ? (r - 10) + 'a' : r + '0'); + i++; + number /= base; + } + + if (negative) + { + arr[i] = '-'; + i++; + } + + printf_strrev(arr, i); + + arr[i] = '\0'; + + return arr; +} + +const char* short_week_days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; +const char* long_week_days[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; +const char* short_month_names[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; +const char* long_month_names[] = {"January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December"}; + +extern "C" size_t strftime(char* s, size_t max, const char* format, const struct tm* time) +{ + char buffer[1025]; // 1024 with null terminator + size_t format_size = strlen(format); + size_t format_index = 0; + size_t buffer_insert_index = 0; + ssize_t max_remaining = (ssize_t)max; + size_t written = 0; + + if (s && max) *s = 0; + + auto flush_buffer = [&]() { + size_t buffer_length = buffer_insert_index; + written += buffer_length; + buffer_insert_index = 0; + if (max_remaining < 0) + { + buffer[buffer_length] = 0; + if (s) strncat(s, buffer, sizeof(buffer)); + return; + } + if (max_remaining == 0) { return; } + if (buffer_length <= (size_t)max_remaining) + { + max_remaining -= buffer_length; + buffer[buffer_length] = 0; + if (s) strncat(s, buffer, sizeof(buffer)); + } + else + { + buffer[max_remaining] = 0; + max_remaining = 0; + if (s) strncat(s, buffer, sizeof(buffer)); + } + }; + + auto append_string = [&](const char* str) { + while (strlen(str) > 1024) + { + flush_buffer(); + memcpy(buffer, str, 1024); + str += 1024; + buffer_insert_index = 1024; + } + if (buffer_insert_index + strlen(str) > 1024) flush_buffer(); + memcpy(buffer + buffer_insert_index, str, strlen(str)); + buffer_insert_index += strlen(str); + if (buffer_insert_index == 1024) flush_buffer(); + }; + + auto append_char = [&](char c) { + buffer[buffer_insert_index++] = c; + if (buffer_insert_index == 1024) flush_buffer(); + }; + + while (format_index < format_size) + { + char current_char = format[format_index]; + if (current_char == '%') + { + if (format_index + 1 == format_size) // end of format string + { + format_index++; + continue; + } + else + { + format_index++; + current_char = format[format_index]; + switch (current_char) + { + case '%': { + append_char('%'); + break; + } + case 'a': { + append_string(short_week_days[time->tm_wday % 7]); + break; + } + case 'A': { + append_string(long_week_days[time->tm_wday % 7]); + break; + } + case 'b': { + append_string(short_month_names[(time->tm_mon - 1) % 12]); + break; + } + case 'B': { + append_string(long_month_names[(time->tm_mon - 1) % 12]); + break; + } + case 'c': { + char buf[32]; + append_string(short_week_days[time->tm_wday % 7]); + append_char(' '); + append_string(short_month_names[(time->tm_mon - 1) % 12]); + append_char(' '); + printf_signed_to_string(time->tm_mday, buf, 10); + append_string(buf); + append_char(' '); + printf_signed_to_string(time->tm_hour % 24, buf, 10); + append_string(buf); + append_char(':'); + printf_signed_to_string(time->tm_min % 60, buf, 10); + append_string(buf); + append_char(':'); + printf_signed_to_string(time->tm_sec % 60, buf, 10); + append_string(buf); + append_char(' '); + printf_signed_to_string(time->tm_year + 1900, buf, 10); + append_string(buf); + break; + } + case 'd': { + char buf[30]; + printf_signed_to_string(time->tm_mday % 32, buf, 10); + append_string(buf); + break; + } + case 'H': { + char buf[30]; + printf_signed_to_string(time->tm_hour % 24, buf, 10); + append_string(buf); + break; + } + case 'I': { + char buf[30]; + int hour = time->tm_hour % 12; + if (hour == 0) hour = 12; + printf_signed_to_string(hour, buf, 10); + append_string(buf); + break; + } + case 'j': { + char buf[30]; + printf_signed_to_string(time->tm_yday % 367, buf, 10); + append_string(buf); + break; + } + case 'm': { + char buf[30]; + printf_signed_to_string(time->tm_mon % 13, buf, 10); + append_string(buf); + break; + } + case 'M': { + char buf[30]; + printf_signed_to_string(time->tm_min % 60, buf, 10); + append_string(buf); + break; + } + case 'p': { + if (time->tm_hour < 12) append_string("AM"); + else + append_string("PM"); + break; + } + case 'P': { + if (time->tm_hour < 12) append_string("am"); + else + append_string("pm"); + break; + } + case 'S': { + char buf[30]; + printf_signed_to_string(time->tm_sec % 61, buf, 10); + append_string(buf); + break; + } + case 'w': { + char buf[30]; + printf_signed_to_string(time->tm_wday % 7, buf, 10); + append_string(buf); + break; + } + case 'x': { + char buf[30]; + printf_signed_to_string(time->tm_mon % 13, buf, 10); + append_string(buf); + append_char('/'); + printf_signed_to_string(time->tm_mday % 32, buf, 10); + append_string(buf); + append_char('/'); + printf_signed_to_string((time->tm_year + 1900) % 100, buf, 10); + append_string(buf); + break; + } + case 'X': { + char buf[30]; + printf_signed_to_string(time->tm_hour % 24, buf, 10); + append_string(buf); + append_char(':'); + printf_signed_to_string(time->tm_min % 60, buf, 10); + append_string(buf); + append_char(':'); + printf_signed_to_string(time->tm_sec % 60, buf, 10); + append_string(buf); + break; + } + case 'y': { + char buf[30]; + printf_signed_to_string((time->tm_year + 1900) % 100, buf, 10); + append_string(buf); + break; + } + case 'Y': { + char buf[30]; + printf_signed_to_string(time->tm_year + 1900, buf, 10); + append_string(buf); + break; + } + case 'Z': { + append_string("UTC"); // FIXME: Add timezone support. + break; + } + default: { + fprintf(stderr, "strftime: unknown format specifier %%%c\n", current_char); + abort(); + } + } + } + } + else { append_char(current_char); } + format_index++; + } + + if (buffer_insert_index > 0) flush_buffer(); + return written; +} \ No newline at end of file diff --git a/libs/libc/src/time.cpp b/libs/libc/src/time.cpp index 9cd7aab3..1c90cfc8 100644 --- a/libs/libc/src/time.cpp +++ b/libs/libc/src/time.cpp @@ -135,8 +135,7 @@ extern "C" char* asctime_r(const struct tm* time, char buf[26]) { - snprintf(buf, 26, "%s %s %d %d:%d:%d %d\n", wday_names[time->tm_wday], month_names[time->tm_mon - 1], - time->tm_mday, time->tm_hour, time->tm_min, time->tm_sec, time->tm_year + 1900); + strftime(buf, 26, "%a %b %d %H:%M:%S %Y\n", time); return buf; } @@ -156,9 +155,4 @@ extern "C" { return asctime(localtime(time)); } - - size_t strftime(char*, size_t, const char*, const struct tm*) - { - NOT_IMPLEMENTED("strftime"); - } } \ No newline at end of file