diff --git a/userspace/libraries/LibC/time.cpp b/userspace/libraries/LibC/time.cpp index 8dbbd0d7..a1ae89a9 100644 --- a/userspace/libraries/LibC/time.cpp +++ b/userspace/libraries/LibC/time.cpp @@ -1,6 +1,8 @@ -#include #include +#include +#include +#include #include #include #include @@ -107,6 +109,346 @@ struct tm* localtime(const time_t* timer) size_t strftime(char* __restrict s, size_t maxsize, const char* __restrict format, const struct tm* __restrict timeptr) { - dwarnln("strftime({}, {}, {}, {})", s, maxsize, format, timeptr); - ASSERT_NOT_REACHED(); + size_t len = 0; + + struct conversion_t + { + char flag = '\0'; + int width = -1; + char modifier = '\0'; + }; + + static constexpr const char* abbr_wday[] { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" + }; + static constexpr const char* full_wday[] { + "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" + }; + + static constexpr const char* abbr_mon[] { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; + static constexpr const char* full_mon[] { + "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" + }; + + const auto append_string = + [&](const char* string) -> bool + { + size_t string_len = strlen(string); + if (len + string_len >= maxsize) + return false; + strcpy(s + len, string); + len += string_len; + return true; + }; + + const auto append_string_from_list = + [&](int index, const char* const (&list)[LIST_SIZE]) -> bool + { + const char* string = "INVALID"; + if (index >= 0 && index < (int)LIST_SIZE) + string = list[index]; + return append_string(string); + }; + + const auto append_value = + [&](const char* format, int value) -> bool + { + int written = snprintf(s + len, maxsize - len, format, value); + if (len + written >= maxsize) + return false; + len += written; + return true; + }; + + while (*format && len < maxsize) + { + if (*format != '%') + { + s[len++] = *format++; + continue; + } + + format++; + + conversion_t conversion; + switch (*format) + { + case '+': + case '0': + conversion.flag = *format; + format++; + break; + } + if (isdigit(*format)) + { + conversion.width = 0; + while (isdigit(*format)) + { + conversion.width = (conversion.width * 10) + (*format + '0'); + format++; + } + } + switch (*format) + { + case 'E': + case 'O': + dwarnln("TODO: strftime moodifiers"); + conversion.modifier = *format; + format++; + break; + } + + switch (*format) + { + case 'a': + if (!append_string_from_list(timeptr->tm_wday, abbr_wday)) + return 0; + break; + case 'A': + if (!append_string_from_list(timeptr->tm_wday, full_wday)) + return 0; + break; + case 'b': + case 'h': + if (!append_string_from_list(timeptr->tm_mon, abbr_mon)) + return 0; + break; + case 'B': + if (!append_string_from_list(timeptr->tm_mon, full_mon)) + return 0; + break; + case 'c': + if (size_t ret = strftime(s + len, maxsize - len, "%a %b %e %H:%M:%S %Y", timeptr)) + len += ret; + else return 0; + break; + case 'C': + { + if (conversion.flag == '\0') + conversion.flag = ' '; + if (conversion.flag == '+' && conversion.width <= 2) + conversion.flag = '0'; + if (conversion.width < 2) + conversion.width = 2; + + char new_format[32]; + sprintf(new_format, "%%%c%dd", conversion.flag, conversion.width); + if (!append_value(new_format, timeptr->tm_year % 100)) + return 0; + break; + } + case 'd': + if (!append_value("%02d", timeptr->tm_mday)) + return 0; + break; + case 'D': + if (size_t ret = strftime(s + len, maxsize - len, "%m/%d/%y", timeptr)) + len += ret; + else return 0; + break; + case 'e': + if (!append_value("% 2d", timeptr->tm_mday)) + return 0; + break; + case 'F': + { + if (conversion.flag == '\0') + conversion.flag = '+'; + if (conversion.width == -1) + conversion.width = 10; + if (conversion.width < 6) + conversion.width = 6; + + char new_format[32]; + sprintf(new_format, "%%%c%dY-%%m-%%d", conversion.flag, conversion.width - 6); + + if (size_t ret = strftime(s + len, maxsize - len, new_format, timeptr)) + len += ret; + else return 0; + break; + } + case 'H': + if (!append_value("%02d", timeptr->tm_hour)) + return 0; + break; + case 'I': + if (!append_value("%02d", ((timeptr->tm_hour + 11) % 12) + 1)) + return 0; + break; + case 'j': + if (!append_value("%03d", timeptr->tm_yday + 1)) + return 0; + break; + case 'm': + if (!append_value("%02d", timeptr->tm_mon + 1)) + return 0; + break; + case 'M': + if (!append_value("%02d", timeptr->tm_min)) + return 0; + break; + case 'n': + s[len++] = '\n'; + break; + case 'p': + if (!append_string(timeptr->tm_hour < 12 ? "AM" : "PM")) + return 0; + break; + case 'r': + if (size_t ret = strftime(s + len, maxsize - len, "%I:%M:%S %p", timeptr)) + len += ret; + else return 0; + break; + case 'R': + if (size_t ret = strftime(s + len, maxsize - len, "%H:%M", timeptr)) + len += ret; + else return 0; + break; + case 'S': + if (!append_value("%02d", timeptr->tm_sec)) + return 0; + break; + case 't': + s[len++] = '\t'; + break; + case 'T': + if (size_t ret = strftime(s + len, maxsize - len, "%H:%M:%S", timeptr)) + len += ret; + else return 0; + break; + case 'u': + if (!append_value("%d", ((timeptr->tm_wday + 6) % 7) + 1)) + return 0; + break; + case 'U': + if (!append_value("%02d", (timeptr->tm_yday - timeptr->tm_wday + 7) / 7)) + return 0; + break; + case 'g': + case 'G': + case 'V': + { + // Adapted from GNU libc implementation + + constexpr auto is_leap_year = + [](int year) -> bool + { + if (year % 400 == 0) + return true; + if (year % 100 == 0) + return false; + if (year % 4 == 0) + return true; + return false; + }; + + constexpr auto iso_week_days = + [](int yday, int wday) -> int + { + return yday - (wday + 382) % 7 + 3; + }; + + int year = timeptr->tm_year + 1900; + int days = iso_week_days(timeptr->tm_yday, timeptr->tm_wday); + + if (days < 0) + { + year--; + days = iso_week_days(timeptr->tm_yday + (365 + is_leap_year(year)), timeptr->tm_wday); + } + else + { + int d = iso_week_days(timeptr->tm_yday - (365 + is_leap_year(year)), timeptr->tm_wday); + if (d >= 0) + { + year++; + days = d; + } + } + + switch (*format) + { + case 'g': + if (!append_value("%02d", ((year % 100) + 100) % 100)) + return 0; + break; + case 'G': + { + if (conversion.flag == '\0') + conversion.flag = ' '; + if (conversion.flag == '+' && conversion.width <= 4) + conversion.flag = '0'; + if (conversion.width == -1) + conversion.width = 0; + + char new_format[32]; + sprintf(new_format, "%%%c%dd", conversion.flag, conversion.width); + if (!append_value(new_format, year)) + return 0; + break; + } + case 'V': + if (!append_value("%02d", days / 7 + 1)) + return 0; + break; + } + break; + } + case 'w': + if (!append_value("%d", timeptr->tm_wday)) + return 0; + break; + case 'W': + if (!append_value("%02d", (timeptr->tm_yday - (timeptr->tm_wday - 1 + 7) % 7 + 7) / 7)) + return 0; + break; + case 'x': + if (size_t ret = strftime(s + len, maxsize - len, "%m/%d/%y", timeptr)) + len += ret; + else return 0; + break; + case 'X': + if (size_t ret = strftime(s + len, maxsize - len, "%H:%M:%S", timeptr)) + len += ret; + else return 0; + break; + case 'y': + if (!append_value("%d", timeptr->tm_yday % 100)) + return 0; + break; + case 'Y': + { + if (conversion.flag == '\0') + conversion.flag = ' '; + if (conversion.flag == '+' && conversion.width <= 4) + conversion.flag = '0'; + if (conversion.width == -1) + conversion.width = 0; + + char new_format[32]; + sprintf(new_format, "%%%c%dd", conversion.flag, conversion.width); + if (!append_value(new_format, 1900 + timeptr->tm_year)) + return 0; + break; + } + case 'z': + // FIXME: support timezones + break; + case 'Z': + // FIXME: support timezones + break; + case '%': + s[len++] = '%'; + break; + } + + format++; + } + + if (*format != '\0') + return 0; + s[len++] = '\0'; + return len; }