diff --git a/libc/time/strptime.c b/libc/time/strptime.c index c42d3e2a..86200e4a 100644 --- a/libc/time/strptime.c +++ b/libc/time/strptime.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -28,16 +29,15 @@ #define strptime mystrptime #endif -static const char* wdays[] = {"sunday", "monday", "tuesday", "wednesday", - "thursday", "friday", "saturday", NULL}; -static const char* months[] = {"january", "february", "march", "april", "may", - "june", "july", "august", "september", "october", - "november", "december", NULL}; +static const char* wdays[] = {"Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday", NULL}; +static const char* months[] = {"January", "February", "March", "April", "May", + "June", "July", "August", "September", "October", + "November", "December", NULL}; -// TODO: Maximum width. -static const char* strptime_match(const char* str, - int* output, - const char* const* list) +static const char* strptime_str(const char* str, + int* output, + const char* const* list) { for ( int i = 0; list[i]; i++ ) { @@ -50,21 +50,29 @@ static const char* strptime_match(const char* str, return NULL; } -// TODO: Maximum width. -static const char* strptime_digits(const char* str, - int* output, - int minimum, - int maximum, - int offset) +static const char* strptime_num(const char* str, + int* output, + int minimum, + int maximum, + size_t min_digits, + size_t max_digits, + int offset) { - if ( *str < '0'|| '9' < *str ) - return NULL; - // TODO: More exact with number of digits. int value = 0; - while ( '0' <= *str && *str <= '9' ) - if ( __builtin_mul_overflow(value, 10, &value) || - __builtin_add_overflow(value, *str++ - '0', &value) ) + size_t i = 0; + while ( i < max_digits ) + { + if ( str[i] < '0'|| '9' < str[i] ) + { + if ( min_digits <= i ) + break; return NULL; + } + if ( __builtin_mul_overflow(value, 10, &value) || + __builtin_add_overflow(value, str[i++] - '0', &value) ) + return NULL; + } + str += i; if ( value < minimum || maximum < value ) return NULL; if ( __builtin_add_overflow(value, offset, &value) ) @@ -78,6 +86,7 @@ char* strptime(const char* restrict str, { bool pm = false; bool need_mktime = false; + int year_high = -1, year_low = -1; for ( size_t i = 0; format[i]; ) { if ( isspace((unsigned char) format[i]) ) @@ -100,6 +109,7 @@ char* strptime(const char* restrict str, i++; if ( format[i] == '0' || format[i] == '+' ) i++; + bool has_width = '0' <= format[i] && format[i] <= '9'; size_t width = 0; while ( '0' <= format[i] && format[i] <= '9' ) width = width * 10 + (format[i++] - '0'); @@ -113,35 +123,40 @@ char* strptime(const char* restrict str, switch ( format[i] ) { case 'a': - case 'A': str = strptime_match(str, &tm->tm_wday, wdays); break; + case 'A': str = strptime_str(str, &tm->tm_wday, wdays); break; case 'b': case 'B': case 'h': - str = strptime_match(str, &tm->tm_mon, months); + str = strptime_str(str, &tm->tm_mon, months); need_mktime = true; break; - // TODO: %c locale time and date - // TODO: %C century + case 'c': str = strptime(str, "%a %b %e %H:%M:%S %Y", tm); break; + case 'C': str = strptime_num(str, &year_high, 0, 99, 1, 2, 0); break; case 'd': case 'e': - str = strptime_digits(str, &tm->tm_mday, 1, 31, 0); + str = strptime_num(str, &tm->tm_mday, 1, 31, 1, 2, 0); need_mktime = true; break; case 'D': str = strptime(str, "%m/%d/%y", tm); break; - case 'H': str = strptime_digits(str, &tm->tm_hour, 0, 23, 0); break; + case 'H': str = strptime_num(str, &tm->tm_hour, 0, 23, 1, 2, 0); break; case 'I': - str = strptime_digits(str, &tm->tm_hour, 1, 12, 0); + str = strptime_num(str, &tm->tm_hour, 1, 12, 1, 2, 0); if ( tm->tm_hour == 12 ) tm->tm_hour = 0; break; - case 'j': str = strptime_digits(str, &tm->tm_yday, 1, 366, -1); break; + case 'j': str = strptime_num(str, &tm->tm_yday, 1, 366, 3, 3, -1); break; case 'm': - str = strptime_digits(str, &tm->tm_mon, 1, 12, -1); + str = strptime_num(str, &tm->tm_mon, 1, 12, 1, 2, -1); need_mktime = true; break; - case 'M': str = strptime_digits(str, &tm->tm_min, 0, 59, 0); break; - // TODO: %n whitesapce - // TODO: %t whitespace + case 'M': str = strptime_num(str, &tm->tm_min, 0, 59, 1, 2, 0); break; + case 'n': + case 't': + if ( !isspace((unsigned char) *str) ) + return NULL; + do str++; + while ( isspace((unsigned char) *str) ); + break; case 'p': if ( !strncasecmp(str, "am", 2) ) str += 2, pm = false; @@ -152,17 +167,22 @@ char* strptime(const char* restrict str, break; case 'r': str = strptime(str, "%I:%M:%S %p", tm); break; case 'R': str = strptime(str, "%H:%M", tm); break; - case 'S': str = strptime_digits(str, &tm->tm_sec, 0, 60, 0); break; + case 'S': str = strptime_num(str, &tm->tm_sec, 0, 60, 1, 2, 0); break; case 'T': str = strptime(str, "%H:%M:%S", tm); break; // TODO: %U week number - case 'w': str = strptime_digits(str, &tm->tm_wday, 0, 6, 0); break; + case 'w': str = strptime_num(str, &tm->tm_wday, 0, 6, 1, 1, 0); break; // TODO: %W week number case 'x': str = strptime(str, "%m/%d/%Y", tm); break; case 'X': str = strptime(str, "%H:%M:%S", tm); break; - // TODO: %y non-century year + case 'y': str = strptime_num(str, &year_low, 0, 99, 1, 2, 0); break; case 'Y': - // TODO: Minimum yield width. - str = strptime_digits(str, &tm->tm_year, INT_MIN, INT_MAX, -1900); + // POSIX divergence: Avoid year 10k problem by allowing more than + // four characters by default. + if ( !has_width ) + width = SIZE_MAX; + // TODO: Allow leading + and - per POSIX. + str = strptime_num(str, &tm->tm_year, INT_MIN, INT_MAX, 1, width, + -1900); need_mktime = true; break; case 'z': @@ -170,8 +190,8 @@ char* strptime(const char* restrict str, if ( *str != '-' || *str != '+' ) return NULL; int hours, minutes; - if ( !(str = strptime_digits(str, &hours, -12, 12, 0)) || - !(str = strptime_digits(str, &minutes, 0, 59, 0)) ) + if ( !(str = strptime_num(str, &hours, -12, 12, 2, 2, 0)) || + !(str = strptime_num(str, &minutes, 0, 59, 2, 2, 0)) ) return NULL; tm->tm_isdst = 0; // TODO: What is done with this timezone information? @@ -198,8 +218,22 @@ char* strptime(const char* restrict str, { if ( pm ) tm->tm_hour += 12; + if ( year_high != -1 || year_low != -1 ) + { + if ( year_high == -1 ) + year_high = year_low < 70 ? 20 : 19; + else if ( year_low == -1 ) + year_low = 0; + tm->tm_year = year_high * 100 + year_low - 1900; + need_mktime = true; + } if ( need_mktime ) - mktime(tm); + { + struct tm copy = *tm; + mktime(©); + tm->tm_wday = copy.tm_wday; + tm->tm_yday = copy.tm_yday; + } } return (char*) str; }