libc: Implement strftime()

This commit is contained in:
apio 2022-10-30 17:47:47 +01:00
parent 4c096bd36c
commit d5a6c7f27f
2 changed files with 319 additions and 7 deletions

318
libs/libc/src/strftime.cpp Normal file
View File

@ -0,0 +1,318 @@
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
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 <typename IntegerType> 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 <typename IntegerType> 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<int>(time->tm_mday, buf, 10);
append_string(buf);
append_char(' ');
printf_signed_to_string<int>(time->tm_hour % 24, buf, 10);
append_string(buf);
append_char(':');
printf_signed_to_string<int>(time->tm_min % 60, buf, 10);
append_string(buf);
append_char(':');
printf_signed_to_string<int>(time->tm_sec % 60, buf, 10);
append_string(buf);
append_char(' ');
printf_signed_to_string<int>(time->tm_year + 1900, buf, 10);
append_string(buf);
break;
}
case 'd': {
char buf[30];
printf_signed_to_string<int>(time->tm_mday % 32, buf, 10);
append_string(buf);
break;
}
case 'H': {
char buf[30];
printf_signed_to_string<int>(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<int>(hour, buf, 10);
append_string(buf);
break;
}
case 'j': {
char buf[30];
printf_signed_to_string<int>(time->tm_yday % 367, buf, 10);
append_string(buf);
break;
}
case 'm': {
char buf[30];
printf_signed_to_string<int>(time->tm_mon % 13, buf, 10);
append_string(buf);
break;
}
case 'M': {
char buf[30];
printf_signed_to_string<int>(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<int>(time->tm_sec % 61, buf, 10);
append_string(buf);
break;
}
case 'w': {
char buf[30];
printf_signed_to_string<int>(time->tm_wday % 7, buf, 10);
append_string(buf);
break;
}
case 'x': {
char buf[30];
printf_signed_to_string<int>(time->tm_mon % 13, buf, 10);
append_string(buf);
append_char('/');
printf_signed_to_string<int>(time->tm_mday % 32, buf, 10);
append_string(buf);
append_char('/');
printf_signed_to_string<int>((time->tm_year + 1900) % 100, buf, 10);
append_string(buf);
break;
}
case 'X': {
char buf[30];
printf_signed_to_string<int>(time->tm_hour % 24, buf, 10);
append_string(buf);
append_char(':');
printf_signed_to_string<int>(time->tm_min % 60, buf, 10);
append_string(buf);
append_char(':');
printf_signed_to_string<int>(time->tm_sec % 60, buf, 10);
append_string(buf);
break;
}
case 'y': {
char buf[30];
printf_signed_to_string<int>((time->tm_year + 1900) % 100, buf, 10);
append_string(buf);
break;
}
case 'Y': {
char buf[30];
printf_signed_to_string<int>(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;
}

View File

@ -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");
}
}