diff --git a/date.h b/date.h index 4823397..12076a3 100644 --- a/date.h +++ b/date.h @@ -4141,10 +4141,51 @@ operator<<(std::basic_ostream& os, const local_time& ut template struct fields { - year_month_day ymd; - time_of_day tod; + year_month_day ymd{0_y/0/0}; + weekday wd{7u}; + time_of_day tod{}; + + fields() = default; + + fields(year_month_day ymd_) : ymd(ymd_) {} + fields(weekday wd_) : wd(wd_) {} + fields(time_of_day tod_) : tod(tod_) {} + + fields(year_month_day ymd_, weekday wd_) : ymd(ymd_), wd(wd_) {} + fields(year_month_day ymd_, time_of_day tod_) : ymd(ymd_), tod(tod_) {} + + fields(weekday wd_, time_of_day tod_) : wd(wd_), tod(tod_) {} + + fields(year_month_day ymd_, weekday wd_, time_of_day tod_) + : ymd(ymd_) + , wd(wd_) + , tod(tod_) + {} }; +namespace detail +{ + +template +unsigned +extract_weekday(const fields& fds) +{ + if (!fds.ymd.ok() && !fds.wd.ok()) + throw std::runtime_error("Can not format %u with unknown weekday"); + unsigned wd; + if (fds.ymd.ok()) + { + wd = static_cast(weekday{fds.ymd}); + if (fds.wd.ok() && wd != static_cast(fds.wd)) + throw std::runtime_error("Can not format %u with inconsistent weekday"); + } + else + wd = static_cast(fds.wd); + return wd; +} + +} // namespace detail + template void to_stream(std::basic_ostream& os, const CharT* fmt, @@ -4171,7 +4212,7 @@ to_stream(std::basic_ostream& os, const CharT* fmt, { if (modified == CharT{}) { - tm.tm_wday = static_cast(unsigned(weekday{fds.ymd})); + tm.tm_wday = static_cast(detail::extract_weekday(fds)); const CharT f[] = {'%', *fmt}; facet.put(os, os, os.fill(), &tm, begin(f), end(f)); } @@ -4224,7 +4265,7 @@ to_stream(std::basic_ostream& os, const CharT* fmt, tm.tm_mday = static_cast(static_cast(ymd.day())); tm.tm_mon = static_cast(static_cast(ymd.month()) - 1); tm.tm_year = static_cast(ymd.year()) - 1900; - tm.tm_wday = static_cast(static_cast(weekday{ld})); + tm.tm_wday = static_cast(detail::extract_weekday(fds)); tm.tm_yday = static_cast((ld - local_days(ymd.year()/1/1)).count()); CharT f[3] = {'%'}; auto fe = begin(f) + 1; @@ -4636,7 +4677,7 @@ to_stream(std::basic_ostream& os, const CharT* fmt, case 'u': if (command) { - auto wd = static_cast(weekday{fds.ymd}); + auto wd = detail::extract_weekday(fds); if (modified == CharT{'O'}) { const CharT f[] = {'%', modified, *fmt}; @@ -4667,7 +4708,7 @@ to_stream(std::basic_ostream& os, const CharT* fmt, { const CharT f[] = {'%', modified, *fmt}; tm.tm_year = static_cast(ymd.year()) - 1900; - tm.tm_wday = static_cast(static_cast(weekday{ld})); + tm.tm_wday = static_cast(detail::extract_weekday(fds)); tm.tm_yday = static_cast((ld - local_days(ymd.year()/1/1)).count()); facet.put(os, os, os.fill(), &tm, begin(f), end(f)); modified = CharT{}; @@ -4704,7 +4745,7 @@ to_stream(std::basic_ostream& os, const CharT* fmt, const CharT f[] = {'%', modified, *fmt}; auto const& ymd = fds.ymd; tm.tm_year = static_cast(ymd.year()) - 1900; - tm.tm_wday = static_cast(static_cast(weekday{ld})); + tm.tm_wday = static_cast(detail::extract_weekday(fds)); tm.tm_yday = static_cast((ld - local_days(ymd.year()/1/1)).count()); facet.put(os, os, os.fill(), &tm, begin(f), end(f)); modified = CharT{}; @@ -4736,7 +4777,7 @@ to_stream(std::basic_ostream& os, const CharT* fmt, case 'w': if (command) { - auto wd = static_cast(weekday{fds.ymd}); + auto wd = detail::extract_weekday(fds); if (modified == CharT{'O'}) { const CharT f[] = {'%', modified, *fmt}; @@ -4767,7 +4808,7 @@ to_stream(std::basic_ostream& os, const CharT* fmt, { const CharT f[] = {'%', modified, *fmt}; tm.tm_year = static_cast(ymd.year()) - 1900; - tm.tm_wday = static_cast(static_cast(weekday{ld})); + tm.tm_wday = static_cast(detail::extract_weekday(fds)); tm.tm_yday = static_cast((ld - local_days(ymd.year()/1/1)).count()); facet.put(os, os, os.fill(), &tm, begin(f), end(f)); modified = CharT{}; @@ -4873,7 +4914,7 @@ to_stream(std::basic_ostream& os, const CharT* fmt, if (command) { if (offset_sec == nullptr) - throw std::runtime_error("Can not format local_time with %z"); + throw std::runtime_error("Can not format %z with unknown offset"); auto m = duration_cast(*offset_sec); auto neg = m < minutes{0}; m = abs(m); @@ -4903,7 +4944,7 @@ to_stream(std::basic_ostream& os, const CharT* fmt, if (modified == CharT{}) { if (abbrev == nullptr) - throw std::runtime_error("Can not format local_time with %Z"); + throw std::runtime_error("Can not format %Z with unknown time_zone"); for (auto c : *abbrev) os << CharT(c); } @@ -4974,6 +5015,66 @@ to_stream(std::basic_ostream& os, const CharT* fmt, os << modified; } +template +inline +void +to_stream(std::basic_ostream& os, const CharT* fmt, const year& y) +{ + using CT = std::chrono::seconds; + fields fds{y/0/0}; + to_stream(os, fmt, fds); +} + +template +inline +void +to_stream(std::basic_ostream& os, const CharT* fmt, const month& m) +{ + using CT = std::chrono::seconds; + fields fds{m/0/0}; + to_stream(os, fmt, fds); +} + +template +inline +void +to_stream(std::basic_ostream& os, const CharT* fmt, const day& d) +{ + using CT = std::chrono::seconds; + fields fds{d/0/0}; + to_stream(os, fmt, fds); +} + +template +inline +void +to_stream(std::basic_ostream& os, const CharT* fmt, const weekday& wd) +{ + using CT = std::chrono::seconds; + fields fds{wd}; + to_stream(os, fmt, fds); +} + +template +inline +void +to_stream(std::basic_ostream& os, const CharT* fmt, const year_month& ym) +{ + using CT = std::chrono::seconds; + fields fds{ym/0}; + to_stream(os, fmt, fds); +} + +template +inline +void +to_stream(std::basic_ostream& os, const CharT* fmt, const month_day& md) +{ + using CT = std::chrono::seconds; + fields fds{md/0}; + to_stream(os, fmt, fds); +} + template inline void @@ -4981,7 +5082,7 @@ to_stream(std::basic_ostream& os, const CharT* fmt, const year_month_day& ymd) { using CT = std::chrono::seconds; - fields fds{ymd, time_of_day{}}; + fields fds{ymd}; to_stream(os, fmt, fds); } @@ -5051,7 +5152,7 @@ template auto format(const std::locale& loc, const std::basic_string& fmt, const Streamable& tp) - -> decltype(to_stream(std::declval&>(), fmt, tp), + -> decltype(to_stream(std::declval&>(), fmt.c_str(), tp), std::basic_string{}) { std::basic_ostringstream os; @@ -5063,7 +5164,7 @@ format(const std::locale& loc, const std::basic_string& fm template auto format(const std::basic_string& fmt, const Streamable& tp) - -> decltype(to_stream(std::declval&>(), fmt, tp), + -> decltype(to_stream(std::declval&>(), fmt.c_str(), tp), std::basic_string{}) { std::basic_ostringstream os; @@ -6047,6 +6148,7 @@ from_stream(std::basic_istream& is, const CharT* fmt, { if (y != not_a_2digit_year) { + // Convert y and an optional C to Y if (!(0 <= y && y <= 99)) goto broken; if (C == not_a_century) @@ -6074,6 +6176,7 @@ from_stream(std::basic_istream& is, const CharT* fmt, } if (g != not_a_2digit_year) { + // Convert g and an optional C to G if (!(0 <= g && g <= 99)) goto broken; if (C == not_a_century) @@ -6101,6 +6204,7 @@ from_stream(std::basic_istream& is, const CharT* fmt, } if (G != not_a_year) { + // Convert G, V and wd to Y, m and d 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]) + @@ -6119,84 +6223,73 @@ from_stream(std::basic_istream& is, const CharT* fmt, else if (day(d) != ymd.day()) goto broken; } - if (Y != not_a_year) + if (j != 0 && Y != not_a_year) { - if (!(static_cast(year::min()) <= Y && - Y <= static_cast(year::max()))) + 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 (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; - if (wd != not_a_weekday) - { - if (weekday{static_cast(wd)} != weekday(ymd)) - goto broken; - } - fds.ymd = ymd; - } - else + if (d == 0) + d = static_cast(static_cast(ymd.day())); + else if (day(d) != ymd.day()) goto broken; } + if (U != not_a_week_num && Y != not_a_year) + { + 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 && Y != not_a_year) + { + 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; + } + auto ymd = year{Y}/m/d; + if (wd != not_a_weekday && ymd.ok()) + { + if (weekday{static_cast(wd)} != weekday(ymd)) + goto broken; + } + fds.ymd = ymd; fds.tod = time_of_day(hours{h} + minutes{min}); fds.tod.s_ = detail::decimal_format_seconds{s}; + if (wd != not_a_weekday) + fds.wd = weekday{static_cast(wd)}; if (abbrev != nullptr) *abbrev = std::move(temp_abbrev); if (offset != nullptr) @@ -6208,6 +6301,106 @@ broken: is.setstate(ios_base::failbit); } +template > +void +from_stream(std::basic_istream& is, const CharT* fmt, year& y, + std::basic_string* abbrev = nullptr, + std::chrono::minutes* offset = nullptr) +{ + using namespace std; + using namespace std::chrono; + using CT = seconds; + fields fds{}; + from_stream(is, fmt, fds, abbrev, offset); + if (!is.fail()) + y = fds.ymd.year(); +} + +template > +void +from_stream(std::basic_istream& is, const CharT* fmt, month& m, + std::basic_string* abbrev = nullptr, + std::chrono::minutes* offset = nullptr) +{ + using namespace std; + using namespace std::chrono; + using CT = seconds; + fields fds{}; + from_stream(is, fmt, fds, abbrev, offset); + if (!fds.ymd.month().ok()) + is.setstate(ios::failbit); + if (!is.fail()) + m = fds.ymd.month(); +} + +template > +void +from_stream(std::basic_istream& is, const CharT* fmt, day& d, + std::basic_string* abbrev = nullptr, + std::chrono::minutes* offset = nullptr) +{ + using namespace std; + using namespace std::chrono; + using CT = seconds; + fields fds{}; + from_stream(is, fmt, fds, abbrev, offset); + if (!fds.ymd.day().ok()) + is.setstate(ios::failbit); + if (!is.fail()) + d = fds.ymd.day(); +} + +template > +void +from_stream(std::basic_istream& is, const CharT* fmt, weekday& wd, + std::basic_string* abbrev = nullptr, + std::chrono::minutes* offset = nullptr) +{ + using namespace std; + using namespace std::chrono; + using CT = seconds; + fields fds{}; + from_stream(is, fmt, fds, abbrev, offset); + if (!fds.wd.ok()) + is.setstate(ios::failbit); + if (!is.fail()) + wd = fds.wd; +} + +template > +void +from_stream(std::basic_istream& is, const CharT* fmt, year_month& ym, + std::basic_string* abbrev = nullptr, + std::chrono::minutes* offset = nullptr) +{ + using namespace std; + using namespace std::chrono; + using CT = seconds; + fields fds{}; + from_stream(is, fmt, fds, abbrev, offset); + if (!fds.ymd.month().ok()) + is.setstate(ios::failbit); + if (!is.fail()) + ym = fds.ymd.year()/fds.ymd.month(); +} + +template > +void +from_stream(std::basic_istream& is, const CharT* fmt, month_day& md, + std::basic_string* abbrev = nullptr, + std::chrono::minutes* offset = nullptr) +{ + using namespace std; + using namespace std::chrono; + using CT = seconds; + fields fds{}; + from_stream(is, fmt, fds, abbrev, offset); + if (!fds.ymd.month().ok() || !fds.ymd.day().ok()) + is.setstate(ios::failbit); + if (!is.fail()) + md = fds.ymd.month()/fds.ymd.day(); +} + template > void from_stream(std::basic_istream& is, const CharT* fmt, diff --git a/test/date_test/format/misc.pass.cpp b/test/date_test/format/misc.pass.cpp new file mode 100644 index 0000000..06deb24 --- /dev/null +++ b/test/date_test/format/misc.pass.cpp @@ -0,0 +1,52 @@ +// 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. + +#include "date.h" +#include +#include + +template +void +test(const std::string& in_fmt, const std::string& input, + const std::string& out_fmt, const std::string& output) +{ + using namespace date; + std::istringstream in{input}; + T t; + in >> parse(in_fmt, t); + assert(!in.fail()); + auto s = format(out_fmt, t); + assert(s == output); +} + +int +main() +{ + using namespace date; + test("%Y", "2017", "%Y", "2017"); + test("%m", "3", "%m", "03"); + test("%d", "25", "%d", "25"); + test("%Y-%m", "2017-03", "%Y-%m", "2017-03"); + test("%y%m", "1703", "%Y-%m", "2017-03"); + test("%m/%d", "3/25", "%m/%d", "03/25"); + test("%w", "3", "%w", "3"); +}