From cfe43c455b0c1f7c86e2f41933d8ddf6e6994f76 Mon Sep 17 00:00:00 2001 From: Elias Kosunen Date: Wed, 21 Aug 2024 01:11:18 +0300 Subject: [PATCH] WIP: chrono --- include/scn/chrono.h | 1061 ++++++++++++++++++++++++++ include/scn/scan.h | 5 +- tests/unittests/CMakeLists.txt | 1 + tests/unittests/chrono_test.cpp | 32 + tests/unittests/custom_type_test.cpp | 58 +- 5 files changed, 1143 insertions(+), 14 deletions(-) create mode 100644 include/scn/chrono.h create mode 100644 tests/unittests/chrono_test.cpp diff --git a/include/scn/chrono.h b/include/scn/chrono.h new file mode 100644 index 00000000..8131c3bf --- /dev/null +++ b/include/scn/chrono.h @@ -0,0 +1,1061 @@ +// Copyright 2017 Elias Kosunen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// This file is a part of scnlib: +// https://github.com/eliaskosunen/scnlib + +#pragma once + +#include + +#include +#include + +namespace scn { +SCN_BEGIN_NAMESPACE + +#if defined(__cpp_lib_chrono) && __cpp_lib_chrono >= 201907L + +using weekday = std::chrono::weekday; +using day = std::chrono::day; +using month = std::chrono::month; +using month_day = std::chrono::month_day; +using year = std::chrono::year; +using year_month = std::chrono::year_month; +using year_month_day = std::chrono::year_month_day; + +#else + +// TODO: fallbacks + +#endif // __cpp_lib_chrono >= 201907 + +struct datetime_components { + std::optional subsec; + std::optional sec; + std::optional min; + std::optional hour; + std::optional mday; + std::optional mon; + std::optional year; + std::optional wday; + std::optional yday; +}; + +template +struct with_tz : public Time { + std::optional tz_offset; + std::optional tz_name; +}; + +namespace detail { + +enum class numeric_system { + standard, + alternative_e, // 'E' + alternative_o, // 'O' +}; + +template +constexpr const CharT* parse_chrono_format_specs(const CharT* begin, + const CharT* end, + Handler&& handler) +{ + if (begin == end || *begin == CharT{'}'}) { + return begin; + } + if (*begin != CharT{'%'}) { + handler.on_error("Invalid chrono format string: Expected '%'"); + return begin; + } + + auto p = begin; + while (p != end) { + auto ch = *p; + if (ch == CharT{'}'}) { + break; + } + if (ch != CharT{'%'}) { + ++p; + continue; + } + if (begin != p) { + handler.on_text(begin, p); + } + ++p; // Consume '%' + if (p == end) { + handler.on_error("Unexpected end of chrono format string"); + return p; + } + + ch = *p; + ++p; + + switch (ch) { + case CharT{'%'}: + handler.on_text(&ch, &ch + 1); + break; + case CharT{'n'}: + case CharT{'t'}: + handler.on_whitespace(); + break; + // Year + case CharT{'Y'}: + handler.on_year(); + break; + case CharT{'y'}: + handler.on_short_year(); + break; + case CharT{'C'}: + handler.on_century(); + break; + case CharT{'G'}: + handler.on_iso_week_based_year(); + break; + case CharT{'g'}: + handler.on_iso_week_based_short_year(); + break; + // Month + case CharT{'b'}: + case CharT{'B'}: + case CharT{'h'}: + handler.on_month_name(); + break; + case CharT{'m'}: + handler.on_dec_month(); + break; + // Week + case CharT{'U'}: + handler.on_dec0_week_of_year(); + break; + case CharT{'W'}: + handler.on_dec1_week_of_year(); + break; + case CharT{'V'}: + handler.on_iso_week_of_year(); + break; + // Day of year + case CharT{'j'}: + handler.on_day_of_year(); + break; + // Day of month + case CharT{'d'}: + case CharT{'e'}: + handler.on_day_of_month(); + break; + // Day of week + case CharT{'a'}: + case CharT{'A'}: + handler.on_weekday_name(); + break; + case CharT{'w'}: + handler.on_dec0_weekday(); + break; + case CharT{'u'}: + handler.on_dec1_weekday(); + break; + // Hour + case CharT{'H'}: + case CharT{'k'}: + handler.on_24_hour(); + break; + case CharT{'I'}: + case CharT{'l'}: + handler.on_12_hour(); + break; + // Minute + case CharT{'M'}: + handler.on_minute(); + break; + // Second + case CharT{'S'}: + handler.on_second(); + break; + // Timezones + case CharT{'z'}: + handler.on_tz_offset(); + break; + case CharT{'Z'}: + handler.on_tz_name(); + break; + // Other + case CharT{'c'}: + handler.on_loc_datetime(); + break; + case CharT{'x'}: + handler.on_loc_date(); + break; + case CharT{'X'}: + handler.on_loc_time(); + break; + case CharT{'D'}: + handler.on_us_date(); + break; + case CharT{'F'}: + handler.on_iso_date(); + break; + case CharT{'r'}: + handler.on_12_hour_time(); + break; + case CharT{'R'}: + handler.on_24_hour_time(); + break; + case CharT{'T'}: + handler.on_iso_time(); + break; + case CharT{'p'}: + case CharT{'P'}: + handler.on_am_pm(); + break; + case CharT{'s'}: + handler.on_epoch_offset(); + break; + // 'E' + case CharT{'E'}: { + if (p == end) { + handler.on_error("Unexpected end of chrono format string"); + return p; + } + ch = *p; + ++p; + + switch (ch) { + case CharT{'c'}: + handler.on_loc_datetime(numeric_system::alternative_e); + break; + case CharT{'C'}: + handler.on_century(numeric_system::alternative_e); + break; + case CharT{'x'}: + handler.on_loc_date(numeric_system::alternative_e); + break; + case CharT{'X'}: + handler.on_loc_time(numeric_system::alternative_e); + break; + case CharT{'y'}: + handler.on_loc_offset_year(); + break; + case CharT{'Y'}: + handler.on_year(numeric_system::alternative_e); + break; + case CharT{'z'}: + handler.on_tz_offset(numeric_system::alternative_e); + break; + default: + handler.on_error( + "Invalid character following 'E' in chrono format " + "string"); + return p; + } + break; + } + // 'O' + case CharT{'O'}: { + if (p == end) { + handler.on_error("Unexpected end of chrono format string"); + return p; + } + ch = *p; + ++p; + + switch (ch) { + case CharT{'d'}: + case CharT{'e'}: + handler.on_day_of_month(numeric_system::alternative_o); + break; + case CharT{'H'}: + case CharT{'k'}: + handler.on_24_hour(numeric_system::alternative_o); + break; + case CharT{'I'}: + case CharT{'l'}: + handler.on_12_hour(numeric_system::alternative_o); + break; + case CharT{'m'}: + handler.on_dec_month(numeric_system::alternative_o); + break; + case CharT{'M'}: + handler.on_minute(numeric_system::alternative_o); + break; + case CharT{'S'}: + handler.on_second(numeric_system::alternative_o); + break; + case CharT{'U'}: + handler.on_dec0_week_of_year( + numeric_system::alternative_o); + break; + case CharT{'w'}: + handler.on_dec0_weekday(numeric_system::alternative_o); + break; + case CharT{'W'}: + handler.on_dec1_weekday(numeric_system::alternative_o); + break; + case CharT{'y'}: + handler.on_short_year(numeric_system::alternative_o); + break; + case CharT{'z'}: + handler.on_tz_offset(numeric_system::alternative_o); + break; + default: + handler.on_error( + "Invalid character following 'O' in chrono format " + "string"); + return p; + } + break; + } + default: + handler.on_error("Invalid character in chrono format string"); + return p; + } + begin = p; + } + if (begin != p) { + handler.on_text(begin, p); + } + return p; +} + +template +struct null_chrono_spec_handler { + constexpr void unsupported() + { + static_cast(this)->unsupported(); + } + + constexpr void on_year(numeric_system = numeric_system::standard) + { + unsupported(); + } + constexpr void on_short_year(numeric_system = numeric_system::standard) + { + unsupported(); + } + constexpr void on_century(numeric_system = numeric_system::standard) + { + unsupported(); + } + constexpr void on_iso_week_based_year() + { + unsupported(); + } + constexpr void on_iso_week_based_short_year() + { + unsupported(); + } + constexpr void on_loc_offset_year() + { + unsupported(); + } + + constexpr void on_month_name() + { + unsupported(); + } + constexpr void on_dec_month(numeric_system = numeric_system::standard) + { + unsupported(); + } + + constexpr void on_dec0_week_of_year( + numeric_system = numeric_system::standard) + { + unsupported(); + } + constexpr void on_dec1_week_of_year() + { + unsupported(); + } + constexpr void on_iso_week_of_year() + { + unsupported(); + } + constexpr void on_day_of_year() + { + unsupported(); + } + constexpr void on_day_of_month(numeric_system = numeric_system::standard) + { + unsupported(); + } + + constexpr void on_weekday_name() + { + unsupported(); + } + constexpr void on_dec0_weekday(numeric_system = numeric_system::standard) + { + unsupported(); + } + constexpr void on_dec1_weekday(numeric_system = numeric_system::standard) + { + unsupported(); + } + + constexpr void on_24_hour(numeric_system = numeric_system::standard) + { + unsupported(); + } + constexpr void on_12_hour(numeric_system = numeric_system::standard) + { + unsupported(); + } + constexpr void on_minute(numeric_system = numeric_system::standard) + { + unsupported(); + } + constexpr void on_second(numeric_system = numeric_system::standard) + { + unsupported(); + } + + constexpr void on_tz_offset(numeric_system = numeric_system::standard) + { + unsupported(); + } + constexpr void on_tz_name() + { + unsupported(); + } + + constexpr void on_loc_datetime(numeric_system = numeric_system::standard) + { + unsupported(); + } + constexpr void on_loc_date(numeric_system = numeric_system::standard) + { + unsupported(); + } + constexpr void on_loc_time(numeric_system = numeric_system::standard) + { + unsupported(); + } + constexpr void on_us_date() + { + unsupported(); + } + constexpr void on_iso_date() + { + unsupported(); + } + constexpr void on_12_hour_time() + { + unsupported(); + } + constexpr void on_24_hour_time() + { + unsupported(); + } + constexpr void on_iso_time() + { + unsupported(); + } + constexpr void on_am_pm() + { + unsupported(); + } + constexpr void on_epoch_offset() + { + unsupported(); + } +}; + +struct tm_format_checker { + template + constexpr void on_text(const CharT*, const CharT*) + { + } + constexpr void on_whitespace() {} + + constexpr void on_year(numeric_system = numeric_system::standard) {} + constexpr void on_short_year(numeric_system = numeric_system::standard) {} + constexpr void on_century(numeric_system = numeric_system::standard) {} + constexpr void on_iso_week_based_year() {} + constexpr void on_iso_week_based_short_year() {} + constexpr void on_loc_offset_year() {} + + constexpr void on_month_name() {} + constexpr void on_dec_month(numeric_system = numeric_system::standard) {} + + constexpr void on_dec0_week_of_year( + numeric_system = numeric_system::standard) + { + } + constexpr void on_dec1_week_of_year() {} + constexpr void on_iso_week_of_year() {} + constexpr void on_day_of_year() {} + constexpr void on_day_of_month(numeric_system = numeric_system::standard) {} + + constexpr void on_weekday_name() {} + constexpr void on_dec0_weekday(numeric_system = numeric_system::standard) {} + constexpr void on_dec1_weekday(numeric_system = numeric_system::standard) {} + + constexpr void on_24_hour(numeric_system = numeric_system::standard) {} + constexpr void on_12_hour(numeric_system = numeric_system::standard) {} + constexpr void on_minute(numeric_system = numeric_system::standard) {} + constexpr void on_second(numeric_system = numeric_system::standard) {} + + constexpr void on_tz_offset(numeric_system = numeric_system::standard) {} + constexpr void on_tz_name() {} + + constexpr void on_loc_datetime(numeric_system = numeric_system::standard) {} + constexpr void on_loc_date(numeric_system = numeric_system::standard) {} + constexpr void on_loc_time(numeric_system = numeric_system::standard) {} + constexpr void on_us_date() {} + constexpr void on_iso_date() {} + constexpr void on_12_hour_time() {} + constexpr void on_24_hour_time() {} + constexpr void on_iso_time() {} + constexpr void on_am_pm() {} + constexpr void on_epoch_offset() {} + + [[nodiscard]] constexpr scan_error get_error() const + { + return err; + } + constexpr void on_error(const char* msg) + { + detail::handle_error(err = {scan_error::invalid_format_string, msg}); + } + + scan_error err{}; +}; + +template +struct datetime_setter; + +template +using has_tm_gmtoff_predicate = decltype(T::tm_gmtoff); + +template +inline constexpr bool can_set_tz_offset = false; +template +inline constexpr bool can_set_tz_offset< + T, + std::enable_if_t< + std::is_invocable_v::set_tz_offset), + T&, + int>>> = true; + +template +inline constexpr bool can_set_tz_name = false; +template +inline constexpr bool can_set_tz_name< + T, + std::enable_if_t< + std::is_invocable_v::set_tz_name), + T&, + std::string>>> = true; + +template <> +struct datetime_setter { + static scan_error set_subsec(std::tm&, double) + { + return scan_error{scan_error::invalid_format_string, + "Subsecond precision not supported with std::tm"}; + } + + static scan_error set_sec(std::tm& t, int s) + { + if (SCN_UNLIKELY(s < 0 || s > 60)) { + return scan_error{scan_error::value_out_of_range, + "Invalid value for tm_sec"}; + } + t.tm_sec = s; + return {}; + } + static scan_error set_min(std::tm& t, int m) + { + if (SCN_UNLIKELY(m < 0 || m > 59)) { + return scan_error{scan_error::value_out_of_range, + "Invalid value for tm_min"}; + } + t.tm_min = m; + return {}; + } + static scan_error set_hour(std::tm& t, int h) + { + if (SCN_UNLIKELY(h < 0 || h > 23)) { + return scan_error{scan_error::value_out_of_range, + "Invalid value for tm_hour"}; + } + t.tm_hour = h; + return {}; + } + static scan_error set_mday(std::tm& t, int d) + { + if (SCN_UNLIKELY(d < 1 || d > 31)) { + return scan_error{scan_error::value_out_of_range, + "Invalid value for tm_mday"}; + } + t.tm_mday = d; + return {}; + } + static scan_error set_mon(std::tm& t, int m) + { + if (SCN_UNLIKELY(m < 0 || m > 11)) { + return scan_error{scan_error::value_out_of_range, + "Invalid value for tm_mon"}; + } + t.tm_mon = m; + return {}; + } + static scan_error set_year(std::tm& t, int y) + { + if (SCN_UNLIKELY(y < std::numeric_limits::min() + 1900)) { + return scan_error{scan_error::value_out_of_range, + "Invalid value for tm_year"}; + } + t.tm_year = y - 1900; + return {}; + } + static scan_error set_wday(std::tm& t, int d) + { + if (SCN_UNLIKELY(d < 0 || d > 6)) { + return scan_error{scan_error::value_out_of_range, + "Invalid value for tm_wday"}; + } + t.tm_wday = d; + return {}; + } + static scan_error set_yday(std::tm& t, int d) + { + if (SCN_UNLIKELY(d < 0 || d > 365)) { + return scan_error{scan_error::value_out_of_range, + "Invalid value for tm_yday"}; + } + t.tm_yday = d; + return {}; + } + + static scan_error set_tz_offset(std::tm& t, std::chrono::minutes o) + { + if constexpr (mp_valid::value) { + t.tm_gmtoff = o.count() * 60l; + return {}; + } + else { + return scan_error{scan_error::invalid_format_string, + "tm_gmtoff not supported"}; + } + } + + static scan_error set_tz_name(std::tm&, const std::string&) + { + return scan_error{scan_error::invalid_format_string, + "tm_zone not supported"}; + } +}; + +template <> +struct datetime_setter { + static scan_error set_subsec(datetime_components& t, double s) + { + t.subsec = s; + return {}; + } + static scan_error set_sec(datetime_components& t, int s) + { + if (SCN_UNLIKELY(s < 0 || s > 60)) { + return scan_error{scan_error::value_out_of_range, + "Invalid value for seconds"}; + } + t.sec = s; + return {}; + } + static scan_error set_min(datetime_components& t, int m) + { + if (SCN_UNLIKELY(m < 0 || m > 59)) { + return scan_error{scan_error::value_out_of_range, + "Invalid value for minutes"}; + } + t.min = m; + return {}; + } + static scan_error set_hour(datetime_components& t, int h) + { + if (SCN_UNLIKELY(h < 0 || h > 23)) { + return scan_error{scan_error::value_out_of_range, + "Invalid value for hours"}; + } + t.hour = h; + return {}; + } + static scan_error set_mday(datetime_components& t, int d) + { + if (SCN_UNLIKELY(d < 1 || d > 31)) { + return scan_error{scan_error::value_out_of_range, + "Invalid value for mday"}; + } + t.mday = d; + return {}; + } + static scan_error set_mon(datetime_components& t, int m) + { + if (SCN_UNLIKELY(m < 0 || m > 11)) { + return scan_error{scan_error::value_out_of_range, + "Invalid value for mon"}; + } + t.mon = m; + return {}; + } + static scan_error set_year(datetime_components& t, int y) + { + t.year = y; + return {}; + } + static scan_error set_wday(datetime_components& t, int d) + { + if (SCN_UNLIKELY(d < 0 || d > 6)) { + return scan_error{scan_error::value_out_of_range, + "Invalid value for wday"}; + } + t.wday = d; + return {}; + } + static scan_error set_yday(datetime_components& t, int d) + { + if (SCN_UNLIKELY(d < 0 || d > 6)) { + return scan_error{scan_error::value_out_of_range, + "Invalid value for yday"}; + } + t.yday = d; + return {}; + } + + static scan_error set_tz_offset(std::tm& t, std::chrono::minutes o) + { + return scan_error{scan_error::invalid_format_string, + "gmtoff not supported for datetime_components"}; + } + + static scan_error set_tz_name(std::tm&, const std::string&) + { + return scan_error{ + scan_error::invalid_format_string, + "string time zone not supported for datetime_components"}; + } +}; + +template +struct datetime_setter> : datetime_setter { + static void set_tz_offset(with_tz& t, std::chrono::minutes o) + { + if constexpr (can_set_tz_offset) { + datetime_setter::set_tz_offset(t, o); + } + t.tz_offset = o; + } + + static void set_tz_name(with_tz& t, std::string n) + { + if constexpr (can_set_tz_name) { + datetime_setter::set_tz_name(t, n); + } + t.tz_name = std::move(n); + } +}; + +template +class tm_reader { +public: + using type = T; + using setter = datetime_setter; + using iterator = ranges::iterator_t; + + tm_reader(Range r, T& t) + : m_range(SCN_MOVE(r)), m_begin(ranges::begin(m_range)), m_tm(t) + { + } + + void on_text(const CharT*, const CharT*) + { + unimplemented(); + } + void on_whitespace() + { + unimplemented(); + } + + void on_year(numeric_system sys = numeric_system::standard) + { + if (sys != numeric_system::standard) { + return unimplemented(); + } + int yr = read_classic_unsigned_integer(4, 4); + setter::set_year(m_tm, yr); + } + void on_short_year(numeric_system = numeric_system::standard) + { + unimplemented(); + } + void on_century(numeric_system = numeric_system::standard) + { + unimplemented(); + } + void on_iso_week_based_year() + { + unimplemented(); + } + void on_iso_week_based_short_year() + { + unimplemented(); + } + void on_loc_offset_year() + { + unimplemented(); + } + + void on_month_name() + { + unimplemented(); + } + void on_dec_month(numeric_system sys = numeric_system::standard) + { + if (sys != numeric_system::standard) { + return unimplemented(); + } + int mon = read_classic_unsigned_integer(1, 2); + setter::set_mon(m_tm, mon - 1); + } + + void on_dec0_week_of_year(numeric_system sys = numeric_system::standard) + { + unimplemented(); + } + void on_dec1_week_of_year() + { + unimplemented(); + } + void on_iso_week_of_year() + { + unimplemented(); + } + void on_day_of_year() + { + int yday = read_classic_unsigned_integer(1, 3); + setter::set_yday(m_tm, yday - 1); + } + void on_day_of_month(numeric_system sys = numeric_system::standard) + { + if (sys != numeric_system::standard) { + return unimplemented(); + } + int mday = read_classic_unsigned_integer(1, 2); + setter::set_mday(m_tm, mday - 1); + } + + void on_weekday_name() + { + unimplemented(); + } + void on_dec0_weekday(numeric_system sys = numeric_system::standard) + { + if (sys != numeric_system::standard) { + return unimplemented(); + } + int wday = read_classic_unsigned_integer(1, 1); + setter::set_wday(m_tm, wday); + } + void on_dec1_weekday(numeric_system sys = numeric_system::standard) + { + if (sys != numeric_system::standard) { + return unimplemented(); + } + int wday = read_classic_unsigned_integer(1, 1); + setter::set_wday(m_tm, wday == 0 ? 6 : wday - 1); + } + + void on_24_hour(numeric_system sys = numeric_system::standard) + { + if (sys != numeric_system::standard) { + return unimplemented(); + } + int hr = read_classic_unsigned_integer(1, 2); + setter::set_hour(m_tm, hr); + } + void on_12_hour(numeric_system sys = numeric_system::standard) + { + unimplemented(); + } + void on_minute(numeric_system sys = numeric_system::standard) + { + if (sys != numeric_system::standard) { + return unimplemented(); + } + int min = read_classic_unsigned_integer(1, 2); + setter::set_min(m_tm, min); + } + void on_second(numeric_system sys = numeric_system::standard) + { + if (sys != numeric_system::standard) { + return unimplemented(); + } + int sec = read_classic_unsigned_integer(1, 2); + setter::set_sec(m_tm, sec); + } + + void on_tz_offset(numeric_system = numeric_system::standard) + { + unimplemented(); + } + void on_tz_name() + { + unimplemented(); + } + + void on_loc_datetime(numeric_system = numeric_system::standard) + { + unimplemented(); + } + void on_loc_date(numeric_system = numeric_system::standard) + { + unimplemented(); + } + void on_loc_time(numeric_system = numeric_system::standard) + { + unimplemented(); + } + void on_us_date() + { + unimplemented(); + } + void on_iso_date() + { + unimplemented(); + } + void on_12_hour_time() + { + unimplemented(); + } + void on_24_hour_time() + { + unimplemented(); + } + void on_iso_time() + { + unimplemented(); + } + void on_am_pm() + { + unimplemented(); + } + void on_epoch_offset() + { + unimplemented(); + } + + scan_error get_error() const + { + return m_error; + } + + void on_error(const char* msg) + { + set_error({scan_error::invalid_format_string, msg}); + } + + iterator get_iterator() + { + return m_begin; + } + +private: + void set_error(scan_error e) + { + if (e) { + m_error = e; + } + } + + void unimplemented() + { + on_error("Unimplemented"); + } + + int read_classic_unsigned_integer(int min_digits, int max_digits) + { + int digits_read = 0; + int accumulator = 0; + while (m_begin != m_range.end()) { + const auto ch = *m_begin; + if (ch < CharT{'0'} || ch > CharT{'9'}) { + break; + } + ++m_begin; + ++digits_read; + accumulator = accumulator * 10 + static_cast(ch - CharT{'0'}); + if (digits_read > max_digits) { + break; + } + } + if (digits_read < min_digits) { + set_error(scan_error{scan_error::invalid_scanned_value, + "Too few integer digits"}); + } + return accumulator; + } + + Range m_range; + iterator m_begin; + T& m_tm; + scan_error m_error{}; +}; + +} // namespace detail + +template +struct scanner { + template + constexpr auto parse(ParseCtx& pctx) + -> scan_expected + { + auto it = pctx.begin(); + auto end = pctx.end(); + if (it == end || *it == CharT{'}'}) { + return it; + } + + auto checker = detail::tm_format_checker{}; + end = detail::parse_chrono_format_specs(it, end, checker); + if (end != it) { + m_fmt_str = detail::make_string_view_from_iterators(it, end); + } + if (auto e = checker.get_error(); SCN_UNLIKELY(!e)) { + return unexpected(e); + } + return end; + } + + template + auto scan(std::tm& t, Context& ctx) const + -> scan_expected + { + auto r = + detail::tm_reader( + ctx.range(), t); + detail::parse_chrono_format_specs( + m_fmt_str.data(), m_fmt_str.data() + m_fmt_str.size(), r); + if (auto e = r.get_error(); SCN_UNLIKELY(!e)) { + return unexpected(e); + } + return r.get_iterator(); + } + +protected: + std::basic_string_view m_fmt_str{}; +}; + +SCN_END_NAMESPACE +} // namespace scn diff --git a/include/scn/scan.h b/include/scn/scan.h index 5f4da6af..9a781d17 100644 --- a/include/scn/scan.h +++ b/include/scn/scan.h @@ -4937,8 +4937,9 @@ class arg_value { auto& pctx_ref = *static_cast(pctx); auto& ctx_ref = *static_cast(ctx); - SCN_TRY_ERR(_, s.parse(pctx_ref)); - SCN_UNUSED(_); + SCN_TRY_ERR(fmt_it, s.parse(pctx_ref)); + pctx_ref.advance_to(fmt_it); + SCN_TRY_ERR(it, s.scan(arg_ref, ctx_ref)); ctx_ref.advance_to(SCN_MOVE(it)); diff --git a/tests/unittests/CMakeLists.txt b/tests/unittests/CMakeLists.txt index ef686ab8..f4a0f144 100644 --- a/tests/unittests/CMakeLists.txt +++ b/tests/unittests/CMakeLists.txt @@ -24,6 +24,7 @@ add_executable(scn_tests args_test.cpp buffer_test.cpp char_test.cpp + chrono_test.cpp context_test.cpp custom_type_test.cpp error_test.cpp diff --git a/tests/unittests/chrono_test.cpp b/tests/unittests/chrono_test.cpp new file mode 100644 index 00000000..2e13be0c --- /dev/null +++ b/tests/unittests/chrono_test.cpp @@ -0,0 +1,32 @@ +// Copyright 2017 Elias Kosunen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// This file is a part of scnlib: +// https://github.com/eliaskosunen/scnlib + +#include "wrapped_gtest.h" + +#include +#include + +namespace { + +TEST(ChronoScanTest, ScanTmYear) +{ + auto result = scn::scan("2024", "{:%Y}"); + ASSERT_TRUE(result); + EXPECT_EQ(result->value().tm_year, 2024 - 1900); +} + +} // namespace diff --git a/tests/unittests/custom_type_test.cpp b/tests/unittests/custom_type_test.cpp index e793fed2..8bfc835a 100644 --- a/tests/unittests/custom_type_test.cpp +++ b/tests/unittests/custom_type_test.cpp @@ -25,15 +25,36 @@ struct mytype { template <> struct scn::scanner : scn::scanner { + template + scn::scan_expected parse( + ParseContext& pctx) + { + ParseContext other{"c}"}; + scn::scanner::parse(other); + return pctx.begin(); + } + template scn::scan_expected scan(mytype& val, Context& ctx) const { - return scn::scan(ctx.range(), "{} {}") - .transform([&](auto result) { - std::tie(val.i, val.j) = result.values(); - return result.begin(); - }); + std::string str{}; + auto in = scn::scanner::scan(str, ctx); + if (!in) { + return unexpected(in.error()); + } + + auto res = scn::scan(str, "{} {}"); + if (!res) { + return unexpected(res.error()); + } + if (res->begin() != str.end()) { + return unexpected(scan_error{scan_error::invalid_scanned_value, + "Expected two integers"}); + } + + std::tie(val.i, val.j) = res->values(); + return *in; } }; @@ -47,11 +68,24 @@ struct scn::scanner : scn::scanner { scn::scan_expected scan(mytype2& val, Context& ctx) const { - return scn::scan, char>(ctx.range(), "{} {}") - .transform([&](auto result) { - std::tie(std::ignore, val.ch) = result.values(); - return result.begin(); - }); + std::string str{}; + auto in = scn::scanner::scan(str, ctx); + if (!in) { + return unexpected(in.error()); + } + + auto res = scn::scan, char>(str, "{} {}"); + if (!res) { + return unexpected(res.error()); + } + if (res->begin() != str.end()) { + return unexpected( + scan_error{scan_error::invalid_scanned_value, + "Expected two chars separated by whitespace"}); + } + + val.ch = std::get<1>(res->values()); + return *in; } }; @@ -90,10 +124,10 @@ TEST(CustomTypeTest, Simple) EXPECT_EQ(val.j, 456); } -TEST(CustomTypeTest, DISABLED_CustomFormatString) +TEST(CustomTypeTest, CustomFormatString) { auto input = std::string_view{"123 456"}; - auto result = scn::scan(input, "{:6}"); + auto result = scn::scan(input, "{:.6}"); ASSERT_TRUE(result); EXPECT_EQ(result->begin(), input.end() - 1); EXPECT_EQ(result->value().i, 123);