libstdc++: Add std::format support to <chrono>

This adds the operator<< overloads and std::formatter specializations
required by C++20 so that <chrono> types can be written to ostreams and
printed with std::format.

libstdc++-v3/ChangeLog:

	* include/Makefile.am: Add new header.
	* include/Makefile.in: Regenerate.
	* include/std/chrono (operator<<): Move to new header.
	(nonexistent_local_time::_M_make_what_str): Define correctly.
	(ambiguous_local_time::_M_make_what_str): Likewise.
	* include/bits/chrono_io.h: New file.
	* src/c++20/tzdb.cc (operator<<(ostream&, const Rule&)): Use
	new ostream output for month and weekday types.
	* testsuite/20_util/duration/io.cc: Test std::format support.
	* testsuite/std/time/exceptions.cc: Check what() strings.
	* testsuite/std/time/syn_c++20.cc: Uncomment local_time_format.
	* testsuite/std/time/time_zone/get_info_local.cc: Enable check
	for formatted output of local_info objects.
	* testsuite/std/time/clock/file/io.cc: New test.
	* testsuite/std/time/clock/gps/io.cc: New test.
	* testsuite/std/time/clock/system/io.cc: New test.
	* testsuite/std/time/clock/tai/io.cc: New test.
	* testsuite/std/time/clock/utc/io.cc: New test.
	* testsuite/std/time/day/io.cc: New test.
	* testsuite/std/time/format.cc: New test.
	* testsuite/std/time/hh_mm_ss/io.cc: New test.
	* testsuite/std/time/month/io.cc: New test.
	* testsuite/std/time/weekday/io.cc: New test.
	* testsuite/std/time/year/io.cc: New test.
	* testsuite/std/time/year_month_day/io.cc: New test.
This commit is contained in:
Jonathan Wakely 2022-12-22 01:29:22 +00:00
parent 9247402a29
commit f99b94865f
21 changed files with 3465 additions and 149 deletions

View file

@ -175,6 +175,7 @@ bits_headers = \
${bits_srcdir}/char_traits.h \
${bits_srcdir}/charconv.h \
${bits_srcdir}/chrono.h \
${bits_srcdir}/chrono_io.h \
${bits_srcdir}/codecvt.h \
${bits_srcdir}/cow_string.h \
${bits_srcdir}/deque.tcc \

View file

@ -528,6 +528,7 @@ bits_freestanding = \
@GLIBCXX_HOSTED_TRUE@ ${bits_srcdir}/char_traits.h \
@GLIBCXX_HOSTED_TRUE@ ${bits_srcdir}/charconv.h \
@GLIBCXX_HOSTED_TRUE@ ${bits_srcdir}/chrono.h \
@GLIBCXX_HOSTED_TRUE@ ${bits_srcdir}/chrono_io.h \
@GLIBCXX_HOSTED_TRUE@ ${bits_srcdir}/codecvt.h \
@GLIBCXX_HOSTED_TRUE@ ${bits_srcdir}/cow_string.h \
@GLIBCXX_HOSTED_TRUE@ ${bits_srcdir}/deque.tcc \

File diff suppressed because it is too large Load diff

View file

@ -45,7 +45,6 @@
# include <sstream>
# include <string>
# include <vector>
# include <bits/charconv.h> // __to_chars_len, __to_chars_10_impl
# include <bits/stl_algo.h> // upper_bound
# include <bits/shared_ptr.h>
# include <bits/unique_ptr.h>
@ -627,8 +626,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
friend constexpr year_month_day
operator/(const year_month& __ym, const day& __d) noexcept;
// TODO: Implement operator<<, to_stream, from_stream.
};
// MONTH
@ -751,8 +748,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
friend constexpr month_weekday_last
operator/(const weekday_last& __wdl, const month& __m) noexcept;
// TODO: Implement operator<<, to_stream, from_stream.
};
inline constexpr month January{1};
@ -929,8 +924,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
friend constexpr year_month_weekday_last
operator/(const month_weekday_last& __mwdl, const year& __y) noexcept;
// TODO: Implement operator<<, to_stream, from_stream.
};
// WEEKDAY
@ -1052,8 +1045,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
auto __n = static_cast<long long>(__x._M_wd) - __y._M_wd;
return days{__detail::__modulo(__n, 7)};
}
// TODO: operator<<, from_stream.
};
inline constexpr weekday Sunday{0};
@ -1110,8 +1101,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
friend constexpr year_month_weekday
operator/(const year_month& __ym, const weekday_indexed& __wdi) noexcept;
// TODO: Implement operator<<.
};
constexpr weekday_indexed
@ -1151,8 +1140,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
friend constexpr year_month_weekday_last
operator/(const year_month& __ym, const weekday_last& __wdl) noexcept;
// TODO: Implement operator<<.
};
constexpr weekday_last
@ -1224,8 +1211,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
friend constexpr year_month_day
operator/(const month_day& __md, int __y) noexcept;
// TODO: Implement operator<<, from_stream.
};
// MONTH_DAY_LAST
@ -1278,8 +1263,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
friend constexpr year_month_day_last
operator/(const month_day_last& __mdl, int __y) noexcept;
// TODO: Implement operator<<.
};
// MONTH_WEEKDAY
@ -1339,8 +1322,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
friend constexpr year_month_weekday
operator/(const month_weekday& __mwd, int __y) noexcept;
// TODO: Implement operator<<.
};
// MONTH_WEEKDAY_LAST
@ -1401,8 +1382,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
friend constexpr year_month_weekday_last
operator/(const month_weekday_last& __mwdl, int __y) noexcept;
// TODO: Implement operator<<.
};
// YEAR_MONTH
@ -1544,8 +1523,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
friend constexpr year_month_day_last
operator/(const year_month& __ym, last_spec) noexcept;
// TODO: Implement operator<<, from_stream.
};
// YEAR_MONTH_DAY
@ -1697,8 +1674,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
friend constexpr year_month_day
operator/(const month_day& __md, int __y) noexcept
{ return chrono::year(__y) / __md; }
// TODO: Implement operator<<, from_stream.
};
// Construct from days since 1970/01/01.
@ -1928,8 +1903,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
friend constexpr year_month_day_last
operator/(const chrono::month_day_last& __mdl, int __y) noexcept
{ return chrono::year(__y) / __mdl; }
// TODO: Implement operator<<.
};
// year_month_day ctor from year_month_day_last
@ -2118,8 +2091,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
friend constexpr year_month_weekday
operator/(const month_weekday& __mwd, int __y) noexcept
{ return chrono::year(__y) / __mwd; }
// TODO: Implement operator<<.
};
// YEAR_MONTH_WEEKDAY_LAST
@ -2267,8 +2238,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
friend constexpr year_month_weekday_last
operator/(const chrono::month_weekday_last& __mwdl, int __y) noexcept
{ return chrono::year(__y) / __mwdl; }
// TODO: Implement operator<<.
};
// HH_MM_SS
@ -2284,6 +2253,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
__r *= 10;
return __r;
}
template<typename _Duration> struct __utc_leap_second;
}
/// @endcond
@ -2389,8 +2360,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
return _M_h + _M_m + _M_s + subseconds();
}
// TODO: Implement operator<<.
private:
static constexpr bool _S_is_unsigned
= __and_v<is_integral<typename _Duration::rep>,
@ -2459,8 +2428,30 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
__byte_duration<ratio<1>> _M_s{};
bool _M_is_neg{};
__subseconds<precision> _M_ss{};
template<typename> friend struct __detail::__utc_leap_second;
};
/// @cond undocumented
namespace __detail
{
// Represents a time that is within a leap second insertion.
template<typename _Duration>
struct __utc_leap_second
{
explicit
__utc_leap_second(const sys_time<_Duration>& __s)
: _M_date(chrono::floor<days>(__s)), _M_time(__s - _M_date)
{
++_M_time._M_s;
}
sys_days _M_date;
hh_mm_ss<common_type_t<_Duration, days>> _M_time;
};
}
/// @endcond
// 12/24 HOURS FUNCTIONS
constexpr bool
@ -2540,9 +2531,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
_S_make_what_str(const local_time<_Duration>& __tp,
const local_info& __i)
{
#if 1
return "local time is non-existent";
#else
std::ostringstream __os;
__os << __tp << " is in a gap between\n"
<< local_seconds(__i.first.end.time_since_epoch())
@ -2552,7 +2540,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
<< " which are both equivalent to\n"
<< __i.first.end << " UTC";
return std::move(__os).str();
#endif
}
};
@ -2571,9 +2558,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
_S_make_what_str(const local_time<_Duration>& __tp,
const local_info& __i)
{
#if 1
return "local time is ambiguous";
#else
std::ostringstream __os;
__os << __tp << " is ambiguous. It could be\n"
<< __tp << ' ' << __i.first.abbrev << " == "
@ -2581,7 +2565,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
<< __tp << ' ' << __i.second.abbrev << " == "
<< __tp - __i.second.offset << " UTC";
return std::move(__os).str();
#endif
}
};
@ -3329,106 +3312,15 @@ namespace __detail
/// @}
} // inline namespace chrono_literals
} // inline namespace literals
namespace chrono
{
/// @addtogroup chrono
/// @{
/// @cond undocumented
namespace __detail
{
template<typename _Period>
const char*
__units_suffix_misc(char* __buf, size_t __n) noexcept
{
namespace __tc = std::__detail;
char* __p = __buf;
__p[0] = '[';
unsigned __nlen = __tc::__to_chars_len((uintmax_t)_Period::num);
__tc::__to_chars_10_impl(__p + 1, __nlen, (uintmax_t)_Period::num);
__p += 1 + __nlen;
if constexpr (_Period::den != 1)
{
__p[0] = '/';
unsigned __dlen = __tc::__to_chars_len((uintmax_t)_Period::den);
__tc::__to_chars_10_impl(__p + 1, __dlen, (uintmax_t)_Period::den);
__p += 1 + __dlen;
}
__p[0] = ']';
__p[1] = 's';
__p[2] = '\0';
return __buf;
}
template<typename _Period, typename _CharT>
auto
__units_suffix(char* __buf, size_t __n) noexcept
{
#define _GLIBCXX_UNITS_SUFFIX(period, suffix) \
if constexpr (is_same_v<_Period, period>) \
{ \
if constexpr (is_same_v<_CharT, wchar_t>) \
return L##suffix; \
else \
return suffix; \
} \
else
_GLIBCXX_UNITS_SUFFIX(atto, "as")
_GLIBCXX_UNITS_SUFFIX(femto, "fs")
_GLIBCXX_UNITS_SUFFIX(pico, "ps")
_GLIBCXX_UNITS_SUFFIX(nano, "ns")
_GLIBCXX_UNITS_SUFFIX(micro, "\u00b5s")
_GLIBCXX_UNITS_SUFFIX(milli, "ms")
_GLIBCXX_UNITS_SUFFIX(centi, "cs")
_GLIBCXX_UNITS_SUFFIX(deci, "ds")
_GLIBCXX_UNITS_SUFFIX(ratio<1>, "s")
_GLIBCXX_UNITS_SUFFIX(deca, "das")
_GLIBCXX_UNITS_SUFFIX(hecto, "hs")
_GLIBCXX_UNITS_SUFFIX(kilo, "ks")
_GLIBCXX_UNITS_SUFFIX(mega, "Ms")
_GLIBCXX_UNITS_SUFFIX(giga, "Gs")
_GLIBCXX_UNITS_SUFFIX(tera, "Ts")
_GLIBCXX_UNITS_SUFFIX(tera, "Ts")
_GLIBCXX_UNITS_SUFFIX(peta, "Ps")
_GLIBCXX_UNITS_SUFFIX(exa, "Es")
_GLIBCXX_UNITS_SUFFIX(ratio<60>, "min")
_GLIBCXX_UNITS_SUFFIX(ratio<3600>, "h")
_GLIBCXX_UNITS_SUFFIX(ratio<86400>, "d")
#undef _GLIBCXX_UNITS_SUFFIX
return __detail::__units_suffix_misc<_Period>(__buf, __n);
}
} // namespace __detail
/// @endcond
template<typename _CharT, typename _Traits,
typename _Rep, typename _Period>
inline basic_ostream<_CharT, _Traits>&
operator<<(std::basic_ostream<_CharT, _Traits>& __os,
const duration<_Rep, _Period>& __d)
{
using period = typename _Period::type;
char __buf[sizeof("[/]s") + 2 * numeric_limits<intmax_t>::digits10];
std::basic_ostringstream<_CharT, _Traits> __s;
__s.flags(__os.flags());
__s.imbue(__os.getloc());
__s.precision(__os.precision());
__s << __d.count();
__s << __detail::__units_suffix<period, _CharT>(__buf, sizeof(__buf));
__os << std::move(__s).str();
return __os;
}
// TODO: from_stream for duration
/// @} group chrono
} // namespace chrono
#endif // C++20
_GLIBCXX_END_NAMESPACE_VERSION
} // namespace std
#if __cplusplus >= 202002L
# include <bits/chrono_io.h>
#endif
#endif // C++11
#endif //_GLIBCXX_CHRONO

View file

@ -511,27 +511,25 @@ namespace std::chrono
friend ostream& operator<<(ostream& out, const Rule& r)
{
out << "Rule " << r.name << ' ' << (int)r.from << ' ' << (int)r.to
<< ' ' << (unsigned)r.when.day.get_month() << ' ';
<< ' ' << r.when.day.get_month() << ' ';
switch (r.when.day.kind)
{
case on_day::DayOfMonth:
out << (unsigned)r.when.day.get_day();
break;
case on_day::LastWeekday:
out << "last" << weekday(r.when.day.day_of_week).c_encoding();
out << "last" << weekday(r.when.day.day_of_week);
break;
case on_day::LessEq:
out << weekday(r.when.day.day_of_week).c_encoding() << " <= "
out << weekday(r.when.day.day_of_week) << " <= "
<< r.when.day.day_of_month;
break;
case on_day::GreaterEq:
out << weekday(r.when.day.day_of_week).c_encoding() << " >= "
out << weekday(r.when.day.day_of_week) << " >= "
<< r.when.day.day_of_month;
break;
}
hh_mm_ss hms(r.when.time);
out << ' ' << hms.hours().count() << ':' << hms.minutes().count()
<< ':' << hms.seconds().count() << "wusd"[r.when.indicator];
out << ' ' << hh_mm_ss(r.when.time) << "wusd"[r.when.indicator];
out << ' ' << r.save.count();
if (!r.letters.empty())
out << ' ' << r.letters;

View file

@ -47,8 +47,56 @@ test02()
#endif
}
void
test_format()
{
using namespace std::chrono_literals;
auto s = std::format("{} {}", 1h + 23min + 45s, -42min);
VERIFY( s == "5025s -42min" );
s = std::format("{:%j} {:%j} {:%j}", 1h + 23min + 45s, 75h, -99h);
VERIFY( s == "0 3 -4" );
s = std::format("{:%T = %H:%M:%S}", 1h + 23min + 45s);
VERIFY( s == "01:23:45 = 01:23:45" );
s = std::format("{:%Q} {:%q} {:%Q%q}", 6min + 1s, 44min, -22h);
VERIFY( s == "361 min -22h" );
std::wstring ws = std::format(L"{:%Q%q}", 81s);
VERIFY( ws == L"81s" );
// Only print '-' on numeric fields for negative durations:
s = std::format("{:%Q} {:%q} {:%q%Q}", -21h, -20h, -19h);
VERIFY( s == "-21 h h-19" );
s = std::format("{:%p} {:%p%H}", -2h, -13h);
VERIFY( s == "AM PM-13" );
s = std::format("{:%t} {:%t%M}", -2h, -123s);
VERIFY( s == "\t \t-02" );
std::string_view specs = "aAbBcCdDeFgGhHIjmMpqQrRSTuUVwWxXyYzZ";
std::string_view my_specs = "HIjMpqQrRSTX";
for (char c : specs)
{
char fmt[] = { '{', ':', '%', c, '}' };
try
{
(void) std::vformat(std::string_view(fmt, 5), std::make_format_args(1s));
// The call above should throw for any conversion-spec not in my_specs:
VERIFY(my_specs.find(c) != my_specs.npos);
}
catch (const std::format_error& e)
{
VERIFY(my_specs.find(c) == my_specs.npos);
std::string_view s = e.what();
// Libstdc++-specific message:
VERIFY(s.find("format argument does not contain the information "
"required by the chrono-specs") != s.npos);
}
}
}
int main()
{
test01();
test02();
test_format();
// TODO: test_parse();
}

View file

@ -0,0 +1,23 @@
// { dg-options "-std=gnu++20" }
// { dg-do run { target c++20 } }
#include <chrono>
#include <sstream>
#include <testsuite_hooks.h>
void
test_ostream()
{
using namespace std::chrono;
file_time<file_clock::duration> t = file_clock::now();
std::ostringstream ss1, ss2;
ss1 << floor<seconds>(t);
ss2 << floor<seconds>(clock_cast<system_clock>(t));
VERIFY( ss1.str() == ss2.str() );
}
int main()
{
test_ostream();
}

View file

@ -0,0 +1,24 @@
// { dg-options "-std=gnu++20" }
// { dg-do run { target c++20 } }
#include <chrono>
#include <format>
#include <testsuite_hooks.h>
void
test01()
{
using std::format;
using namespace std::chrono;
auto st = sys_days{2000y/January/1};
auto gt = clock_cast<gps_clock>(st);
auto s = format("{0:%F %T %Z} == {1:%F %T %Z}", st, gt);
VERIFY( s == "2000-01-01 00:00:00 UTC == 2000-01-01 00:00:13 GPS" );
}
int main()
{
test01();
}

View file

@ -0,0 +1,72 @@
// { dg-options "-std=gnu++20" }
// { dg-do run { target c++20 } }
#include <chrono>
#include <sstream>
#include <testsuite_hooks.h>
void
test_ostream()
{
using namespace std::chrono;
std::stringstream ss;
ss << sys_seconds{0s} << '\n'; // 1970-01-01 00:00:00
ss << sys_seconds{946'684'800s} << '\n'; // 2000-01-01 00:00:00
ss << sys_seconds{946'688'523s} << '\n'; // 2000-01-01 01:02:03
std::string s1, s2, s3;
std::getline(ss, s1);
std::getline(ss, s2);
std::getline(ss, s3);
VERIFY( s1 == "1970-01-01 00:00:00" );
VERIFY( s2 == "2000-01-01 00:00:00" );
VERIFY( s3 == "2000-01-01 01:02:03" );
}
template<typename T>
concept stream_insertable
= requires (std::ostream& out, const T& t) { out << t; };
// operator<<(ostream&, const sys_time<D>&) is constrained to not
// allow floating-point types or periods of days or greater.
using fp_sys_time = std::chrono::sys_time<std::chrono::duration<float>>;
static_assert( !stream_insertable<fp_sys_time> );
// But there is an overload for sys_days.
static_assert( stream_insertable<std::chrono::sys_days> );
void
test_format()
{
using namespace std::chrono_literals;
std::chrono::sys_time<std::chrono::milliseconds> t(1671470785708ms);
// Every conversion specifier is valid for a sys_time except %q and %Q.
std::string s = std::format("{:%a | %A | %b | %B | %c"
" | %C | %d | %D | %e | %F | %g | %G | %h"
" | %H | %I | %j | %m | %M | %p | %r | %R"
" | %S | %T | %u | %U | %V | %w | %W | %x"
" | %X | %y | %Y | %z | %Z}", t);
VERIFY( s == "Mon | Monday | Dec | December | Mon Dec 19 17:26:25.708 2022"
" | 20 | 19 | 12/19/22 | 19 | 2022-12-19 | 22 | 2022 | Dec"
" | 17 | 05 | 353 | 12 | 26 | PM | 05:26:25.708 PM | 17:26"
" | 25.708 | 17:26:25.708 | 1 | 51 | 51 | 1 | 51 | 12/19/22"
" | 17:26:25.708 | 22 | 2022 | +0000 | UTC" );
std::wstring ws = std::format(L"{:%a | %A | %b | %B | %c"
" | %C | %d | %D | %e | %F | %g | %G | %h"
" | %H | %I | %j | %m | %M | %p | %r | %R"
" | %S | %T | %u | %U | %V | %w | %W | %x"
" | %X | %y | %Y | %z | %Z}", t);
VERIFY( ws == L"Mon | Monday | Dec | December | Mon Dec 19 17:26:25.708 2022"
" | 20 | 19 | 12/19/22 | 19 | 2022-12-19 | 22 | 2022 | Dec"
" | 17 | 05 | 353 | 12 | 26 | PM | 05:26:25.708 PM | 17:26"
" | 25.708 | 17:26:25.708 | 1 | 51 | 51 | 1 | 51 | 12/19/22"
" | 17:26:25.708 | 22 | 2022 | +0000 | UTC" );
}
int main()
{
test_ostream();
test_format();
}

View file

@ -0,0 +1,24 @@
// { dg-options "-std=gnu++20" }
// { dg-do run { target c++20 } }
#include <chrono>
#include <format>
#include <testsuite_hooks.h>
void
test01()
{
using std::format;
using namespace std::chrono;
auto st = sys_days{2000y/January/1};
auto tt = clock_cast<tai_clock>(st);
auto s = format("{0:%F %T %Z} == {1:%F %T %Z}", st, tt);
VERIFY( s == "2000-01-01 00:00:00 UTC == 2000-01-01 00:00:32 TAI" );
}
int main()
{
test01();
}

View file

@ -0,0 +1,120 @@
// { dg-options "-std=gnu++20" }
// { dg-do run { target c++20 } }
#include <chrono>
#include <sstream>
#include <testsuite_hooks.h>
void
test_ostream()
{
using std::ostringstream;
using namespace std::chrono;
auto t = sys_days{July/1/2015} - 500ms;
auto u = clock_cast<utc_clock>(t);
std::string_view results[] = {
"2015-06-30 23:59:59.500 UTC",
"2015-06-30 23:59:59.750 UTC",
"2015-06-30 23:59:60.000 UTC",
"2015-06-30 23:59:60.250 UTC",
"2015-06-30 23:59:60.500 UTC",
"2015-06-30 23:59:60.750 UTC",
"2015-07-01 00:00:00.000 UTC",
"2015-07-01 00:00:00.250 UTC",
};
for (auto result : results)
{
ostringstream out;
out << u << " UTC";
VERIFY( out.str() == result );
u += 250ms;
}
}
void
test_format()
{
using namespace std::chrono_literals;
std::chrono::utc_time<std::chrono::milliseconds> t(1671470812708ms);
// Every conversion specifier is valid for a utc_time except %q and %Q.
std::string_view specs = "aAbBcCdDeFgGhHIjmMpqQrRSTuUVwWxXyYzZ";
std::string_view badspecs = "qQ";
std::ostringstream ss;
std::wostringstream wss;
for (char c : specs)
{
char fmt[] = { '{', ':', '%', c, '}' };
try
{
ss << std::vformat(std::string_view(fmt, 5),
std::make_format_args(t));
ss << " | ";
// The call above should throw for any conversion-spec in badspecs:
VERIFY(badspecs.find(c) == badspecs.npos);
}
catch (const std::format_error& e)
{
VERIFY(badspecs.find(c) != badspecs.npos);
std::string_view s = e.what();
// Libstdc++-specific message:
VERIFY(s.find("format argument does not contain the information "
"required by the chrono-specs") != s.npos);
}
wchar_t wfmt[] = { L'{', L':', L'%', c, L'}' };
try
{
wss << std::vformat(std::wstring_view(wfmt, 5),
std::make_wformat_args(t));
wss << L" | ";
// The call above should throw for any conversion-spec in badspecs:
VERIFY(badspecs.find(c) == badspecs.npos);
}
catch (const std::format_error& e)
{
VERIFY(badspecs.find(c) != badspecs.npos);
std::string_view s = e.what();
// Libstdc++-specific message:
VERIFY(s.find("format argument does not contain the information "
"required by the chrono-specs") != s.npos);
}
}
std::string s = ss.str();
VERIFY( s == "Mon | Monday | Dec | December | Mon Dec 19 17:26:25.708 2022"
" | 20 | 19 | 12/19/22 | 19 | 2022-12-19 | 22 | 2022 | Dec"
" | 17 | 05 | 353 | 12 | 26 | PM | 05:26:25.708 PM | 17:26"
" | 25.708 | 17:26:25.708 | 1 | 51 | 51 | 1 | 51 | 12/19/22"
" | 17:26:25.708 | 22 | 2022 | +0000 | UTC | " );
std::wstring ws = wss.str();
VERIFY( ws == L"Mon | Monday | Dec | December | Mon Dec 19 17:26:25.708 2022"
" | 20 | 19 | 12/19/22 | 19 | 2022-12-19 | 22 | 2022 | Dec"
" | 17 | 05 | 353 | 12 | 26 | PM | 05:26:25.708 PM | 17:26"
" | 25.708 | 17:26:25.708 | 1 | 51 | 51 | 1 | 51 | 12/19/22"
" | 17:26:25.708 | 22 | 2022 | +0000 | UTC | " );
std::chrono::utc_seconds leap(1483228800s + 26s); // 1 Jan 2017
s = std::format("{:%T}", leap - 1s);
VERIFY( s == "23:59:59" );
s = std::format("{:%T}", leap);
VERIFY( s == "23:59:60" );
s = std::format("{:%T}", leap + 10ms);
VERIFY( s == "23:59:60.010" );
s = std::format("{:%T}", leap + 1s);
VERIFY( s == "00:00:00" );
}
int main()
{
test_ostream();
test_format();
}

View file

@ -0,0 +1,75 @@
// { dg-options "-std=gnu++20" }
// { dg-do run { target c++20 } }
// { dg-require-namedlocale "fr_FR.ISO8859-15" }
#include <chrono>
#include <sstream>
#include <testsuite_hooks.h>
void
test_ostream()
{
using std::ostringstream;
using namespace std::chrono;
ostringstream ss;
ss << day(1) << ' ' << day(11) << ' ' << day(21) << ' ' << day(31)
<< ' ' << day(41);
auto s = ss.str();
VERIFY( s == "01 11 21 31 41 is not a valid day" );
ss.str("");
ss.imbue(std::locale(ISO_8859(15,fr_FR)));
ss << day(1);
VERIFY( ss.str() == "01" );
}
void
test_format()
{
using std::chrono::day;
auto s = std::format("{:%d%%%e%t}{:%d%%%e%n}", day(1), day(11));
VERIFY( s == "01% 1\t11%11\n" );
auto ws = std::format(L"{:%d%%%e%t}{:%d%%%e%n}", day(1), day(11));
VERIFY( ws == L"01% 1\t11%11\n" );
VERIFY( std::format("{} {}", day(8), day(0)) == "08 00 is not a valid day" );
s = std::format("{:%Od}", day(1));
VERIFY( s == "01" );
s = std::format(std::locale::classic(), "{:%Od}", day(1));
VERIFY( s == "01" );
s = std::format(std::locale::classic(), "{:L%Od}", day(1));
VERIFY( s == "01" );
// TODO test "{:L%Od}" with locale that has alternative numeric rep.
std::string_view specs = "aAbBcCdDeFgGhHIjmMpqQrRSTuUVwWxXyYzZ";
std::string_view my_specs = "de";
for (char c : specs)
{
char fmt[] = { '{', ':', '%', c, '}' };
try
{
(void) std::vformat(std::string_view(fmt, 5),
std::make_format_args(day(1)));
// The call above should throw for any conversion-spec not in my_specs:
VERIFY(my_specs.find(c) != my_specs.npos);
}
catch (const std::format_error& e)
{
VERIFY(my_specs.find(c) == my_specs.npos);
std::string_view s = e.what();
// Libstdc++-specific message:
VERIFY(s.find("format argument does not contain the information "
"required by the chrono-specs") != s.npos);
}
}
}
int main()
{
test_ostream();
test_format();
// TODO: test_parse();
}

View file

@ -20,7 +20,7 @@ test_nonexistent()
local_days{Sunday[2]/March/2016} + 2h + 30min};
VERIFY(false);
} catch (const nonexistent_local_time& e) {
// VERIFY( e.what() == expected );
VERIFY( e.what() == expected );
}
}
@ -38,7 +38,7 @@ test_ambiguous()
local_days{Sunday[1]/November/2016} + 1h + 30min};
VERIFY(false);
} catch (const ambiguous_local_time& e) {
// VERIFY( e.what() == expected );
VERIFY( e.what() == expected );
}
}

View file

@ -0,0 +1,117 @@
// { dg-options "-std=gnu++20" }
// { dg-do run { target c++20 } }
#include <chrono>
#include <testsuite_hooks.h>
void
test_format_strings()
{
using namespace std::chrono_literals;
// valid format strings
VERIFY( std::format("{}", 1s) == "1s" );
VERIFY( std::format("{:}", 1s) == "1s" );
VERIFY( std::format("{:L}", 1s) == "1s" );
VERIFY( std::format("{:%%%n%t}", 1s) == "%\n\t" );
VERIFY( std::format("{:L%%%n%t}", 1s) == "%\n\t" );
VERIFY( std::format("{:4%%}", 1s) == "% " );
VERIFY( std::format("{:4L%%}", 1s) == "% " );
VERIFY( std::format("{: >4}", 1s) == " 1s" );
VERIFY( std::format("{: <4}", 1s) == "1s " );
VERIFY( std::format("{: <4L}", 1s) == "1s " );
VERIFY( std::format("{: >4%%}", 1s) == " %" );
VERIFY( std::format("{: >4L%%}", 1s) == " %" );
VERIFY( std::format("{: ^4%%}", 1s) == " % " );
}
template<typename... Args>
bool
is_format_string_for(const char* str, Args&&... args)
{
try {
(void) std::vformat(str, std::make_format_args(args...));
return true;
} catch (const std::format_error&) {
return false;
}
}
void
test_bad_format_strings()
{
std::chrono::sys_seconds t{};
// literal '%' must be formatted as "%%"
VERIFY( not is_format_string_for("{:%}", t) );
// chrono-specs must start with '%'
VERIFY( not is_format_string_for("{:a%}", t) );
VERIFY( not is_format_string_for("{:La%}", t) );
// '{' not valid in chrono-specs
VERIFY( not is_format_string_for("{:%%{{%%}", t) );
// padding with leading zero not valid for chrono types
VERIFY( not is_format_string_for("{:04%T}", t) );
// precision only valid for chrono::duration types with floating-point rep.
VERIFY( not is_format_string_for("{:.4}", t) );
// unfinished format string
VERIFY( not is_format_string_for("{:", t) );
// dangling modifiers
VERIFY( not is_format_string_for("{:%E}", t) );
VERIFY( not is_format_string_for("{:%O}", t) );
// modifier not valid for conversion specifier
VERIFY( not is_format_string_for("{:%Ea}", t) );
VERIFY( not is_format_string_for("{:%Oa}", t) );
}
template<typename I>
struct move_only_iterator
{
using iterator = I;
using value_type = iterator::value_type;
using difference_type = iterator::difference_type;
using iterator_category = std::output_iterator_tag;
move_only_iterator(iterator b) : base_(b) { }
move_only_iterator(move_only_iterator&&) = default;
move_only_iterator& operator=(move_only_iterator&&) = default;
move_only_iterator& operator++() { ++base_; return *this; }
move_only_iterator operator++(int) { auto tmp = *this; ++base_; return tmp; }
decltype(auto) operator*() { return *base_; }
private:
iterator base_;
};
void
test_move_only_iterator()
{
using namespace std::chrono;
utc_seconds ut(1671543754s);
sys_seconds st(1671543727s);
std::string str;
move_only_iterator mo(std::back_inserter(str));
std::format_to(std::move(mo), "{:%F} {:%T} {:%Q}", ut, st, 1s);
VERIFY( str == "2022-12-20 13:42:07 1" );
std::vector<wchar_t> vec;
move_only_iterator wmo(std::back_inserter(vec));
std::format_to(std::move(wmo), L"{:%F} {:%T} {:%Q}", ut, st, 2s);
VERIFY( std::wstring_view(vec.data(), vec.size()) == L"2022-12-20 13:42:07 2" );
}
int main()
{
test_format_strings();
test_bad_format_strings();
test_move_only_iterator();
}

View file

@ -0,0 +1,46 @@
// { dg-options "-std=gnu++20" }
// { dg-do run { target c++20 } }
#include <chrono>
#include <sstream>
#include <testsuite_hooks.h>
void
test01()
{
using std::ostringstream;
using std::chrono::hh_mm_ss;
using namespace std::chrono_literals;
std::locale::global(std::locale::classic());
{
hh_mm_ss hms{-4083007ms};
ostringstream out;
out << hms;
VERIFY( out.str() == "-01:08:03.007" );
}
{
hh_mm_ss hms{4083007ms};
ostringstream out;
out << hms;
VERIFY( out.str() == "01:08:03.007" );
}
{
hh_mm_ss hms{65745123ms};
ostringstream out;
out << hms;
VERIFY( out.str() == "18:15:45.123" );
}
ostringstream out;
out << hh_mm_ss{65745s};
VERIFY( out.str() == "18:15:45" );
}
int main()
{
test01();
}

View file

@ -0,0 +1,98 @@
// { dg-options "-std=gnu++20" }
// { dg-do run { target c++20 } }
// { dg-require-namedlocale "fr_FR.ISO8859-15" }
#include <chrono>
#include <sstream>
#include <testsuite_hooks.h>
void
test_ostream()
{
using std::ostringstream;
using namespace std::chrono;
ostringstream ss;
for (int i = 1; i <= 12; ++i)
ss << month(i);
VERIFY( ss.str() == "JanFebMarAprMayJunJulAugSepOctNovDec" );
ss.str("");
ss << month(0) << '|' << month(13);
VERIFY( ss.str() == "0 is not a valid month|13 is not a valid month" );
ss.str("");
ss.imbue(std::locale(ISO_8859(15,fr_FR)));
ss << month(1);
VERIFY( ss.str() == "janv." );
}
void
test_format()
{
using std::chrono::month;
auto s = std::format("{:%b%%%B%t%m%n}", month(1));
VERIFY( s == "Jan%January\t01\n" );
auto ws = std::format(L"{:%b%%%B%t%m%n}", month(12));
VERIFY( ws == L"Dec%December\t12\n" );
try
{
(void) std::format("{:%b}", month(13));
VERIFY(false);
}
catch (const std::format_error&)
{
}
s = std::format("{} is OK, but {:L}", month(2), month(13));
VERIFY( s == "Feb is OK, but 13 is not a valid month" );
std::locale loc_fr(ISO_8859(15,fr_FR));
s = std::format("{:%Om}", month(1));
VERIFY( s == "01" );
s = std::format(std::locale::classic(), "{:%Om}", month(1));
VERIFY( s == "01" );
s = std::format(std::locale::classic(), "{:L%Om}", month(1));
VERIFY( s == "01" );
s = std::format(loc_fr, "{:%Om}", month(1));
VERIFY( s == "01" );
s = std::format(loc_fr, "{:L%Om}", month(1));
VERIFY( s == "01" );
// TODO test "{:L%Om}" with locale that has alternative numeric rep.
s = std::format(loc_fr, "{:%b}", month(1));
VERIFY( s == "Jan" );
s = std::format(loc_fr, "{:L%b}", month(1));
VERIFY( s == "janv." );
std::string_view specs = "aAbBcCdDeFgGhHIjmMpqQrRSTuUVwWxXyYzZ";
std::string_view my_specs = "bBhm";
for (char c : specs)
{
char fmt[] = { '{', ':', '%', c, '}' };
try
{
(void) std::vformat(std::string_view(fmt, 5),
std::make_format_args(month(1)));
// The call above should throw for any conversion-spec not in my_specs:
VERIFY(my_specs.find(c) != my_specs.npos);
}
catch (const std::format_error& e)
{
VERIFY(my_specs.find(c) == my_specs.npos);
std::string_view s = e.what();
// Libstdc++-specific message:
VERIFY(s.find("format argument does not contain the information "
"required by the chrono-specs") != s.npos);
}
}
}
int main()
{
test_ostream();
test_format();
// TODO: test_parse();
}

View file

@ -124,8 +124,7 @@ namespace __gnu_test
using std::chrono::time_zone_link;
#endif
// FIXME
// using std::chrono::local_time_format;
using std::chrono::local_time_format;
// FIXME
// using std::chrono::parse;

View file

@ -148,7 +148,6 @@ test_egypt()
VERIFY( info.second.save == 1h );
VERIFY( info.second.abbrev == "EEST" );
#if 0
std::ostringstream out;
local_seconds lt(local_days(2001y/January/1));
const local_days end(2021y/January/1);
@ -209,7 +208,6 @@ test_egypt()
[[2014-09-25 21:00:00,32767-12-31 00:00:00,02:00:00,0min,EET]]
)";
VERIFY( out.str() == expected );
#endif
}
int main()

View file

@ -0,0 +1,101 @@
// { dg-options "-std=gnu++20" }
// { dg-do run { target c++20 } }
// { dg-require-namedlocale "fr_FR.ISO8859-15" }
#include <chrono>
#include <sstream>
#include <testsuite_hooks.h>
void
test_ostream()
{
using std::ostringstream;
using namespace std::chrono;
ostringstream ss;
for (int i = 0; i <= 7; ++i)
ss << weekday(i);
VERIFY( ss.str() == "SunMonTueWedThuFriSatSun" );
ss.str("");
ss << weekday(8) << '|' << weekday(99);
VERIFY( ss.str() == "8 is not a valid weekday|99 is not a valid weekday" );
ss.str("");
ss.imbue(std::locale(ISO_8859(15,fr_FR)));
ss << weekday(6);
VERIFY( ss.str() == "sam." );
}
void
test_format()
{
using std::chrono::weekday;
auto s = std::format("{:%a%%%A%t%u%n%w}", std::chrono::Monday);
VERIFY( s == "Mon%Monday\t1\n1" );
auto ws = std::format(L"{:%a%%%A%t%u%n%w}", weekday(7));
VERIFY( ws == L"Sun%Sunday\t7\n0" );
s = std::format("{:%w}", weekday(8));
VERIFY( s == "8" );
try
{
(void) std::format("{:%a}", weekday(8));
VERIFY(false);
}
catch (const std::format_error&)
{
}
s = std::format("{} is OK, but {:L}", weekday(2), weekday(13));
VERIFY( s == "Tue is OK, but 13 is not a valid weekday" );
std::locale loc_fr(ISO_8859(15,fr_FR));
s = std::format("{:%Ow}", weekday(1));
VERIFY( s == "1" );
s = std::format(std::locale::classic(), "{:%Ow}", weekday(1));
VERIFY( s == "1" );
s = std::format(std::locale::classic(), "{:L%Ow}", weekday(1));
VERIFY( s == "1" );
s = std::format(loc_fr, "{:%Ow}", weekday(1));
VERIFY( s == "1" );
s = std::format(loc_fr, "{:L%Ow}", weekday(1));
VERIFY( s == "1" );
// TODO test "{:L%Ow}" with locale that has alternative numeric rep.
s = std::format(loc_fr, "{:%a}", weekday(1));
VERIFY( s == "Mon" );
s = std::format(loc_fr, "{:L%a}", weekday(1));
VERIFY( s == "lun." );
std::string_view specs = "aAbBcCdDeFgGhHIjmMpqQrRSTuUVwWxXyYzZ";
std::string_view my_specs = "aAuw";
for (char c : specs)
{
char fmt[] = { '{', ':', '%', c, '}' };
try
{
(void) std::vformat(std::string_view(fmt, 5),
std::make_format_args(weekday(1)));
// The call above should throw for any conversion-spec not in my_specs:
VERIFY(my_specs.find(c) != my_specs.npos);
}
catch (const std::format_error& e)
{
VERIFY(my_specs.find(c) == my_specs.npos);
std::string_view s = e.what();
// Libstdc++-specific message:
VERIFY(s.find("format argument does not contain the information "
"required by the chrono-specs") != s.npos);
}
}
}
int main()
{
test_ostream();
test_format();
// TODO: test_parse();
}

View file

@ -0,0 +1,89 @@
// { dg-options "-std=gnu++20" }
// { dg-do run { target c++20 } }
// { dg-require-namedlocale "fr_FR.ISO8859-15" }
#include <chrono>
#include <sstream>
#include <testsuite_hooks.h>
void
test_ostream()
{
using std::ostringstream;
using namespace std::chrono;
ostringstream ss;
for (int y : {-1234, -44, -1, 0, 5, 32, 325, 1066, 2022})
ss << year(y) << ' ';
VERIFY( ss.str() == "-1234 -0044 -0001 0000 0005 0032 0325 1066 2022 " );
ss.str("");
ss << year::min() << ' ' << year::max() << ' ' << --year::min();
VERIFY( ss.str() == "-32767 32767 -32768 is not a valid year" );
ss.str("");
ss.imbue(std::locale(ISO_8859(15,fr_FR)));
ss << 1789y;
VERIFY( ss.str() == "1789" );
}
void
test_format()
{
using std::chrono::year;
using namespace std::chrono_literals;
auto s = std::format("{:%y%%%Y%t%C%n}", 2022y);
VERIFY( s == "22%2022\t20\n" );
auto ws = std::format(L"{:%y%%%Y%t%C%n}", 2023y);
VERIFY( ws == L"23%2023\t20\n" );
s = std::format("{:%Y}", --year::min());
VERIFY( s == "-32768" );
s = std::format("{}", --year::min()); // formatted via ostream
VERIFY( s == "-32768 is not a valid year" );
s = std::format("{:%y} {:%y}", 1976y, -1976y);
VERIFY( s == "76 76" ); // LWG 3831
s = std::format("{0:%EC}{0:%Ey} = {0:%EY}", 1642y);
VERIFY( s == "1642 = 1642" );
s = std::format("{0:L%EC}{0:L%Ey} = {0:L%EY}", 1642y);
VERIFY( s == "1642 = 1642" );
s = std::format(std::locale::classic(), "{0:L%EC}{0:L%Ey} = {0:L%EY}", 1642y);
VERIFY( s == "1642 = 1642" );
// TODO test "{:L%EC}" with locale that has alternative era rep.
// TODO test "{:L%Ey}" with locale that has alternative year rep.
// TODO test "{:L%EY}" with locale that has alternative year rep.
// TODO test "{:L%Oy}" with locale that has alternative numeric rep.
std::string_view specs = "aAbBcCdDeFgGhHIjmMpqQrRSTuUVwWxXyYzZ";
std::string_view my_specs = "CyY";
for (char c : specs)
{
char fmt[] = { '{', ':', '%', c, '}' };
try
{
(void) std::vformat(std::string_view(fmt, 5),
std::make_format_args(year(2022)));
// The call above should throw for any conversion-spec not in my_specs:
VERIFY(my_specs.find(c) != my_specs.npos);
}
catch (const std::format_error& e)
{
VERIFY(my_specs.find(c) == my_specs.npos);
std::string_view s = e.what();
// Libstdc++-specific message:
VERIFY(s.find("format argument does not contain the information "
"required by the chrono-specs") != s.npos);
}
}
}
int main()
{
test_ostream();
test_format();
// TODO: test_parse();
}

View file

@ -0,0 +1,121 @@
// { dg-options "-std=gnu++20" }
// { dg-do run { target c++20 } }
// { dg-require-namedlocale "fr_FR.ISO8859-15" }
#include <chrono>
#include <sstream>
#include <testsuite_hooks.h>
void
test_ostream()
{
using std::ostringstream;
using namespace std::chrono;
ostringstream ss;
ss << 2022y/December/19 << ' ' << 2022y/November/31;
VERIFY( ss.str() == "2022-12-19 2022-11-31 is not a valid date" );
ss.str("");
ss.imbue(std::locale(ISO_8859(15,fr_FR)));
ss << 1789y/July/14;
VERIFY( ss.str() == "1789-07-14" );
}
void
test_format()
{
using std::chrono::year_month_day;
using std::chrono::December;
using std::chrono::January;
using namespace std::chrono_literals;
auto s = std::format("{:%y%%%Y%t%C%n%j %a %b}", 2022y/December/19);
VERIFY( s == "22%2022\t20\n353 Mon Dec" );
auto ws = std::format(L"{:%y%%%Y%t%C%n%d}", 2023y/January/32);
VERIFY( ws == L"23%2023\t20\n32" );
s = std::format("{:%F} {}", 2023y/January/32, 2023y/January/32);
VERIFY( s == "2023-01-32 2023-01-32 is not a valid date" );
s = std::format("{:%C%g-W%V-%u}", 2022y/January/1);
VERIFY( s == "2021-W52-6" );
s = std::format("{:%G-W%V-%u}", 2022y/January/3);
VERIFY( s == "2022-W01-1" );
// %U: Week number for weeks starting on Sunday
s = std::format("Day {:%w (%a) of Week %U of %Y}", 2022y/January/1);
VERIFY( s == "Day 6 (Sat) of Week 00 of 2022" );
s = std::format("Day {:%w (%a) of Week %U of %Y}", 2022y/January/2);
VERIFY( s == "Day 0 (Sun) of Week 01 of 2022" );
// %W: Week number for weeks starting on Monday
s = std::format("Day {:%u (%a) of Week %W of %Y}", 2022y/January/2);
VERIFY( s == "Day 7 (Sun) of Week 00 of 2022" );
s = std::format("Day {:%u (%a) of Week %W of %Y}", 2022y/January/3);
VERIFY( s == "Day 1 (Mon) of Week 01 of 2022" );
// %V: ISO week number (ISO 8601).
s = std::format("W{:%V}", 1977y/1/1);
VERIFY( s == "W53" );
s = std::format("W{:%V}", 1977y/1/2);
VERIFY( s == "W53" );
s = std::format("W{:%V}", 1977y/12/31);
VERIFY( s == "W52" );
s = std::format("W{:%V}", 1978y/1/1);
VERIFY( s == "W52" );
s = std::format("W{:%V}", 1978y/1/2);
VERIFY( s == "W01" );
s = std::format("W{:%V}", 1978y/12/31);
VERIFY( s == "W52" );
s = std::format("W{:%V}", 1979y/1/1);
VERIFY( s == "W01" );
s = std::format("W{:%V}", 1979y/12/30);
VERIFY( s == "W52" );
s = std::format("W{:%V}", 1979y/12/31);
VERIFY( s == "W01" );
s = std::format("W{:%V}", 1980y/1/1);
VERIFY( s == "W01" );
s = std::format("{:%x}", 2022y/December/19);
VERIFY( s == "12/19/22" );
s = std::format("{:L%x}", 2022y/December/19);
VERIFY( s == "12/19/22" );
std::locale loc_fr(ISO_8859(15,fr_FR));
s = std::format(loc_fr, "{:%x}", 2022y/December/19);
VERIFY( s == "12/19/22" );
s = std::format(loc_fr, "{:L%x}", 2022y/December/19);
VERIFY( s == "19/12/2022" );
s = std::format(loc_fr, "{}", 2022y/December/19);
VERIFY( s == "2022-12-19" );
s = std::format(loc_fr, "{:L%F}", 2022y/December/19);
VERIFY( s == "2022-12-19" );
std::string_view specs = "aAbBcCdDeFgGhHIjmMpqQrRSTuUVwWxXyYzZ";
std::string_view my_specs = "aAbBCdDeFgGhjmuUVwWxyY";
for (char c : specs)
{
char fmt[] = { '{', ':', '%', c, '}' };
try
{
(void) std::vformat(std::string_view(fmt, 5),
std::make_format_args(2022y/December/19));
// The call above should throw for any conversion-spec not in my_specs:
VERIFY(my_specs.find(c) != my_specs.npos);
}
catch (const std::format_error& e)
{
VERIFY(my_specs.find(c) == my_specs.npos);
std::string_view s = e.what();
// Libstdc++-specific message:
VERIFY(s.find("format argument does not contain the information "
"required by the chrono-specs") != s.npos);
}
}
}
int main()
{
test_ostream();
test_format();
// TODO: test_parse();
}