#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; }