From 628404b87c77692daffec9923315dad6ca217381 Mon Sep 17 00:00:00 2001 From: Howard Hinnant Date: Mon, 26 Dec 2016 16:27:56 -0500 Subject: [PATCH] Re-implement parse. * Work with const CharT* format at the lowest level. * Avoid dependence on std::lib except for locale-sensitive parsing. * Add tests for parse. --- date.h | 1676 +++++++++++++++++++++++++-------- test/date_test/parse.pass.cpp | 730 ++++++++++++++ 2 files changed, 2039 insertions(+), 367 deletions(-) create mode 100644 test/date_test/parse.pass.cpp diff --git a/date.h b/date.h index 45d13c7..66037fb 100644 --- a/date.h +++ b/date.h @@ -3489,6 +3489,7 @@ class decimal_format_seconds static CONSTDATA unsigned w = 0; public: using precision = std::chrono::seconds; + static auto CONSTDATA width = make_precision::width; private: std::chrono::seconds s_; @@ -4916,10 +4917,238 @@ format(const std::basic_string& fmt, const sys_time& tp namespace detail { +template +bool +read_char(std::basic_istream& is, CharT fmt, std::ios::iostate& err) +{ + auto ic = is.get(); + if (Traits::eq_int_type(ic, Traits::eof()) || + !Traits::eq(Traits::to_char_type(ic), fmt)) + { + err |= std::ios::failbit; + is.setstate(std::ios::failbit); + return false; + } + return true; +} + +template +unsigned +read_unsigned(std::basic_istream& is, unsigned m = 1, unsigned M = 10) +{ + unsigned x = 0; + unsigned count = 0; + while (true) + { + auto ic = is.peek(); + if (Traits::eq_int_type(ic, Traits::eof())) + break; + auto c = static_cast(Traits::to_char_type(ic)); + if (!('0' <= c && c <= '9')) + break; + (void)is.get(); + ++count; + x = 10*x + (c - '0'); + if (count == M) + break; + } + if (count < m) + is.setstate(std::ios::failbit); + return x; +} + +template +int +read_signed(std::basic_istream& is, unsigned m = 1, unsigned M = 10) +{ + auto ic = is.peek(); + if (!Traits::eq_int_type(ic, Traits::eof())) + { + auto c = static_cast(Traits::to_char_type(ic)); + if (('0' <= c && c <= '9') || c == '-' || c == '+') + { + if (c == '-' || c == '+') + (void)is.get(); + auto x = static_cast(read_unsigned(is, m, M)); + if (!is.fail()) + { + if (c == '-') + x = -x; + return x; + } + } + } + is.setstate(std::ios::failbit); + return 0; +} + +template +long double +read_long_double(std::basic_istream& is, unsigned m = 1, unsigned M = 10) +{ + using namespace std; + unsigned count = 0; + auto decimal_point = Traits::to_int_type( + use_facet>(is.getloc()).decimal_point()); + string buf; + while (true) + { + auto ic = is.peek(); + if (Traits::eq_int_type(ic, Traits::eof())) + break; + if (Traits::eq_int_type(ic, decimal_point)) + { + buf += '.'; + decimal_point = Traits::eof(); + is.get(); + } + else + { + auto c = static_cast(Traits::to_char_type(ic)); + if (!('0' <= c && c <= '9')) + break; + buf += c; + (void)is.get(); + ++count; + } + if (count == M) + break; + } + if (count < m) + is.setstate(std::ios::failbit); + return std::stold(buf); +} + +struct rs +{ + int& i; + unsigned m; + unsigned M; +}; + +struct ru +{ + int& i; + unsigned m; + unsigned M; +}; + +struct rld +{ + long double& i; + unsigned m; + unsigned M; +}; + +template +void +read(std::basic_istream&) +{ +} + +template +void +read(std::basic_istream& is, CharT a0, Args&& ...args); + +template +void +read(std::basic_istream& is, rs a0, Args&& ...args); + +template +void +read(std::basic_istream& is, ru a0, Args&& ...args); + +template +void +read(std::basic_istream& is, int a0, Args&& ...args); + +template +void +read(std::basic_istream& is, rld a0, Args&& ...args); + +template +void +read(std::basic_istream& is, CharT a0, Args&& ...args) +{ + if (a0 != CharT{}) + { + auto ic = is.peek(); + if (Traits::eq_int_type(ic, Traits::eof())) + return; + if (!Traits::eq(Traits::to_char_type(ic), a0)) + { + is.setstate(std::ios::failbit); + return; + } + (void)is.get(); + } + else + { + while (isspace(is.peek())) + (void)is.get(); + } + read(is, std::forward(args)...); +} + +template +void +read(std::basic_istream& is, rs a0, Args&& ...args) +{ + auto x = read_signed(is, a0.m, a0.M); + if (is.fail()) + return; + a0.i = x; + read(is, std::forward(args)...); +} + +template +void +read(std::basic_istream& is, ru a0, Args&& ...args) +{ + auto x = read_unsigned(is, a0.m, a0.M); + if (is.fail()) + return; + a0.i = static_cast(x); + read(is, std::forward(args)...); +} + +template +void +read(std::basic_istream& is, int a0, Args&& ...args) +{ + if (a0 != -1) + { + auto u = static_cast(a0); + CharT buf[std::numeric_limits::digits10+2] = {}; + auto e = buf; + do + { + *e++ = CharT(u % 10) + CharT{'0'}; + u /= 10; + } while (u > 0); + std::reverse(buf, e); + for (auto p = buf; p != e && is.rdstate() == std::ios::goodbit; ++p) + read(is, *p); + } + if (is.rdstate() == std::ios::goodbit) + read(is, std::forward(args)...); +} + +template +void +read(std::basic_istream& is, rld a0, Args&& ...args) +{ + auto x = read_long_double(is, a0.m, a0.M); + if (is.fail()) + return; + a0.i = x; + read(is, std::forward(args)...); +} + template void parse(std::basic_istream& is, - const std::basic_string& format, local_time& tp, + const CharT* fmt, local_time& tp, std::basic_string* abbrev = nullptr, std::chrono::minutes* offset = nullptr) { @@ -4929,208 +5158,904 @@ parse(std::basic_istream& is, if (ok) { auto& f = use_facet>(is.getloc()); - ios_base::iostate err = ios_base::goodbit; std::tm tm{}; - Duration subseconds{}; std::basic_string temp_abbrev; minutes temp_offset{}; - - auto b = format.data(); - auto i = b; - auto e = b + format.size(); - auto command = false; - auto modified = false; - for (; i < e && 0 == (err & ios_base::failbit); ++i) + const CharT* command = nullptr; + auto modified = CharT{}; + auto width = -1; + constexpr int not_a_year = 33000; + int Y = not_a_year; + constexpr int not_a_century = not_a_year / 100; + int C = not_a_century; + constexpr int not_a_2digit_year = 100; + int y = not_a_2digit_year; + int m{}; + int d{}; + int j{}; + constexpr int not_a_weekday = 7; + int wd = not_a_weekday; + constexpr int not_a_hour_12_value = 0; + int I = not_a_hour_12_value; + hours h{}; + minutes min{}; + Duration s{}; + int g = not_a_2digit_year; + int G = not_a_year; + constexpr int not_a_week_num = 100; + int V = not_a_week_num; + int U = not_a_week_num; + int W = not_a_week_num; + using detail::read; + using detail::rs; + using detail::ru; + using detail::rld; + for (; *fmt && is.rdstate() == std::ios::goodbit; ++fmt) { - switch (*i) + if (isspace(*fmt)) { - case '%': - command = true; - modified = false; - break; - case 'F': - if (command && !modified) + // space matches 0 or more white space characters + ws(is); + continue; + } + switch (*fmt) + { + case 'a': + case 'A': + if (command) { - f.get(is, 0, is, err, &tm, b, i-1); - b = i+1; - if ((err & ios_base::failbit) == 0) - { - const CharT ymd[] = {'%', 'Y', '-', '%', 'm', '-', '%', 'd'}; - f.get(is, 0, is, err, &tm, ymd, ymd+8); - } + ios_base::iostate err = ios_base::goodbit; + f.get(is, 0, is, err, &tm, command, fmt+1); + command = nullptr; + width = -1; + modified = CharT{}; + if ((err & ios::failbit) == 0) + wd = tm.tm_wday; + is.setstate(err); } - command = false; - modified = false; + else + read(is, *fmt); break; - case 'O': - case 'E': - modified = true; - break; - case 'T': - case 'S': - if (command && !modified) + case 'b': + case 'B': + case 'h': + if (command) { - f.get(is, 0, is, err, &tm, b, i-1); - if (err & ios_base::failbit) + ios_base::iostate err = ios_base::goodbit; + f.get(is, 0, is, err, &tm, command, fmt+1); + command = nullptr; + width = -1; + modified = CharT{}; + if ((err & ios::failbit) == 0) + m = tm.tm_mon + 1; + is.setstate(err); + } + else + read(is, *fmt); + break; + case 'c': + if (command) + { + ios_base::iostate err = ios_base::goodbit; + f.get(is, 0, is, err, &tm, command, fmt+1); + command = nullptr; + width = -1; + modified = CharT{}; + if ((err & ios::failbit) == 0) { - command = modified = false; - break; // break the switch/case + Y = tm.tm_year + 1900; + m = tm.tm_mon + 1; + d = tm.tm_mday; + h = hours{tm.tm_hour}; + min = minutes{tm.tm_min}; + s = duration_cast(seconds{tm.tm_sec}); } - b = i+1; - if (*i == 'T') + is.setstate(err); + } + else + read(is, *fmt); + break; + case 'x': + if (command) + { + ios_base::iostate err = ios_base::goodbit; + f.get(is, 0, is, err, &tm, command, fmt+1); + command = nullptr; + width = -1; + modified = CharT{}; + if ((err & ios::failbit) == 0) { - const CharT hm[] = {'%', 'H', ':', '%', 'M', ':'}; - f.get(is, 0, is, err, &tm, hm, hm+6); - if (err & ios_base::failbit) - { - command = modified = false; - break; // break the switch/case - } + Y = tm.tm_year + 1900; + m = tm.tm_mon + 1; + d = tm.tm_mday; } - if (ratio_less>::value) + is.setstate(err); + } + else + read(is, *fmt); + break; + case 'X': + if (command) + { + ios_base::iostate err = ios_base::goodbit; + f.get(is, 0, is, err, &tm, command, fmt+1); + command = nullptr; + width = -1; + modified = CharT{}; + if ((err & ios::failbit) == 0) { - auto decimal_point = Traits::to_int_type( - use_facet>(is.getloc()).decimal_point()); - string buf; - while (true) - { - auto k = is.peek(); - if (Traits::eq_int_type(k, Traits::eof())) - break; - if (Traits::eq_int_type(k, decimal_point)) - { - buf += '.'; - decimal_point = Traits::eof(); - is.get(); - } - else - { - auto c = static_cast(Traits::to_char_type(k)); - if (isdigit(c)) - { - buf += c; - is.get(); - } - else - { - break; - } - } - }; - if (!buf.empty()) - subseconds = round(duration{stod(buf)}); - else - err |= ios_base::failbit; + h = hours{tm.tm_hour}; + min = minutes{tm.tm_min}; + s = duration_cast(seconds{tm.tm_sec}); + } + is.setstate(err); + } + else + read(is, *fmt); + break; + case 'C': + if (command) + { + if (modified == CharT{}) + { + read(is, rs{C, 1, width == -1 ? 2u : width}); } else { - const CharT hm[] = {'%', 'S'}; - f.get(is, 0, is, err, &tm, hm, hm+2); + ios_base::iostate err = ios_base::goodbit; + f.get(is, 0, is, err, &tm, command, fmt+1); + if ((err & ios::failbit) == 0) + { + auto tY = tm.tm_year + 1900; + C = (tY >= 0 ? tY : tY-99) / 100; + } + is.setstate(err); + } + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'D': + if (command) + { + if (modified == CharT{}) + read(is, ru{m, 1, 2}, CharT{'\0'}, CharT{'/'}, CharT{'\0'}, + ru{d, 1, 2}, CharT{'\0'}, CharT{'/'}, CharT{'\0'}, + rs{y, 1, 2}); + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'F': + if (command) + { + if (modified == CharT{}) + read(is, rs{Y, 1, width == -1 ? 4u : width}, CharT{'-'}, + ru{m, 1, 2}, CharT{'-'}, ru{d, 1, 2}); + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'd': + case 'e': + if (command) + { + if (modified == CharT{}) + read(is, rs{d, 1, width == -1 ? 2u : width}); + else if (modified == CharT{'O'}) + { + ios_base::iostate err = ios_base::goodbit; + f.get(is, 0, is, err, &tm, command, fmt+1); + command = nullptr; + width = -1; + modified = CharT{}; + if ((err & ios::failbit) == 0) + d = tm.tm_mday; + is.setstate(err); + } + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'H': + if (command) + { + if (modified == CharT{}) + { + int H; + read(is, ru{H, 1, width == -1 ? 2u : width}); + if (!is.fail()) + h = hours{H}; + } + else if (modified == CharT{'O'}) + { + ios_base::iostate err = ios_base::goodbit; + f.get(is, 0, is, err, &tm, command, fmt+1); + if ((err & ios::failbit) == 0) + h = hours{tm.tm_hour}; + is.setstate(err); + } + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'I': + if (command) + { + if (modified == CharT{}) + { + // reads in an hour into I, but most be in [1, 12] + read(is, rs{I, 1, width == -1 ? 2u : width}); + if (I != not_a_hour_12_value) + { + if (!(1 <= I && I <= 12)) + { + I = not_a_hour_12_value; + goto broken; + } + } + } + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'j': + if (command) + { + if (modified == CharT{}) + read(is, ru{j, 1, width == -1 ? 3u : width}); + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'M': + if (command) + { + if (modified == CharT{}) + { + int M; + read(is, ru{M, 1, width == -1 ? 2u : width}); + if (!is.fail()) + min = minutes{M}; + } + else if (modified == CharT{'O'}) + { + ios_base::iostate err = ios_base::goodbit; + f.get(is, 0, is, err, &tm, command, fmt+1); + if ((err & ios::failbit) == 0) + min = minutes{tm.tm_min}; + is.setstate(err); + } + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'm': + if (command) + { + if (modified == CharT{}) + read(is, rs{m, 1, width == -1 ? 2u : width}); + else if (modified == CharT{'O'}) + { + ios_base::iostate err = ios_base::goodbit; + f.get(is, 0, is, err, &tm, command, fmt+1); + command = nullptr; + width = -1; + modified = CharT{}; + if ((err & ios::failbit) == 0) + m = tm.tm_mon + 1; + is.setstate(err); + } + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'n': + case 't': + if (command) + { + // %n and %t match 1 or more white space characters + // consecutive %n and %t count as one + auto ic = is.peek(); + if (Traits::eq_int_type(ic, Traits::eof())) + break; + if (!isspace(ic)) + { + is.setstate(ios::failbit); + break; + } + ws(is); + for (++fmt; *fmt == 'n' || *fmt == 't'; ++fmt) + ; + --fmt; + } + else + read(is, *fmt); + break; + case 'p': + // Error if haven't yet seen %I + if (command) + { + if (modified == CharT{}) + { + if (I == not_a_hour_12_value) + goto broken; + tm.tm_hour = I; + ios_base::iostate err = ios_base::goodbit; + f.get(is, 0, is, err, &tm, command, fmt+1); + if (!(err & ios::failbit)) + { + h = hours{tm.tm_hour}; + I = not_a_hour_12_value; + } + } + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + + break; + case 'r': + if (command) + { + ios_base::iostate err = ios_base::goodbit; + f.get(is, 0, is, err, &tm, command, fmt+1); + command = nullptr; + width = -1; + modified = CharT{}; + if ((err & ios::failbit) == 0) + { + h = hours{tm.tm_hour}; + min = minutes{tm.tm_min}; + s = duration_cast(seconds{tm.tm_sec}); + } + is.setstate(err); + } + else + read(is, *fmt); + break; + case 'R': + if (command) + { + if (modified == CharT{}) + { + int H, M; + read(is, ru{H, 1, 2}, CharT{'\0'}, CharT{':'}, CharT{'\0'}, + ru{M, 1, 2}, CharT{'\0'}); + if (!is.fail()) + { + h = hours{H}; + min = minutes{M}; + } + } + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'S': + if (command) + { + if (modified == CharT{}) + { + using dfs = detail::decimal_format_seconds; + CONSTDATA auto w = Duration::period::den == 1 ? 2 : 3 + dfs::width; + long double S; + read(is, rld{S, 1, width == -1 ? w : width}); + if (!is.fail()) + s = round(duration{S}); + } + else if (modified == CharT{'O'}) + { + ios_base::iostate err = ios_base::goodbit; + f.get(is, 0, is, err, &tm, command, fmt+1); + if ((err & ios::failbit) == 0) + s = duration_cast(seconds{tm.tm_sec}); + is.setstate(err); + } + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'T': + if (command) + { + if (modified == CharT{}) + { + using dfs = detail::decimal_format_seconds; + CONSTDATA auto w = Duration::period::den == 1 ? 2 : 3 + dfs::width; + int H; + int M; + long double S; + read(is, ru{H, 1, 2}, CharT{':'}, ru{M, 1, 2}, + CharT{':'}, rld{S, 1, w}); + if (!is.fail()) + { + h = hours{H}; + min = minutes{M}; + s = round(duration{S}); + } + } + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'Y': + if (command) + { + if (modified == CharT{}) + read(is, rs{Y, 1, width == -1 ? 4u : width}); + else if (modified == CharT{'E'}) + { + ios_base::iostate err = ios_base::goodbit; + f.get(is, 0, is, err, &tm, command, fmt+1); + command = nullptr; + width = -1; + modified = CharT{}; + if ((err & ios::failbit) == 0) + Y = tm.tm_year + 1900; + is.setstate(err); + } + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'y': + if (command) + { + if (modified == CharT{}) + read(is, ru{y, 1, width == -1 ? 2u : width}); + else + { + ios_base::iostate err = ios_base::goodbit; + f.get(is, 0, is, err, &tm, command, fmt+1); + if ((err & ios::failbit) == 0) + Y = tm.tm_year + 1900; + is.setstate(err); + } + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'g': + if (command) + { + if (modified == CharT{}) + read(is, ru{g, 1, width == -1 ? 2u : width}); + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'G': + if (command) + { + if (modified == CharT{}) + read(is, rs{G, 1, width == -1 ? 4u : width}); + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'U': + if (command) + { + if (modified == CharT{}) + read(is, ru{U, 1, width == -1 ? 2u : width}); + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'V': + if (command) + { + if (modified == CharT{}) + read(is, ru{V, 1, width == -1 ? 2u : width}); + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'W': + if (command) + { + if (modified == CharT{}) + read(is, ru{W, 1, width == -1 ? 2u : width}); + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'u': + case 'w': + if (command) + { + if (modified == CharT{}) + { + read(is, ru{wd, 1, width == -1 ? 1u : width}); + if (!is.fail() && *fmt == 'u') + { + if (wd == 7) + wd = 0; + } + } + else if (modified == CharT{'O'}) + { + ios_base::iostate err = ios_base::goodbit; + f.get(is, 0, is, err, &tm, command, fmt+1); + if ((err & ios::failbit) == 0) + wd = tm.tm_wday; + is.setstate(err); + } + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'E': + case 'O': + if (command) + { + if (modified == CharT{}) + { + modified = *fmt; + } + else + { + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; } } - command = false; - modified = false; + else + read(is, *fmt); + break; + case '%': + if (command) + { + if (modified == CharT{}) + read(is, *fmt); + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + command = fmt; break; case 'z': if (command) { - f.get(is, 0, is, err, &tm, b, i-1-modified); - b = i+1; - if ((err & ios_base::failbit) == 0) - { - CharT sign{}; - is >> sign; - if (!is.fail() && (sign == '+' || sign == '-')) - { - char h1, h0, m1, m0; - char colon = ':'; - h1 = static_cast(is.get()); - h0 = static_cast(is.get()); - if (modified) - { - if (h0 == ':') - { - colon = h0; - h0 = h1; - h1 = '0'; - } - else - colon = static_cast(is.get()); - } - m1 = static_cast(is.get()); - m0 = static_cast(is.get()); - if (!is.fail() && std::isdigit(h1) && std::isdigit(h0) - && std::isdigit(m1) && std::isdigit(m0) - && colon == ':') - { - temp_offset = 10*hours{h1 - '0'} + hours{h0 - '0'} + - 10*minutes{m1 - '0'} + minutes{m0 - '0'}; - if (sign == '-') - temp_offset = -temp_offset; - } - else - err |= ios_base::failbit; - } - else - err |= ios_base::failbit; - } + int H, M; + if (modified == CharT{}) + read(is, rs{H, 2, 2}, ru{M, 2, 2}); + else + read(is, rs{H, 2, 2}, CharT{':'}, ru{M, 2, 2}); + if (!is.fail()) + temp_offset = hours{H} + minutes{M}; + command = nullptr; + width = -1; + modified = CharT{}; } - command = false; - modified = false; + else + read(is, *fmt); break; case 'Z': - if (command && !modified) + if (command) { - f.get(is, 0, is, err, &tm, b, i-1); - b = i+1; - if ((err & ios_base::failbit) == 0) - { + if (modified == CharT{}) is >> temp_abbrev; - if (is.fail()) - err |= ios_base::failbit; - } + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; } - command = false; - modified = false; + else + read(is, *fmt); break; default: - command = false; - modified = false; + if (command) + { + if (width == -1 && modified == CharT{} && '0' <= *fmt && *fmt <= '9') + { + width = static_cast(*fmt) - '0'; + while ('0' <= fmt[1] && fmt[1] <= '9') + width = 10*width + static_cast(*++fmt) - '0'; + } + else + { + if (modified == CharT{}) + read(is, CharT{'%'}, width, *fmt); + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + } + else + read(is, *fmt); break; } } - if ((err & ios_base::failbit) == 0) + // is.rdstate() != ios::goodbit || *fmt == CharT{} + if (is.rdstate() == ios::goodbit && command) { - if (b < e) - f.get(is, 0, is, err, &tm, b, e); - if ((err & ios_base::failbit) == 0) - { - using namespace std::chrono; - tp = floor(local_days(year{tm.tm_year + 1900}/ - (tm.tm_mon+1)/ - (tm.tm_mday)) + - subseconds + seconds{tm.tm_sec} + - minutes{tm.tm_min} + hours{tm.tm_hour}); - if (abbrev != nullptr) - *abbrev = std::move(temp_abbrev); - if (offset != nullptr) - *offset = temp_offset; - } + if (modified == CharT{}) + read(is, CharT{'%'}, width); + else + read(is, CharT{'%'}, width, modified); } - is.setstate(err); + if (!is.fail()) + { + if (y != not_a_2digit_year) + { + if (!(0 <= y && y <= 99)) + goto broken; + if (C == not_a_century) + { + if (Y == not_a_year) + { + if (y >= 69) + C = 19; + else + C = 20; + } + else + { + C = (Y >= 0 ? Y : Y-100) / 100; + } + } + int tY; + if (C >= 0) + tY = 100*C + y; + else + tY = 100*(C+1) - (y == 0 ? 100 : y); + if (Y != not_a_year && Y != tY) + goto broken; + Y = tY; + } + if (g != not_a_2digit_year) + { + if (!(0 <= g && g <= 99)) + goto broken; + if (C == not_a_century) + { + if (G == not_a_year) + { + if (g >= 69) + C = 19; + else + C = 20; + } + else + { + C = (G >= 0 ? G : G-100) / 100; + } + } + int tG; + if (C >= 0) + tG = 100*C + g; + else + tG = 100*(C+1) - (g == 0 ? 100 : g); + if (G != not_a_year && G != tG) + goto broken; + G = tG; + } + if (G != not_a_year) + { + if (V == not_a_week_num || wd == not_a_weekday) + goto broken; + auto ymd = year_month_day{local_days{year{G-1}/dec/thu[last]} + + (mon-thu) + weeks{V-1} + + (weekday{static_cast(wd)}-mon)}; + if (Y == not_a_year) + Y = static_cast(ymd.year()); + else if (year{Y} != ymd.year()) + goto broken; + if (m == 0) + m = static_cast(static_cast(ymd.month())); + else if (month(m) != ymd.month()) + goto broken; + if (d == 0) + d = static_cast(static_cast(ymd.day())); + else if (day(d) != ymd.day()) + goto broken; + } + if (Y != not_a_year) + { + if (!(static_cast(year::min()) <= Y && + Y <= static_cast(year::max()))) + goto broken; + if (j != 0) + { + auto ymd = year_month_day{local_days{year{Y}/1/1} + days{j-1}}; + if (m == 0) + m = static_cast(static_cast(ymd.month())); + else if (month(m) != ymd.month()) + goto broken; + if (d == 0) + d = static_cast(static_cast(ymd.day())); + else if (day(d) != ymd.day()) + goto broken; + } + if (U != not_a_week_num) + { + if (wd == not_a_weekday) + goto broken; + sys_days sd; + if (U == 0) + sd = year{Y-1}/dec/weekday{static_cast(wd)}[last]; + else + sd = sys_days{year{Y}/jan/sun[1]} + weeks{U-1} + + (weekday{static_cast(wd)} - sun); + year_month_day ymd = sd; + if (year{Y} != ymd.year()) + goto broken; + if (m == 0) + m = static_cast(static_cast(ymd.month())); + else if (month(m) != ymd.month()) + goto broken; + if (d == 0) + d = static_cast(static_cast(ymd.day())); + else if (day(d) != ymd.day()) + goto broken; + } + if (W != not_a_week_num) + { + if (wd == not_a_weekday) + goto broken; + sys_days sd; + if (W == 0) + sd = year{Y-1}/dec/weekday{static_cast(wd)}[last]; + else + sd = sys_days{year{Y}/jan/mon[1]} + weeks{W-1} + + (weekday{static_cast(wd)} - mon); + year_month_day ymd = sd; + if (year{Y} != ymd.year()) + goto broken; + if (m == 0) + m = static_cast(static_cast(ymd.month())); + else if (month(m) != ymd.month()) + goto broken; + if (d == 0) + d = static_cast(static_cast(ymd.day())); + else if (day(d) != ymd.day()) + goto broken; + } + if (m != 0 && d != 0) + { + auto ymd = year{Y}/m/d; + if (!ymd.ok()) + goto broken; + auto ld = local_days{ymd}; + if (wd != not_a_weekday && + weekday{static_cast(wd)} != weekday{ld}) + goto broken; + tp = local_time{floor(ld + h + min + s)}; + } + else + goto broken; + } + else // did not parse a year + { + goto broken; + } + if (abbrev != nullptr) + *abbrev = std::move(temp_abbrev); + if (offset != nullptr) + *offset = temp_offset; + } + return; } - else - is.setstate(ios_base::failbit); +broken: + is.setstate(ios_base::failbit); } template inline void -parse(std::basic_istream& is, - const std::basic_string& format, local_time& tp, +parse(std::basic_istream& is, const CharT* fmt, local_time& tp, std::chrono::minutes* offset) { - parse(is, format, tp, static_cast*>(nullptr), offset); + parse(is, fmt, tp, static_cast*>(nullptr), offset); } template > @@ -5158,7 +6083,7 @@ std::basic_istream& operator>>(std::basic_istream& is, const parse_local_manip& x) { - parse(is, x.format_, x.tp_, x.abbrev_, x.offset_); + parse(is, x.format_.c_str(), x.tp_, x.abbrev_, x.offset_); return is; } @@ -5190,7 +6115,7 @@ operator>>(std::basic_istream& is, std::chrono::minutes offset{}; auto offptr = x.offset_ ? x.offset_ : &offset; local_time lt; - parse(is, x.format_, lt, x.abbrev_, offptr); + parse(is, x.format_.c_str(), lt, x.abbrev_, offptr); if (!is.fail()) x.tp_ = sys_time{floor(lt - *offptr).time_since_epoch()}; return is; @@ -5198,19 +6123,6 @@ operator>>(std::basic_istream& is, } // namespace detail -template -inline -void -parse(std::basic_istream& is, - const std::basic_string& format, sys_time& tp) -{ - std::chrono::minutes offset{}; - local_time lt; - detail::parse(is, format, lt, &offset); - if (!is.fail()) - tp = sys_time{floor(lt - offset).time_since_epoch()}; -} - template inline detail::parse_sys_manip @@ -5219,20 +6131,6 @@ parse(const std::basic_string& format, sys_time& tp) return {format, tp}; } -template -inline -void -parse(std::basic_istream& is, - const std::basic_string& format, sys_time& tp, - std::basic_string& abbrev) -{ - std::chrono::minutes offset{}; - local_time lt; - detail::parse(is, format, lt, &abbrev, &offset); - if (!is.fail()) - tp = sys_time{floor(lt - offset).time_since_epoch()}; -} - template inline detail::parse_sys_manip @@ -5242,19 +6140,6 @@ parse(const std::basic_string& format, sys_time& tp, return {format, tp, &abbrev}; } -template -inline -void -parse(std::basic_istream& is, - const std::basic_string& format, sys_time& tp, - std::chrono::minutes& offset) -{ - local_time lt; - detail::parse(is, format, lt, &offset); - if (!is.fail()) - tp = sys_time{floor(lt - offset).time_since_epoch()}; -} - template inline detail::parse_sys_manip @@ -5264,19 +6149,6 @@ parse(const std::basic_string& format, sys_time& tp, return {format, tp, nullptr, &offset}; } -template -inline -void -parse(std::basic_istream& is, - const std::basic_string& format, sys_time& tp, - std::basic_string& abbrev, std::chrono::minutes& offset) -{ - local_time lt; - detail::parse(is, format, lt, &abbrev, &offset); - if (!is.fail()) - tp = sys_time{floor(lt - offset).time_since_epoch()}; -} - template inline detail::parse_sys_manip @@ -5286,19 +6158,6 @@ parse(const std::basic_string& format, sys_time& tp, return {format, tp, &abbrev, &offset}; } -template -inline -void -parse(std::basic_istream& is, - const std::basic_string& format, sys_time& tp, - std::chrono::minutes& offset, std::basic_string& abbrev) -{ - local_time lt; - detail::parse(is, format, lt, &abbrev, &offset); - if (!is.fail()) - tp = sys_time{floor(lt - offset).time_since_epoch()}; -} - template inline detail::parse_sys_manip @@ -5308,15 +6167,6 @@ parse(const std::basic_string& format, sys_time& tp, return {format, tp, &abbrev, &offset}; } -template -inline -void -parse(std::basic_istream& is, - const std::basic_string& format, local_time& tp) -{ - detail::parse(is, format, tp); -} - template inline detail::parse_local_manip @@ -5325,16 +6175,6 @@ parse(const std::basic_string& format, local_time& tp) return {format, tp}; } -template -inline -void -parse(std::basic_istream& is, - const std::basic_string& format, local_time& tp, - std::basic_string& abbrev) -{ - detail::parse(is, format, tp, &abbrev); -} - template inline detail::parse_local_manip @@ -5344,16 +6184,6 @@ parse(const std::basic_string& format, local_time& tp, return {format, tp, &abbrev}; } -template -inline -void -parse(std::basic_istream& is, - const std::basic_string& format, local_time& tp, - std::chrono::minutes& offset) -{ - detail::parse(is, format, tp, &offset); -} - template inline detail::parse_local_manip @@ -5363,16 +6193,6 @@ parse(const std::basic_string& format, local_time& tp, return {format, tp, nullptr, &offset}; } -template -inline -void -parse(std::basic_istream& is, - const std::basic_string& format, local_time& tp, - std::basic_string& abbrev, std::chrono::minutes& offset) -{ - detail::parse(is, format, tp, &abbrev, &offset); -} - template inline detail::parse_local_manip @@ -5382,16 +6202,6 @@ parse(const std::basic_string& format, local_time& tp, return {format, tp, &abbrev, &offset}; } -template -inline -void -parse(std::basic_istream& is, - const std::basic_string& format, local_time& tp, - std::chrono::minutes& offset, std::basic_string& abbrev) -{ - detail::parse(is, format, tp, &abbrev, &offset); -} - template inline detail::parse_local_manip @@ -5401,16 +6211,123 @@ parse(const std::basic_string& format, local_time& tp, return {format, tp, &abbrev, &offset}; } +template +inline +void +parse(std::basic_istream& is, + const std::basic_string& format, sys_time& tp) +{ + std::chrono::minutes offset{}; + local_time lt; + detail::parse(is, format.c_str(), lt, &offset); + if (!is.fail()) + tp = sys_time{floor(lt - offset).time_since_epoch()}; +} + +template +inline +void +parse(std::basic_istream& is, + const std::basic_string& format, sys_time& tp, + std::basic_string& abbrev) +{ + std::chrono::minutes offset{}; + local_time lt; + detail::parse(is, format.c_str(), lt, &abbrev, &offset); + if (!is.fail()) + tp = sys_time{floor(lt - offset).time_since_epoch()}; +} + +template +inline +void +parse(std::basic_istream& is, + const std::basic_string& format, sys_time& tp, + std::chrono::minutes& offset) +{ + local_time lt; + detail::parse(is, format.c_str(), lt, &offset); + if (!is.fail()) + tp = sys_time{floor(lt - offset).time_since_epoch()}; +} + +template +inline +void +parse(std::basic_istream& is, + const std::basic_string& format, sys_time& tp, + std::basic_string& abbrev, std::chrono::minutes& offset) +{ + local_time lt; + detail::parse(is, format.c_str(), lt, &abbrev, &offset); + if (!is.fail()) + tp = sys_time{floor(lt - offset).time_since_epoch()}; +} + +template +inline +void +parse(std::basic_istream& is, + const std::basic_string& format, sys_time& tp, + std::chrono::minutes& offset, std::basic_string& abbrev) +{ + local_time lt; + detail::parse(is, format.c_str(), lt, &abbrev, &offset); + if (!is.fail()) + tp = sys_time{floor(lt - offset).time_since_epoch()}; +} + +template +inline +void +parse(std::basic_istream& is, + const std::basic_string& format, local_time& tp) +{ + detail::parse(is, format.c_str(), tp); +} + +template +inline +void +parse(std::basic_istream& is, + const std::basic_string& format, local_time& tp, + std::basic_string& abbrev) +{ + detail::parse(is, format.c_str(), tp, &abbrev); +} + +template +inline +void +parse(std::basic_istream& is, + const std::basic_string& format, local_time& tp, + std::chrono::minutes& offset) +{ + detail::parse(is, format.c_str(), tp, &offset); +} + +template +inline +void +parse(std::basic_istream& is, + const std::basic_string& format, local_time& tp, + std::basic_string& abbrev, std::chrono::minutes& offset) +{ + detail::parse(is, format.c_str(), tp, &abbrev, &offset); +} + +template +inline +void +parse(std::basic_istream& is, + const std::basic_string& format, local_time& tp, + std::chrono::minutes& offset, std::basic_string& abbrev) +{ + detail::parse(is, format.c_str(), tp, &abbrev, &offset); +} + // const CharT* formats -template -inline -void -parse(std::basic_istream& is, const CharT* format, sys_time& tp) -{ - parse(is, std::basic_string(format), tp); -} - template inline detail::parse_sys_manip @@ -5419,15 +6336,6 @@ parse(const CharT* format, sys_time& tp) return {format, tp}; } -template -inline -void -parse(std::basic_istream& is, const CharT* format, sys_time& tp, - std::basic_string& abbrev) -{ - parse(is, std::basic_string(format), tp, abbrev); -} - template inline detail::parse_sys_manip @@ -5437,15 +6345,6 @@ parse(const CharT* format, sys_time& tp, return {format, tp, &abbrev}; } -template -inline -void -parse(std::basic_istream& is, const CharT* format, sys_time& tp, - std::chrono::minutes& offset) -{ - parse(is, std::basic_string(format), tp, offset); -} - template inline detail::parse_sys_manip @@ -5454,15 +6353,6 @@ parse(const CharT* format, sys_time& tp, std::chrono::minutes& offset) return {format, tp, nullptr, &offset}; } -template -inline -void -parse(std::basic_istream& is, const CharT* format, sys_time& tp, - std::basic_string& abbrev, std::chrono::minutes& offset) -{ - parse(is, std::basic_string(format), tp, abbrev, offset); -} - template inline detail::parse_sys_manip @@ -5472,15 +6362,6 @@ parse(const CharT* format, sys_time& tp, return {format, tp, &abbrev, &offset}; } -template -inline -void -parse(std::basic_istream& is, const CharT* format, sys_time& tp, - std::chrono::minutes& offset, std::basic_string& abbrev) -{ - parse(is, std::basic_string(format), tp, abbrev, offset); -} - template inline detail::parse_sys_manip @@ -5490,15 +6371,6 @@ parse(const CharT* format, sys_time& tp, return {format, tp, &abbrev, &offset}; } -template -inline -void -parse(std::basic_istream& is, const CharT* format, - local_time& tp) -{ - parse(is, std::basic_string(format), tp); -} - template inline detail::parse_local_manip @@ -5507,15 +6379,6 @@ parse(const CharT* format, local_time& tp) return {format, tp}; } -template -inline -void -parse(std::basic_istream& is, const CharT* format, - local_time& tp, std::basic_string& abbrev) -{ - parse(is, std::basic_string(format), tp, abbrev); -} - template inline detail::parse_local_manip @@ -5525,15 +6388,6 @@ parse(const CharT* format, local_time& tp, return {format, tp, &abbrev}; } -template -inline -void -parse(std::basic_istream& is, const CharT* format, - local_time& tp, std::chrono::minutes& offset) -{ - parse(is, std::basic_string(format), tp, offset); -} - template inline detail::parse_local_manip @@ -5542,16 +6396,6 @@ parse(const CharT* format, local_time& tp, std::chrono::minutes& offse return {format, tp, nullptr, &offset}; } -template -inline -void -parse(std::basic_istream& is, const CharT* format, - local_time& tp, std::basic_string& abbrev, - std::chrono::minutes& offset) -{ - parse(is, std::basic_string(format), tp, abbrev, offset); -} - template inline detail::parse_local_manip @@ -5561,16 +6405,6 @@ parse(const CharT* format, local_time& tp, return {format, tp, &abbrev, &offset}; } -template -inline -void -parse(std::basic_istream& is, const CharT* format, - local_time& tp, std::chrono::minutes& offset, - std::basic_string& abbrev) -{ - parse(is, std::basic_string(format), tp, abbrev, offset); -} - template inline detail::parse_local_manip @@ -5580,6 +6414,114 @@ parse(const CharT* format, local_time& tp, std::chrono::minutes& offse return {format, tp, &abbrev, &offset}; } +template +inline +void +parse(std::basic_istream& is, const CharT* format, sys_time& tp) +{ + std::chrono::minutes offset{}; + local_time lt; + detail::parse(is, format, lt, &offset); + if (!is.fail()) + tp = sys_time{floor(lt - offset).time_since_epoch()}; +} + +template +inline +void +parse(std::basic_istream& is, const CharT* format, sys_time& tp, + std::basic_string& abbrev) +{ + std::chrono::minutes offset{}; + local_time lt; + detail::parse(is, format, lt, &abbrev, &offset); + if (!is.fail()) + tp = sys_time{floor(lt - offset).time_since_epoch()}; +} + +template +inline +void +parse(std::basic_istream& is, const CharT* format, sys_time& tp, + std::chrono::minutes& offset) +{ + local_time lt; + detail::parse(is, format, lt, &offset); + if (!is.fail()) + tp = sys_time{floor(lt - offset).time_since_epoch()}; +} + +template +inline +void +parse(std::basic_istream& is, const CharT* format, sys_time& tp, + std::basic_string& abbrev, std::chrono::minutes& offset) +{ + local_time lt; + detail::parse(is, format, lt, &abbrev, &offset); + if (!is.fail()) + tp = sys_time{floor(lt - offset).time_since_epoch()}; +} + +template +inline +void +parse(std::basic_istream& is, const CharT* format, sys_time& tp, + std::chrono::minutes& offset, std::basic_string& abbrev) +{ + local_time lt; + detail::parse(is, format, lt, &abbrev, &offset); + if (!is.fail()) + tp = sys_time{floor(lt - offset).time_since_epoch()}; +} + +template +inline +void +parse(std::basic_istream& is, const CharT* format, + local_time& tp) +{ + detail::parse(is, format, tp); +} + +template +inline +void +parse(std::basic_istream& is, const CharT* format, + local_time& tp, std::basic_string& abbrev) +{ + detail::parse(is, format, tp, &abbrev); +} + +template +inline +void +parse(std::basic_istream& is, const CharT* format, + local_time& tp, std::chrono::minutes& offset) +{ + detail::parse(is, format, tp, &offset); +} + +template +inline +void +parse(std::basic_istream& is, const CharT* format, + local_time& tp, std::basic_string& abbrev, + std::chrono::minutes& offset) +{ + detail::parse(is, format, tp, &abbrev, &offset); +} + +template +inline +void +parse(std::basic_istream& is, const CharT* format, + local_time& tp, std::chrono::minutes& offset, + std::basic_string& abbrev) +{ + detail::parse(is, format, tp, &abbrev, &offset); +} + } // namespace date #endif // DATE_H diff --git a/test/date_test/parse.pass.cpp b/test/date_test/parse.pass.cpp new file mode 100644 index 0000000..997cead --- /dev/null +++ b/test/date_test/parse.pass.cpp @@ -0,0 +1,730 @@ +// The MIT License (MIT) +// +// Copyright (c) 2016 Howard Hinnant +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// This test is meant to maintain a record of the sizeof each type. + +#include "date.h" +#include +#include + +void +test_a() +{ + using namespace date; + { + // correct abbreviation + std::istringstream in{"Sun 2016-12-11"}; + sys_days tp; + parse(in, "%a %F", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(!in.eof()); + assert(tp == 2016_y/12/11); + } + { + // correct abbreviation + std::istringstream in{"Sun 2016-12-11"}; + sys_days tp; + parse(in, "%A %F", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(!in.eof()); + assert(tp == 2016_y/12/11); + } + { + // correct full name + std::istringstream in{"Sunday 2016-12-11"}; + sys_days tp; + parse(in, "%a %F", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(!in.eof()); + assert(tp == 2016_y/12/11); + } + { + // correct full name + std::istringstream in{"Sunday 2016-12-11"}; + sys_days tp; + parse(in, "%A %F", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(!in.eof()); + assert(tp == 2016_y/12/11); + } + { + // not a valid name + std::istringstream in{"Dec 2016-12-11"}; + sys_days tp; + parse(in, "%a %F", tp); + assert( in.fail()); + assert(!in.bad()); + assert(!in.eof()); + assert(tp == 1970_y/1/1); + } + { + // wrong name + std::istringstream in{"Sat 2016-12-11"}; + sys_days tp; + parse(in, "%a %F", tp); + assert( in.fail()); + assert(!in.bad()); + assert(!in.eof()); + assert(tp == 1970_y/1/1); + } + { + // extra ws in input + std::istringstream in{"Sun 2016-12-11"}; + sys_days tp; + parse(in, "%a %F", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(!in.eof()); + assert(tp == 2016_y/12/11); + } + { + // extra ws in format + std::istringstream in{"Sun 2016-12-11"}; + sys_days tp; + parse(in, "%a %F", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(!in.eof()); + assert(tp == 2016_y/12/11); + } +} + +void +test_b() +{ + using namespace date; + { + // correct abbreviation + std::istringstream in{"Dec 11 2016"}; + sys_days tp; + parse(in, "%b %d %Y", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(!in.eof()); + assert(tp == 2016_y/12/11); + } + { + // correct abbreviation + std::istringstream in{"Dec 11 2016"}; + sys_days tp; + parse(in, "%B %d %Y", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(!in.eof()); + assert(tp == 2016_y/12/11); + } + { + // correct abbreviation + std::istringstream in{"Dec 11 2016"}; + sys_days tp; + parse(in, "%h %d %Y", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(!in.eof()); + assert(tp == 2016_y/12/11); + } + { + // correct full name + std::istringstream in{"December 11 2016"}; + sys_days tp; + parse(in, "%b %d %Y", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(!in.eof()); + assert(tp == 2016_y/12/11); + } + { + // correct full name + std::istringstream in{"December 11 2016"}; + sys_days tp; + parse(in, "%B %d %Y", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(!in.eof()); + assert(tp == 2016_y/12/11); + } + { + // correct full name + std::istringstream in{"December 11 2016"}; + sys_days tp; + parse(in, "%h %d %Y", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(!in.eof()); + assert(tp == 2016_y/12/11); + } + { + // incorrect abbreviation + std::istringstream in{"Dece 11 2016"}; + sys_days tp; + parse(in, "%b %d %Y", tp); + assert( in.fail()); + assert(!in.bad()); + assert(!in.eof()); + assert(tp == 1970_y/1/1); + } +} + +void +test_c() +{ + using namespace date; + using namespace std::chrono; + { + // correct abbreviation + std::istringstream in{"Sun Dec 11 14:02:43 2016"}; + sys_seconds tp; + parse(in, "%c", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == sys_days{2016_y/12/11} + hours{14} + minutes{2} + seconds{43}); + } +} + +void +test_x() +{ + using namespace date; + using namespace std::chrono; + { + // correct abbreviation + std::istringstream in{"12/11/16"}; + sys_seconds tp; + parse(in, "%x", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == sys_days{2016_y/12/11}); + } +} + +void +test_X() +{ + using namespace date; + using namespace std::chrono; + { + // correct abbreviation + std::istringstream in{"2016-12-11 14:02:43"}; + sys_seconds tp; + parse(in, "%F %X", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == sys_days{2016_y/12/11} + hours{14} + minutes{2} + seconds{43}); + } +} + +void +test_C() +{ + using namespace date; + using namespace std::chrono; + { + std::istringstream in{"20 16 12 11"}; + sys_days tp; + parse(in, "%C %y %m %d", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == 2016_y/12/11); + } + { + std::istringstream in{"-2 1 12 11"}; + sys_days tp; + parse(in, "%C %y %m %d", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == -101_y/12/11); + } + { + std::istringstream in{"-1 0 12 11"}; + sys_days tp; + parse(in, "%C %y %m %d", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == -100_y/12/11); + } + { + std::istringstream in{"-1 99 12 11"}; + sys_days tp; + parse(in, "%C %y %m %d", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == -99_y/12/11); + } + { + std::istringstream in{"-1 1 12 11"}; + sys_days tp; + parse(in, "%C %y %m %d", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == -1_y/12/11); + } + { + std::istringstream in{"0 0 12 11"}; + sys_days tp; + parse(in, "%C %y %m %d", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == 0_y/12/11); + } + { + std::istringstream in{"0 1 12 11"}; + sys_days tp; + parse(in, "%C %y %m %d", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == 1_y/12/11); + } + { + std::istringstream in{"0 99 12 11"}; + sys_days tp; + parse(in, "%C %y %m %d", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == 99_y/12/11); + } + { + std::istringstream in{"1 0 12 11"}; + sys_days tp; + parse(in, "%C %y %m %d", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == 100_y/12/11); + } + { + std::istringstream in{"1 1 12 11"}; + sys_days tp; + parse(in, "%C %y %m %d", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == 101_y/12/11); + } +} + +void +test_d() +{ + using namespace date; + using namespace std::chrono; + { + std::istringstream in{"2016 09 12"}; + sys_days tp; + parse(in, "%Y %d %m", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == 2016_y/12/9); + } + { + std::istringstream in{"2016 09 12"}; + sys_days tp; + parse(in, "%Y %e %m", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == 2016_y/12/9); + } + { + std::istringstream in{"2016 9 12"}; + sys_days tp; + parse(in, "%Y %d %m", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == 2016_y/12/9); + } + { + std::istringstream in{"2016 9 12"}; + sys_days tp; + parse(in, "%Y %e %m", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == 2016_y/12/9); + } +} + +void +test_D() +{ + using namespace date; + using namespace std::chrono; + { + std::istringstream in{"12/11/16"}; + sys_days tp; + parse(in, "%D", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == 2016_y/12/11); + } +} + +void +test_F() +{ + using namespace date; + using namespace std::chrono; + { + std::istringstream in{"2016-12-13"}; + sys_days tp; + parse(in, "%F", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == 2016_y/12/13); + } +} + +void +test_H() +{ + using namespace date; + using namespace std::chrono; + { + std::istringstream in{"2016-12-11 15"}; + sys_time tp; + parse(in, "%F %H", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == sys_days{2016_y/12/11} + hours{15}); + } +} + +void +test_Ip() +{ + using namespace date; + using namespace std::chrono; + { + std::istringstream in{"2016-12-11 1 pm"}; + sys_time tp; + parse(in, "%F %I %p", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == sys_days{2016_y/12/11} + hours{13}); + } + { + std::istringstream in{"2016-12-11 1 am"}; + sys_time tp; + parse(in, "%F %I %p", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == sys_days{2016_y/12/11} + hours{1}); + } +} + +void +test_j() +{ + using namespace date; + using namespace std::chrono; + { + std::istringstream in{"2016 361"}; + sys_days tp; + parse(in, "%Y %j", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == sys_days{2016_y/12/26}); + } +} + +void +test_m() +{ + using namespace date; + using namespace std::chrono; + { + std::istringstream in{"2016 12 09"}; + sys_days tp; + parse(in, "%Y %d %m", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == 2016_y/9/12); + } + { + std::istringstream in{"2016 12 9"}; + sys_days tp; + parse(in, "%Y %d %m", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == 2016_y/9/12); + } +} + +void +test_M() +{ + using namespace date; + using namespace std::chrono; + { + std::istringstream in{"2016-12-11 15"}; + sys_time tp; + parse(in, "%F %M", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == sys_days{2016_y/12/11} + minutes{15}); + } +} + +void +test_S() +{ + using namespace date; + using namespace std::chrono; + { + std::istringstream in{"2016-12-11 15"}; + sys_seconds tp; + parse(in, "%F %S", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == sys_days{2016_y/12/11} + seconds{15}); + } + { + std::istringstream in{"2016-12-11 15.001"}; + sys_time tp; + parse(in, "%F %S", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == sys_days{2016_y/12/11} + seconds{15} + milliseconds{1}); + } +} + +void +test_T() +{ + using namespace date; + using namespace std::chrono; + { + std::istringstream in{"2016-12-11 15:43:22"}; + sys_seconds tp; + parse(in, "%F %T", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == sys_days{2016_y/12/11} + hours{15} + minutes{43} + seconds{22}); + } + { + std::istringstream in{"2016-12-11 15:43:22.001"}; + sys_time tp; + parse(in, "%F %T", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == sys_days{2016_y/12/11} + hours{15} + minutes{43} + seconds{22} + + milliseconds{1}); + } + { + std::istringstream in{"2016-12-11 15:43:22"}; + sys_time tp; + parse(in, "%F %T", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == sys_days{2016_y/12/11} + hours{15} + minutes{43} + seconds{22}); + } +} + +void +test_p() +{ + using namespace date; + using namespace std::chrono; + { + std::istringstream in{"2016-12-11 11pm"}; + sys_time tp; + parse(in, "%F %I%p", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == sys_days{2016_y/12/11} + hours{23}); + } +} + +void +test_r() +{ + using namespace date; + using namespace std::chrono; + { + std::istringstream in{"2016-12-26 1:36:57 pm"}; + sys_seconds tp; + parse(in, "%F %r", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == sys_days{2016_y/12/26} + hours{13} + minutes{36} + seconds{57}); + } +} + +void +test_R() +{ + using namespace date; + using namespace std::chrono; + { + std::istringstream in{"2016-12-26 13:36"}; + sys_seconds tp; + parse(in, "%F %R", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == sys_days{2016_y/12/26} + hours{13} + minutes{36}); + } +} + +void +test_U() +{ + using namespace date; + using namespace std::chrono; + { + std::istringstream in{"2016-52-1"}; + sys_days tp; + parse(in, "%Y-%U-%w", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == sys_days{2016_y/12/26}); + } +} + +void +test_W() +{ + using namespace date; + using namespace std::chrono; + { + std::istringstream in{"2016-52-1"}; + sys_days tp; + parse(in, "%Y-%W-%w", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == sys_days{2016_y/12/26}); + } +} + +void +test_GV() +{ + using namespace date; + using namespace std::chrono; + { + std::istringstream in{"2016-52-1"}; + sys_days tp; + parse(in, "%G-%V-%w", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == sys_days{2016_y/12/26}); + } + { + std::istringstream in{"2016-52-1"}; + sys_days tp; + parse(in, "%G-%V-%w", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == sys_days{2016_y/12/26}); + } + { + std::istringstream in{"20 16-52-1"}; + sys_days tp; + parse(in, "%C %g-%V-%w", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == sys_days{2016_y/12/26}); + } + { + std::istringstream in{"20 16-52-1"}; + sys_days tp; + parse(in, "%C %g-%V-%u", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == sys_days{2016_y/12/26}); + } +} + +void +test_z() +{ + using namespace date; + using namespace std::chrono; + { + std::istringstream in{"2016-12-26 15:53:22 -0500"}; + sys_seconds tp; + parse(in, "%F %T %z", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == sys_days{2016_y/12/26} + hours{20} + minutes{53} + seconds{22}); + } + { + std::istringstream in{"2016-12-26 15:53:22 -0500"}; + local_seconds tp; + parse(in, "%F %T %z", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == local_days{2016_y/12/26} + hours{15} + minutes{53} + seconds{22}); + } + { + std::istringstream in{"2016-12-26 15:53:22 -05:00"}; + sys_seconds tp; + parse(in, "%F %T %Ez", tp); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == sys_days{2016_y/12/26} + hours{20} + minutes{53} + seconds{22}); + } +} + +void +test_Z() +{ + using namespace date; + using namespace std::chrono; + { + std::string a; + std::istringstream in{"2016-12-26 15:53:22 word"}; + local_seconds tp; + parse(in, "%F %T %Z", tp, a); + assert(!in.fail()); + assert(!in.bad()); + assert(tp == local_days{2016_y/12/26} + hours{15} + minutes{53} + seconds{22}); + assert(a == "word"); + } +} + +int +main() +{ + test_a(); + test_b(); + test_c(); + test_C(); + test_d(); + test_D(); + test_F(); + test_H(); + test_Ip(); + test_j(); + test_m(); + test_M(); + test_p(); + test_r(); + test_R(); + test_S(); + test_T(); + test_U(); + test_W(); + test_GV(); + test_x(); + test_X(); + test_z(); + test_Z(); +}