diff --git a/meson.build b/meson.build index 5fae3307..47c3bca5 100644 --- a/meson.build +++ b/meson.build @@ -221,6 +221,7 @@ internal_sources = [ 'options/internal/generic/frigg.cpp', 'options/internal/generic/inline-emitter.cpp', 'options/internal/generic/sigset.cpp', + 'options/internal/generic/strings.cpp', 'options/internal/generic/ubsan.cpp', 'options/internal/gcc/stack_protector.cpp', 'options/internal/gcc/guard-abi.cpp', diff --git a/options/ansi/generic/time-stubs.cpp b/options/ansi/generic/time-stubs.cpp index a4736065..112876bf 100644 --- a/options/ansi/generic/time-stubs.cpp +++ b/options/ansi/generic/time-stubs.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include @@ -615,478 +616,6 @@ char *ctime_r(const time_t *clock, char *buf) { return asctime_r(localtime(clock), buf); } -namespace { - -int month_to_day(int month) { - switch(month){ - case 0: return 0; - case 1: return 31; - case 2: return 59; - case 3: return 90; - case 4: return 120; - case 5: return 151; - case 6: return 181; - case 7: return 212; - case 8: return 243; - case 9: return 273; - case 10: return 304; - case 11: return 334; - } - return -1; -} - -int is_leapyear(int year) { - return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); -} - -int month_and_year_to_day_in_year(int month, int year){ - int day = month_to_day(month); - if(is_leapyear(year) && month < 2) - return day + 1; - - return day; -} - -int target_determination(int month) { - switch(month){ - case 0: return 3; - case 1: return 14; - case 2: return 14; - case 3: return 4; - case 4: return 9; - case 5: return 6; - case 6: return 11; - case 7: return 8; - case 8: return 5; - case 9: return 10; - case 10: return 7; - case 11: return 12; - } - - return -1; -} - -int doom_determination(int full_year) { - int century = full_year / 100; - int anchor = 2 + 5 * (century % 4) % 7; - - int year = full_year % 100; - - if(year % 2) - year += 11; - - year /= 2; - - if(year % 2) - year += 11; - - return 7 - (year % 7) + anchor; -} - -//Determine day of week through the doomsday algorithm. -int day_determination(int day, int month, int year) { - int doom = doom_determination(year); - bool leap = is_leapyear(year); - - int target = target_determination(month); - if(leap && month < 2) - target++; - - int doom_dif = (day - target) % 7; - return (doom + doom_dif) % 7; -} - -struct strptime_internal_state { - bool has_century; - bool has_year; - bool has_month; - bool has_day_of_month; - bool has_day_of_year; - bool has_day_of_week; - - bool full_year_given; - - int century; - - size_t format_index; - size_t input_index; -}; - -char *strptime_internal(const char *__restrict input, const char *__restrict format, - struct tm *__restrict tm, struct strptime_internal_state *__restrict state) { - while(isspace(input[state->input_index])) - state->input_index++; - - if(input[state->input_index] == '\0') - return NULL; - - while(format[state->format_index] != '\0'){ - if(format[state->format_index] != '%'){ - if(isspace(format[state->format_index])){ - while(isspace(input[state->input_index++])); - state->input_index--; - } - else { - if(format[state->format_index] != input[state->input_index++]) - return NULL; - } - state->format_index++; - continue; - } - state->format_index++; - switch(format[state->format_index]){ - case '%': - if(input[state->input_index++] != '%') - return NULL; - break; - case 'a': - case 'A': - __ensure(!"strptime() %a and %A directives unimplemented."); - __builtin_unreachable(); - break; - case 'b': - case 'B': - case 'h': - __ensure(!"strptime() %b, %B and %h directives unimplemented."); - __builtin_unreachable(); - break; - case 'c': - __ensure(!"strptime() %c directive unimplemented."); - __builtin_unreachable(); - break; - case 'C': { - int product = 0, n = 0; - sscanf(&input[state->input_index], "%d%n", &product, &n); - if(n == 0 || 2 < n) - return NULL; - state->input_index += n; - state->century = product; - state->has_century = true; - break; - } - case 'd': //`%d` and `%e` are equivalent - case 'e': { - int product = 0, n = 0; - sscanf(&input[state->input_index], "%d%n", &product, &n); - if(n == 0 || 2 < n) - return NULL; - state->input_index += n; - tm->tm_mday = product; - state->has_day_of_month = true; - break; - } - case 'D': { //equivalent to `%m/%d/%y` - size_t pre_fi = state->format_index; - state->format_index = 0; - - char *result = strptime_internal(input, "%m/%d/%y", tm, state); - if(result == NULL) - return NULL; - - state->format_index = pre_fi; - break; - } - case 'H': { - int product = 0, n = 0; - sscanf(&input[state->input_index], "%d%n", &product, &n); - if(n == 0 || 2 < n) - return NULL; - state->input_index += n; - tm->tm_hour = product; - break; - } - case 'I': - __ensure(!"strptime() %I directive unimplemented."); - __builtin_unreachable(); - break; - case 'j': { - int product = 0, n = 0; - sscanf(&input[state->input_index], "%d%n", &product, &n); - if(n == 0 || 3 < n) - return NULL; - state->input_index += n; - tm->tm_yday = product - 1; - state->has_day_of_year = true; - break; - } - case 'm': { - int product = 0, n = 0; - sscanf(&input[state->input_index], "%d%n", &product, &n); - if(n == 0 || 2 < n) - return NULL; - state->input_index += n; - tm->tm_mon = product - 1; - state->has_month = true; - break; - } - case 'M': { - int product = 0, n = 0; - sscanf(&input[state->input_index], "%d%n", &product, &n); - if(n == 0 || 2 < n) - return NULL; - state->input_index += n; - tm->tm_min = product; - break; - } - case 'n': - case 't': { - size_t n = 0; - while(isspace(input[state->input_index++])) - n++; - if(n == 0) - return NULL; - state->input_index--; - break; - } - case 'p': - __ensure(!"strptime() %p directive unimplemented."); - __builtin_unreachable(); - break; - case 'r': - __ensure(!"strptime() %r directive unimplemented."); - __builtin_unreachable(); - break; - case 'R': { //equivalent to `%H:%M` - size_t pre_fi = state->format_index; - state->format_index = 0; - - char *result = strptime_internal(input, "%H:%M", tm, state); - if(result == NULL) - return NULL; - - state->format_index = pre_fi; - break; - } - case 'S': { - int product = 0, n = 0; - sscanf(&input[state->input_index], "%d%n", &product, &n); - if(n == 0 || 2 < n) - return NULL; - state->input_index += n; - tm->tm_sec = product; - break; - } - case 'T': { //equivalent to `%H:%M:%S` - size_t pre_fi = state->format_index; - state->format_index = 0; - - char *result = strptime_internal(input, "%H:%M:%S", tm, state); - if(result == NULL) - return NULL; - - state->format_index = pre_fi; - break; - } - case 'U': - __ensure(!"strptime() %U directive unimplemented."); - __builtin_unreachable(); - break; - case 'w': { - int product = 0, n = 0; - sscanf(&input[state->input_index], "%d%n", &product, &n); - if(n == 0 || 1 < n) - return NULL; - state->input_index += n; - tm->tm_wday = product; - state->has_day_of_week = true; - break; - } - case 'W': - __ensure(!"strptime() %W directive unimplemented."); - __builtin_unreachable(); - break; - case 'x': - __ensure(!"strptime() %x directive unimplemented."); - __builtin_unreachable(); - break; - case 'X': - __ensure(!"strptime() %X directive unimplemented."); - __builtin_unreachable(); - break; - case 'y': { - int product = 0, n = 0; - sscanf(&input[state->input_index], "%d%n", &product, &n); - if(n == 0 || 2 < n) - return NULL; - if(product < 69) - product += 100; - state->input_index += n; - tm->tm_year = product; - state->has_year = true; - break; - } - case 'Y': { - int product = 0, n = 0; - sscanf(&input[state->input_index], "%d%n", &product, &n); - if(n == 0 || 4 < n) - return NULL; - state->input_index += n; - tm->tm_year = product - 1900; - state->has_year = true; - state->has_century = true; - state->full_year_given = true; - state->century = product / 100; - break; - } - case 'F': { //GNU extensions - //equivalent to `%Y-%m-%d` - size_t pre_fi = state->format_index; - state->format_index = 0; - - char *result = strptime_internal(input, "%Y-%m-%d", tm, state); - if(result == NULL) - return NULL; - - state->format_index = pre_fi; - break; - } - case 'g': - __ensure(!"strptime() %g directive unimplemented."); - __builtin_unreachable(); - break; - case 'G': - __ensure(!"strptime() %G directive unimplemented."); - __builtin_unreachable(); - break; - case 'u': { - int product = 0, n = 0; - sscanf(&input[state->input_index], "%d%n", &product, &n); - if(n == 0 || 1 < n) - return NULL; - state->input_index += n; - tm->tm_wday = product - 1; - break; - } - case 'V': - __ensure(!"strptime() %V directive unimplemented."); - __builtin_unreachable(); - break; - case 'z': - __ensure(!"strptime() %z directive unimplemented."); - __builtin_unreachable(); - break; - case 'Z': - __ensure(!"strptime() %Z directive unimplemented."); - __builtin_unreachable(); - break; - case 's': //end of GNU extensions - __ensure(!"strptime() %s directive unimplemented."); - __builtin_unreachable(); - break; - case 'E': { //locale-dependent date & time representation - __ensure(!"strptime() %E* directives unimplemented."); - __builtin_unreachable(); - /* - state->format_index++; - switch(format[state->format_index]){ - case 'c': - break; - case 'C': - break; - case 'x': - break; - case 'X': - break; - case 'y': - break; - case 'Y': - break; - default: - return NULL; - } - */ - } - case 'O': { //locale-dependent numeric symbols - __ensure(!"strptime() %O* directives unimplemented."); - __builtin_unreachable(); - /* - state->format_index++; - switch(format[state->format_index]){ - case 'd': - case 'e': - break; - case 'H': - break; - case 'I': - break; - case 'm': - break; - case 'M': - break; - case 'S': - break; - case 'U': - break; - case 'w': - break; - case 'W': - break; - case 'y': - break; - default: - return NULL; - } - */ - } - default: - return NULL; - } - state->format_index++; - } - - return (char*)input + state->input_index; -} - -} //anonymous namespace - -char *strptime(const char *__restrict s, const char *__restrict format, struct tm *__restrict tm){ - struct strptime_internal_state state = {}; - - char *result = strptime_internal(s, format, tm, &state); - - if(result == NULL) - return NULL; - - if(state.has_century && !state.full_year_given){ - int full_year = state.century * 100; - - if(state.has_year){ - //Compensate for default century-adjustment of `%j` operand - if(tm->tm_year >= 100) - full_year += tm->tm_year - 100; - else - full_year += tm->tm_year; - } - - tm->tm_year = full_year - 1900; - - state.has_year = true; - } - - if(state.has_month && !state.has_day_of_year){ - int day = 0; - if(state.has_year) - day = month_and_year_to_day_in_year(tm->tm_mon, tm->tm_year); - else - day = month_to_day(tm->tm_mon); - - tm->tm_yday = day + tm->tm_mday - 1; - state.has_day_of_year = true; - } - - if(state.has_year && !state.has_day_of_week){ - if(!state.has_month && !state.has_day_of_month){ - tm->tm_wday = day_determination(0, 0, tm->tm_year + 1900); - } - else if(state.has_month && state.has_day_of_month){ - tm->tm_wday = day_determination(tm->tm_mday, tm->tm_mon, tm->tm_year + 1900); - } - state.has_day_of_week = true; - } - - return result; -} - time_t timelocal(struct tm *) { __ensure(!"Not implemented"); __builtin_unreachable(); diff --git a/options/ansi/include/time.h b/options/ansi/include/time.h index 9fbbceed..ecd6c224 100644 --- a/options/ansi/include/time.h +++ b/options/ansi/include/time.h @@ -99,8 +99,10 @@ struct tm *localtime_r(const time_t *, struct tm *); char *asctime_r(const struct tm *tm, char *buf); char *ctime_r(const time_t *, char *); +#ifdef __MLIBC_POSIX_OPTION char *strptime(const char *__restrict, const char *__restrict, struct tm *__restrict); +#endif #ifdef __cplusplus } diff --git a/options/internal/generic/strings.cpp b/options/internal/generic/strings.cpp new file mode 100644 index 00000000..ce4f84bf --- /dev/null +++ b/options/internal/generic/strings.cpp @@ -0,0 +1,22 @@ +#include + +#include + +namespace mlibc { + +int strncasecmp(const char *a, const char *b, size_t size) { + for(size_t i = 0; i < size; i++) { + unsigned char a_byte = tolower(a[i]); + unsigned char b_byte = tolower(b[i]); + if(!a_byte && !b_byte) + return 0; + // If only one char is null, one of the following cases applies. + if(a_byte < b_byte) + return -1; + if(a_byte > b_byte) + return 1; + } + return 0; +} + +} diff --git a/options/internal/include/mlibc/strings.hpp b/options/internal/include/mlibc/strings.hpp new file mode 100644 index 00000000..5a93c7c6 --- /dev/null +++ b/options/internal/include/mlibc/strings.hpp @@ -0,0 +1,12 @@ +#ifndef MLIBC_STRINGS +#define MLIBC_STRINGS + +#include + +namespace mlibc { + +int strncasecmp(const char *a, const char *b, size_t size); + +} // namespace mlibc + +#endif // MLIBC_STRINGS diff --git a/options/posix/generic/langinfo-stubs.cpp b/options/posix/generic/langinfo-stubs.cpp index 5772ad70..7a86e62e 100644 --- a/options/posix/generic/langinfo-stubs.cpp +++ b/options/posix/generic/langinfo-stubs.cpp @@ -22,6 +22,48 @@ char *nl_langinfo(nl_item item) { case ABMON_12: return const_cast("Dec"); } __builtin_unreachable(); + } else if(item >= MON_1 && item <= MON_12) { + switch(item) { + case MON_1: return const_cast("January"); + case MON_2: return const_cast("Feburary"); + case MON_3: return const_cast("March"); + case MON_4: return const_cast("April"); + case MON_5: return const_cast("May"); + case MON_6: return const_cast("June"); + case MON_7: return const_cast("July"); + case MON_8: return const_cast("August"); + case MON_9: return const_cast("September"); + case MON_10: return const_cast("October"); + case MON_11: return const_cast("November"); + case MON_12: return const_cast("December"); + } + __builtin_unreachable(); + } else if(item == AM_STR) { + return const_cast("AM"); + } else if(item == PM_STR) { + return const_cast("PM"); + } else if(item >= DAY_1 && item <= DAY_7) { + switch(item) { + case DAY_1: return const_cast("Sunday"); + case DAY_2: return const_cast("Monday"); + case DAY_3: return const_cast("Tuesday"); + case DAY_4: return const_cast("Wednesday"); + case DAY_5: return const_cast("Thursday"); + case DAY_6: return const_cast("Friday"); + case DAY_7: return const_cast("Saturday"); + } + __builtin_unreachable(); + } else if(item >= ABDAY_1 && item <= ABDAY_7) { + switch(item) { + case ABDAY_1: return const_cast("Sun"); + case ABDAY_2: return const_cast("Mon"); + case ABDAY_3: return const_cast("Tue"); + case ABDAY_4: return const_cast("Wed"); + case ABDAY_5: return const_cast("Thu"); + case ABDAY_6: return const_cast("Fri"); + case ABDAY_7: return const_cast("Sat"); + } + __builtin_unreachable(); }else{ mlibc::infoLogger() << "mlibc: nl_langinfo item " << item << " is not implemented properly" << frg::endlog; diff --git a/options/posix/generic/strings-stubs.cpp b/options/posix/generic/strings-stubs.cpp index e25c7901..e4de0702 100644 --- a/options/posix/generic/strings-stubs.cpp +++ b/options/posix/generic/strings-stubs.cpp @@ -4,6 +4,7 @@ #include #include +#include char *index (const char *s, int c) { return strchr(s, c); @@ -88,17 +89,6 @@ int strcasecmp(const char *a, const char *b) { } int strncasecmp(const char *a, const char *b, size_t size) { - for(size_t i = 0; i < size; i++) { - unsigned char a_byte = tolower(a[i]); - unsigned char b_byte = tolower(b[i]); - if(!a_byte && !b_byte) - return 0; - // If only one char is null, one of the following cases applies. - if(a_byte < b_byte) - return -1; - if(a_byte > b_byte) - return 1; - } - return 0; + return mlibc::strncasecmp(a, b, size); } diff --git a/options/posix/generic/time.cpp b/options/posix/generic/time.cpp new file mode 100644 index 00000000..14193afc --- /dev/null +++ b/options/posix/generic/time.cpp @@ -0,0 +1,505 @@ +#include +#include +#include +#include +#include + +#include +#include + +namespace { + +int month_to_day(int month) { + switch(month){ + case 0: return 0; + case 1: return 31; + case 2: return 59; + case 3: return 90; + case 4: return 120; + case 5: return 151; + case 6: return 181; + case 7: return 212; + case 8: return 243; + case 9: return 273; + case 10: return 304; + case 11: return 334; + } + return -1; +} + +int is_leapyear(int year) { + return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); +} + +int month_and_year_to_day_in_year(int month, int year){ + int day = month_to_day(month); + if(is_leapyear(year) && month < 2) + return day + 1; + + return day; +} + +int target_determination(int month) { + switch(month){ + case 0: return 3; + case 1: return 14; + case 2: return 14; + case 3: return 4; + case 4: return 9; + case 5: return 6; + case 6: return 11; + case 7: return 8; + case 8: return 5; + case 9: return 10; + case 10: return 7; + case 11: return 12; + } + + return -1; +} + +int doom_determination(int full_year) { + int century = full_year / 100; + int anchor = 2 + 5 * (century % 4) % 7; + + int year = full_year % 100; + + if(year % 2) + year += 11; + + year /= 2; + + if(year % 2) + year += 11; + + return 7 - (year % 7) + anchor; +} + +//Determine day of week through the doomsday algorithm. +int day_determination(int day, int month, int year) { + int doom = doom_determination(year); + bool leap = is_leapyear(year); + + int target = target_determination(month); + if(leap && month < 2) + target++; + + int doom_dif = (day - target) % 7; + return (doom + doom_dif) % 7; +} + +struct strptime_internal_state { + bool has_century; + bool has_year; + bool has_month; + bool has_day_of_month; + bool has_day_of_year; + bool has_day_of_week; + + bool full_year_given; + + int century; + + size_t format_index; + size_t input_index; +}; + +char *strptime_internal(const char *__restrict input, const char *__restrict format, + struct tm *__restrict tm, struct strptime_internal_state *__restrict state) { + auto matchLanginfoItem = [&] (int start, size_t num, int &dest, bool &flag) -> bool { + for(size_t i = start; i < (start + num); i++) { + const char *mon = nl_langinfo(i); + size_t len = strlen(mon); + if(mlibc::strncasecmp(&input[state->input_index], mon, len)) + continue; + state->input_index += len; + dest = i - start; + flag = true; + return true; + } + return false; + }; + + auto matchNumericRange = [&] (int start, int end, int &dest, bool *flag) -> bool { + int product = 0, n = 0; + sscanf(&input[state->input_index], "%d%n", &product, &n); + if(n == 0 || 2 < n) + return false; + if(product < start || product > end) + return false; + state->input_index += n; + dest = product; + if(flag) *flag = true; + return true; + }; + + while(isspace(input[state->input_index])) + state->input_index++; + + if(input[state->input_index] == '\0') + return NULL; + + while(format[state->format_index] != '\0'){ + if(format[state->format_index] != '%'){ + if(isspace(format[state->format_index])){ + while(isspace(input[state->input_index++])); + state->input_index--; + } + else { + if(format[state->format_index] != input[state->input_index++]) + return NULL; + } + state->format_index++; + continue; + } + state->format_index++; + switch(format[state->format_index]){ + case '%': + if(input[state->input_index++] != '%') + return NULL; + break; + case 'a': + case 'A': { + if (!matchLanginfoItem(DAY_1, 7, tm->tm_wday, state->has_day_of_week) && \ + !matchLanginfoItem(ABDAY_1, 7, tm->tm_wday, state->has_day_of_week)) + return NULL; + break; + } + case 'b': + case 'B': + case 'h': { + if (!matchLanginfoItem(MON_1, 12, tm->tm_mon, state->has_month) && \ + !matchLanginfoItem(ABMON_1, 12, tm->tm_mon, state->has_month)) + return NULL; + break; + } + case 'c': + __ensure(!"strptime() %c directive unimplemented."); + __builtin_unreachable(); + break; + case 'C': { + int product = 0, n = 0; + sscanf(&input[state->input_index], "%d%n", &product, &n); + if(n == 0 || 2 < n) + return NULL; + state->input_index += n; + state->century = product; + state->has_century = true; + break; + } + case 'd': //`%d` and `%e` are equivalent + case 'e': { + if(!matchNumericRange(1, 31, tm->tm_mday, &state->has_day_of_month)) + return NULL; + break; + } + case 'D': { //equivalent to `%m/%d/%y` + size_t pre_fi = state->format_index; + state->format_index = 0; + + char *result = strptime_internal(input, "%m/%d/%y", tm, state); + if(result == NULL) + return NULL; + + state->format_index = pre_fi; + break; + } + case 'H': { + if(!matchNumericRange(0, 23, tm->tm_hour, nullptr)) + return NULL; + break; + } + case 'I': { + if(!matchNumericRange(1, 12, tm->tm_hour, nullptr)) + return NULL; + break; + } + case 'j': { + if(!matchNumericRange(1, 366, tm->tm_yday, &state->has_day_of_year)) + return NULL; + tm->tm_yday--; + break; + } + case 'm': { + if(!matchNumericRange(1, 12, tm->tm_mon, &state->has_month)) + return NULL; + tm->tm_mon--; + break; + } + case 'M': { + if(!matchNumericRange(0, 59, tm->tm_min, nullptr)) + return NULL; + break; + } + case 'n': + case 't': { + size_t n = 0; + while(isspace(input[state->input_index++])) + n++; + if(n == 0) + return NULL; + state->input_index--; + break; + } + case 'p': { + const char *meridian_str = nl_langinfo(AM_STR); + size_t len = strlen(meridian_str); + if (!mlibc::strncasecmp(&input[state->input_index], meridian_str, len)) { + tm->tm_hour %= 12; + state->input_index += len; + break; + } + meridian_str = nl_langinfo(PM_STR); + len = strlen(meridian_str); + if (!mlibc::strncasecmp(&input[state->input_index], meridian_str, len)) { + tm->tm_hour %= 12; + tm->tm_hour += 12; + state->input_index += len; + break; + } + break; + } + case 'r': { //equivalent to `%I:%M:%S %p` + size_t pre_fi = state->format_index; + state->format_index = 0; + + char *result = strptime_internal(input, "%I:%M:%S %p", tm, state); + if(result == NULL) + return NULL; + + state->format_index = pre_fi; + break; + } + case 'R': { //equivalent to `%H:%M` + size_t pre_fi = state->format_index; + state->format_index = 0; + + char *result = strptime_internal(input, "%H:%M", tm, state); + if(result == NULL) + return NULL; + + state->format_index = pre_fi; + break; + } + case 'S': { + if(!matchNumericRange(0, 60, tm->tm_sec, nullptr)) + return NULL; + break; + } + case 'T': { //equivalent to `%H:%M:%S` + size_t pre_fi = state->format_index; + state->format_index = 0; + + char *result = strptime_internal(input, "%H:%M:%S", tm, state); + if(result == NULL) + return NULL; + + state->format_index = pre_fi; + break; + } + case 'U': + __ensure(!"strptime() %U directive unimplemented."); + __builtin_unreachable(); + break; + case 'w': { + int product = 0, n = 0; + sscanf(&input[state->input_index], "%d%n", &product, &n); + if(n == 0 || 1 < n) + return NULL; + state->input_index += n; + tm->tm_wday = product; + state->has_day_of_week = true; + break; + } + case 'W': + __ensure(!"strptime() %W directive unimplemented."); + __builtin_unreachable(); + break; + case 'x': + __ensure(!"strptime() %x directive unimplemented."); + __builtin_unreachable(); + break; + case 'X': + __ensure(!"strptime() %X directive unimplemented."); + __builtin_unreachable(); + break; + case 'y': { + int product = 0, n = 0; + sscanf(&input[state->input_index], "%d%n", &product, &n); + if(n == 0 || 2 < n) + return NULL; + if(product < 69) + product += 100; + state->input_index += n; + tm->tm_year = product; + state->has_year = true; + break; + } + case 'Y': { + int product = 0, n = 0; + sscanf(&input[state->input_index], "%d%n", &product, &n); + if(n == 0 || 4 < n) + return NULL; + state->input_index += n; + tm->tm_year = product - 1900; + state->has_year = true; + state->has_century = true; + state->full_year_given = true; + state->century = product / 100; + break; + } + case 'F': { //GNU extensions + //equivalent to `%Y-%m-%d` + size_t pre_fi = state->format_index; + state->format_index = 0; + + char *result = strptime_internal(input, "%Y-%m-%d", tm, state); + if(result == NULL) + return NULL; + + state->format_index = pre_fi; + break; + } + case 'g': + __ensure(!"strptime() %g directive unimplemented."); + __builtin_unreachable(); + break; + case 'G': + __ensure(!"strptime() %G directive unimplemented."); + __builtin_unreachable(); + break; + case 'u': { + if(!matchNumericRange(1, 7, tm->tm_wday, nullptr)) + return NULL; + tm->tm_wday--; + break; + } + case 'V': + __ensure(!"strptime() %V directive unimplemented."); + __builtin_unreachable(); + break; + case 'z': + __ensure(!"strptime() %z directive unimplemented."); + __builtin_unreachable(); + break; + case 'Z': + __ensure(!"strptime() %Z directive unimplemented."); + __builtin_unreachable(); + break; + case 's': //end of GNU extensions + __ensure(!"strptime() %s directive unimplemented."); + __builtin_unreachable(); + break; + case 'E': { //locale-dependent date & time representation + __ensure(!"strptime() %E* directives unimplemented."); + __builtin_unreachable(); + /* + state->format_index++; + switch(format[state->format_index]){ + case 'c': + break; + case 'C': + break; + case 'x': + break; + case 'X': + break; + case 'y': + break; + case 'Y': + break; + default: + return NULL; + } + */ + } + case 'O': { //locale-dependent numeric symbols + __ensure(!"strptime() %O* directives unimplemented."); + __builtin_unreachable(); + /* + state->format_index++; + switch(format[state->format_index]){ + case 'd': + case 'e': + break; + case 'H': + break; + case 'I': + break; + case 'm': + break; + case 'M': + break; + case 'S': + break; + case 'U': + break; + case 'w': + break; + case 'W': + break; + case 'y': + break; + default: + return NULL; + } + */ + } + default: + return NULL; + } + state->format_index++; + } + + return (char*)input + state->input_index; +} + +} //anonymous namespace + +char *strptime(const char *__restrict s, const char *__restrict format, struct tm *__restrict tm){ + struct strptime_internal_state state = {}; + + char *result = strptime_internal(s, format, tm, &state); + + if(result == NULL) + return NULL; + + if(state.has_century && !state.full_year_given){ + int full_year = state.century * 100; + + if(state.has_year){ + //Compensate for default century-adjustment of `%j` operand + if(tm->tm_year >= 100) + full_year += tm->tm_year - 100; + else + full_year += tm->tm_year; + } + + tm->tm_year = full_year - 1900; + + state.has_year = true; + } + + if(state.has_month && !state.has_day_of_year){ + int day = 0; + if(state.has_year) + day = month_and_year_to_day_in_year(tm->tm_mon, tm->tm_year); + else + day = month_to_day(tm->tm_mon); + + tm->tm_yday = day + tm->tm_mday - 1; + state.has_day_of_year = true; + } + + if(state.has_year && !state.has_day_of_week){ + if(!state.has_month && !state.has_day_of_month){ + tm->tm_wday = day_determination(0, 0, tm->tm_year + 1900); + } + else if(state.has_month && state.has_day_of_month){ + tm->tm_wday = day_determination(tm->tm_mday, tm->tm_mon, tm->tm_year + 1900); + } + state.has_day_of_week = true; + } + + return result; +} diff --git a/options/posix/meson.build b/options/posix/meson.build index d9f55062..91508ea0 100644 --- a/options/posix/meson.build +++ b/options/posix/meson.build @@ -54,6 +54,7 @@ libc_sources += files( 'generic/sys-msg.cpp', 'generic/sys-sem.cpp', 'generic/sys-ipc.cpp', + 'generic/time.cpp', 'generic/wordexp-stubs.cpp', 'musl-generic/fnmatch.cpp', 'musl-generic/glob.cpp', diff --git a/tests/meson.build b/tests/meson.build index 30a01223..7d55014e 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -13,7 +13,6 @@ all_test_cases = [ 'ansi/strrchr', 'ansi/wcsrtombs', 'ansi/wmemcmp', - 'ansi/time', 'ansi/timegm', 'ansi/ungetc', 'ansi/wcsdup', @@ -54,6 +53,7 @@ all_test_cases = [ 'posix/system', # This test should be in the ANSI tests, but it depends on sys/wait.h 'posix/sigsuspend', 'posix/sigaltstack', + 'posix/time', 'posix/realpath', 'posix/ffs', 'posix/getcwd', diff --git a/tests/ansi/time.c b/tests/posix/time.c similarity index 67% rename from tests/ansi/time.c rename to tests/posix/time.c index f98588a7..2ea26292 100644 --- a/tests/ansi/time.c +++ b/tests/posix/time.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -87,4 +88,46 @@ int main() { assert(tm.tm_sec == 53); assert(tm.tm_hour == 14); memset(&tm, 0, sizeof(tm)); + + a = strptime("24", "%H", &tm); + assert(a == NULL); + memset(&tm, 0, sizeof(tm)); + + a = strptime("0", "%I", &tm); + assert(a == NULL); + memset(&tm, 0, sizeof(tm)); + + setlocale(LC_TIME, "en_US.UTF-8"); + a = strptime("10 21 PM", "%I %M %p", &tm); + assert(a != NULL); + assert(*a == '\0'); + assert(tm.tm_hour == 22); + assert(tm.tm_min == 21); + memset(&tm, 0, sizeof(tm)); + + a = strptime("January", "%h", &tm); + assert(a != NULL); + assert(*a == '\0'); + assert(tm.tm_mon == 0); + memset(&tm, 0, sizeof(tm)); + + a = strptime("2", "%j", &tm); + assert(a != NULL); + assert(*a == '\0'); + assert(tm.tm_yday == 1); + memset(&tm, 0, sizeof(tm)); + + a = strptime("Wednesday", "%A", &tm); + assert(a != NULL); + assert(*a == '\0'); + assert(tm.tm_wday == 3); + memset(&tm, 0, sizeof(tm)); + + a = strptime("11:51:13 PM", "%r", &tm); + assert(a != NULL); + assert(*a == '\0'); + assert(tm.tm_hour == 23); + assert(tm.tm_min == 51); + assert(tm.tm_sec == 13); + memset(&tm, 0, sizeof(tm)); }