mirror of
https://github.com/fmtlib/fmt.git
synced 2025-02-05 00:40:12 +00:00
Implement more time specifiers
This commit is contained in:
parent
0835f1ba3b
commit
322b2594e0
@ -10,12 +10,11 @@
|
|||||||
|
|
||||||
#include "format.h"
|
#include "format.h"
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
|
#include <locale>
|
||||||
|
|
||||||
#ifndef FMT_USE_CHRONO
|
#if FMT_HAS_INCLUDE(<chrono>)
|
||||||
# define FMT_USE_CHRONO 0
|
|
||||||
#endif
|
|
||||||
#if FMT_USE_CHRONO
|
|
||||||
# include <chrono>
|
# include <chrono>
|
||||||
|
# include <sstream>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
@ -30,6 +29,11 @@ inline null<> localtime_s(...) { return null<>(); }
|
|||||||
inline null<> gmtime_r(...) { return null<>(); }
|
inline null<> gmtime_r(...) { return null<>(); }
|
||||||
inline null<> gmtime_s(...) { return null<>(); }
|
inline null<> gmtime_s(...) { return null<>(); }
|
||||||
|
|
||||||
|
enum class numeric_system {
|
||||||
|
standard,
|
||||||
|
alternative
|
||||||
|
};
|
||||||
|
|
||||||
// Parses a put_time-like format string and invokes handler actions.
|
// Parses a put_time-like format string and invokes handler actions.
|
||||||
template <typename Char, typename Handler>
|
template <typename Char, typename Handler>
|
||||||
FMT_CONSTEXPR const Char *parse_chrono_format(
|
FMT_CONSTEXPR const Char *parse_chrono_format(
|
||||||
@ -44,11 +48,13 @@ FMT_CONSTEXPR const Char *parse_chrono_format(
|
|||||||
}
|
}
|
||||||
if (begin != ptr)
|
if (begin != ptr)
|
||||||
handler.on_text(begin, ptr);
|
handler.on_text(begin, ptr);
|
||||||
c = *++ptr;
|
++ptr; // consume '%'
|
||||||
begin = ptr;
|
if (ptr == end)
|
||||||
|
throw format_error("invalid format");
|
||||||
|
c = *ptr++;
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case '%':
|
case '%':
|
||||||
handler.on_text(ptr, ptr + 1);
|
handler.on_text(ptr - 1, ptr);
|
||||||
break;
|
break;
|
||||||
// Day of the week:
|
// Day of the week:
|
||||||
case 'a':
|
case 'a':
|
||||||
@ -64,18 +70,48 @@ FMT_CONSTEXPR const Char *parse_chrono_format(
|
|||||||
handler.on_dec1_weekday();
|
handler.on_dec1_weekday();
|
||||||
break;
|
break;
|
||||||
// Month:
|
// Month:
|
||||||
case 'b': case 'h':
|
case 'b':
|
||||||
handler.on_abbr_month();
|
handler.on_abbr_month();
|
||||||
break;
|
break;
|
||||||
case 'B':
|
case 'B':
|
||||||
handler.on_full_month();
|
handler.on_full_month();
|
||||||
break;
|
break;
|
||||||
// Hour, minute, second:
|
// Hour, minute, second:
|
||||||
|
case 'H':
|
||||||
|
handler.on_24_hour(numeric_system::standard);
|
||||||
|
break;
|
||||||
|
case 'I':
|
||||||
|
handler.on_12_hour(numeric_system::standard);
|
||||||
|
break;
|
||||||
|
case 'M':
|
||||||
|
handler.on_minute(numeric_system::standard);
|
||||||
|
break;
|
||||||
case 'S':
|
case 'S':
|
||||||
handler.on_second();
|
handler.on_second(numeric_system::standard);
|
||||||
|
break;
|
||||||
|
// Alternative numeric system:
|
||||||
|
case 'O':
|
||||||
|
if (ptr == end)
|
||||||
|
throw format_error("invalid format");
|
||||||
|
c = *ptr++;
|
||||||
|
switch (c) {
|
||||||
|
case 'H':
|
||||||
|
handler.on_24_hour(numeric_system::alternative);
|
||||||
|
break;
|
||||||
|
case 'I':
|
||||||
|
handler.on_12_hour(numeric_system::alternative);
|
||||||
|
break;
|
||||||
|
case 'M':
|
||||||
|
handler.on_minute(numeric_system::alternative);
|
||||||
|
break;
|
||||||
|
case 'S':
|
||||||
|
handler.on_second(numeric_system::alternative);
|
||||||
|
break;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
// TODO: parse more format specifiers
|
// TODO: parse more format specifiers
|
||||||
}
|
}
|
||||||
|
begin = ptr;
|
||||||
}
|
}
|
||||||
if (begin != ptr)
|
if (begin != ptr)
|
||||||
handler.on_text(begin, ptr);
|
handler.on_text(begin, ptr);
|
||||||
@ -91,20 +127,27 @@ struct chrono_format_checker {
|
|||||||
void on_dec1_weekday() {}
|
void on_dec1_weekday() {}
|
||||||
void on_abbr_month() {}
|
void on_abbr_month() {}
|
||||||
void on_full_month() {}
|
void on_full_month() {}
|
||||||
void on_second() {}
|
void on_24_hour(numeric_system) {}
|
||||||
|
void on_12_hour(numeric_system) {}
|
||||||
|
void on_minute(numeric_system) {}
|
||||||
|
void on_second(numeric_system) {}
|
||||||
};
|
};
|
||||||
} // namespace internal
|
} // namespace internal
|
||||||
|
|
||||||
#if FMT_USE_CHRONO
|
#ifdef __cpp_lib_chrono
|
||||||
namespace internal {
|
namespace internal {
|
||||||
|
|
||||||
template <typename OutputIt, typename Char>
|
template <typename FormatContext>
|
||||||
struct chrono_formatter {
|
struct chrono_formatter {
|
||||||
OutputIt out;
|
FormatContext &context;
|
||||||
|
typename FormatContext::iterator out;
|
||||||
std::chrono::seconds s;
|
std::chrono::seconds s;
|
||||||
std::chrono::milliseconds ms;
|
std::chrono::milliseconds ms;
|
||||||
|
|
||||||
explicit chrono_formatter(OutputIt o) : out(o) {}
|
using char_type = typename FormatContext::char_type;
|
||||||
|
|
||||||
|
explicit chrono_formatter(FormatContext &ctx)
|
||||||
|
: context(ctx), out(ctx.out()) {}
|
||||||
|
|
||||||
template <typename Int>
|
template <typename Int>
|
||||||
void write(Int value, int width) {
|
void write(Int value, int width) {
|
||||||
@ -113,10 +156,13 @@ struct chrono_formatter {
|
|||||||
auto num_digits = internal::count_digits(n);
|
auto num_digits = internal::count_digits(n);
|
||||||
if (width > num_digits)
|
if (width > num_digits)
|
||||||
out = std::fill_n(out, width - num_digits, '0');
|
out = std::fill_n(out, width - num_digits, '0');
|
||||||
out = format_decimal<Char>(out, n, num_digits);
|
out = format_decimal<char_type>(out, n, num_digits);
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_text(const char_type *begin, const char_type *end) {
|
||||||
|
std::copy(begin, end, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
void on_text(const Char *, const Char *) {}
|
|
||||||
void on_abbr_weekday() {}
|
void on_abbr_weekday() {}
|
||||||
void on_full_weekday() {}
|
void on_full_weekday() {}
|
||||||
void on_dec0_weekday() {}
|
void on_dec0_weekday() {}
|
||||||
@ -124,8 +170,34 @@ struct chrono_formatter {
|
|||||||
void on_abbr_month() {}
|
void on_abbr_month() {}
|
||||||
void on_full_month() {}
|
void on_full_month() {}
|
||||||
|
|
||||||
void on_second() {
|
void on_24_hour(numeric_system ns) {
|
||||||
write(s.count(), 2);
|
auto hour = (s.count() / 3600) % 24;
|
||||||
|
if (ns == numeric_system::standard)
|
||||||
|
return write(hour, 2);
|
||||||
|
auto locale = context.locale().template get<std::locale>();
|
||||||
|
auto &facet = std::use_facet<std::time_put<char_type>>(locale);
|
||||||
|
std::basic_ostringstream<char_type> os;
|
||||||
|
os.imbue(locale);
|
||||||
|
auto time = tm();
|
||||||
|
time.tm_hour = hour;
|
||||||
|
const char format[] = {'%', 'O', 'H'};
|
||||||
|
facet.put(os, os, ' ', &time, format, format + sizeof(format));
|
||||||
|
auto str = os.str();
|
||||||
|
std::copy(str.begin(), str.end(), out);
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_12_hour(numeric_system) {
|
||||||
|
auto hour = (s.count() / 3600) % 12;
|
||||||
|
write(hour > 0 ? hour : 12, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_minute(numeric_system) {
|
||||||
|
auto minute = s.count() / 60;
|
||||||
|
write(minute % 60, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_second(numeric_system) {
|
||||||
|
write(s.count() % 60, 2);
|
||||||
if (ms != std::chrono::milliseconds()) {
|
if (ms != std::chrono::milliseconds()) {
|
||||||
*out++ = '.';
|
*out++ = '.';
|
||||||
write(ms.count(), 3);
|
write(ms.count(), 3);
|
||||||
@ -150,14 +222,14 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
|||||||
template <typename FormatContext>
|
template <typename FormatContext>
|
||||||
auto format(const Duration &d, FormatContext &ctx)
|
auto format(const Duration &d, FormatContext &ctx)
|
||||||
-> decltype(ctx.out()) {
|
-> decltype(ctx.out()) {
|
||||||
internal::chrono_formatter<decltype(ctx.out()), Char> f(ctx.out());
|
internal::chrono_formatter<FormatContext> f(ctx);
|
||||||
f.s = std::chrono::duration_cast<std::chrono::seconds>(d);
|
f.s = std::chrono::duration_cast<std::chrono::seconds>(d);
|
||||||
f.ms = std::chrono::duration_cast<std::chrono::milliseconds>(d - f.s);
|
f.ms = std::chrono::duration_cast<std::chrono::milliseconds>(d - f.s);
|
||||||
parse_chrono_format(format_str.begin(), format_str.end(), f);
|
parse_chrono_format(format_str.begin(), format_str.end(), f);
|
||||||
return f.out;
|
return f.out;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
#endif // FMT_USE_CHRONO
|
#endif // __cpp_lib_chrono
|
||||||
|
|
||||||
// Thread-safe replacement for std::localtime
|
// Thread-safe replacement for std::localtime
|
||||||
inline std::tm localtime(std::time_t time) {
|
inline std::tm localtime(std::time_t time) {
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
template struct internal::basic_data<void>;
|
template struct internal::basic_data<void>;
|
||||||
template FMT_API internal::locale_ref::locale_ref(const std::locale &loc);
|
template FMT_API internal::locale_ref::locale_ref(const std::locale &loc);
|
||||||
|
template FMT_API std::locale internal::locale_ref::get<std::locale>() const;
|
||||||
|
|
||||||
// Explicit instantiations for char.
|
// Explicit instantiations for char.
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "gmock.h"
|
#include "gmock.h"
|
||||||
|
#include "fmt/locale.h"
|
||||||
#include "fmt/time.h"
|
#include "fmt/time.h"
|
||||||
|
|
||||||
TEST(TimeTest, Format) {
|
TEST(TimeTest, Format) {
|
||||||
@ -58,9 +59,46 @@ TEST(TimeTest, GMTime) {
|
|||||||
EXPECT_TRUE(EqualTime(tm, fmt::gmtime(t)));
|
EXPECT_TRUE(EqualTime(tm, fmt::gmtime(t)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#if FMT_USE_CHRONO
|
#ifdef __cpp_lib_chrono
|
||||||
TEST(TimeTest, Chrono) {
|
TEST(TimeTest, Chrono) {
|
||||||
|
EXPECT_EQ("00", fmt::format("{:%S}", std::chrono::seconds(0)));
|
||||||
|
EXPECT_EQ("00", fmt::format("{:%S}", std::chrono::seconds(60)));
|
||||||
EXPECT_EQ("42", fmt::format("{:%S}", std::chrono::seconds(42)));
|
EXPECT_EQ("42", fmt::format("{:%S}", std::chrono::seconds(42)));
|
||||||
EXPECT_EQ("01.234", fmt::format("{:%S}", std::chrono::milliseconds(1234)));
|
EXPECT_EQ("01.234", fmt::format("{:%S}", std::chrono::milliseconds(1234)));
|
||||||
|
EXPECT_EQ("00", fmt::format("{:%M}", std::chrono::minutes(0)));
|
||||||
|
EXPECT_EQ("00", fmt::format("{:%M}", std::chrono::minutes(60)));
|
||||||
|
EXPECT_EQ("42", fmt::format("{:%M}", std::chrono::minutes(42)));
|
||||||
|
EXPECT_EQ("01", fmt::format("{:%M}", std::chrono::seconds(61)));
|
||||||
|
EXPECT_EQ("00", fmt::format("{:%H}", std::chrono::hours(0)));
|
||||||
|
EXPECT_EQ("00", fmt::format("{:%H}", std::chrono::hours(24)));
|
||||||
|
EXPECT_EQ("14", fmt::format("{:%H}", std::chrono::hours(14)));
|
||||||
|
EXPECT_EQ("01", fmt::format("{:%H}", std::chrono::minutes(61)));
|
||||||
|
EXPECT_EQ("12", fmt::format("{:%I}", std::chrono::hours(0)));
|
||||||
|
EXPECT_EQ("12", fmt::format("{:%I}", std::chrono::hours(12)));
|
||||||
|
EXPECT_EQ("12", fmt::format("{:%I}", std::chrono::hours(24)));
|
||||||
|
EXPECT_EQ("04", fmt::format("{:%I}", std::chrono::hours(4)));
|
||||||
|
EXPECT_EQ("02", fmt::format("{:%I}", std::chrono::hours(14)));
|
||||||
|
EXPECT_EQ("03:25:45",
|
||||||
|
fmt::format("{:%H:%M:%S}", std::chrono::seconds(12345)));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TimeTest, ChronoLocale) {
|
||||||
|
const char *loc_name = "ja_JP.utf8";
|
||||||
|
bool has_locale = false;
|
||||||
|
std::locale loc;
|
||||||
|
try {
|
||||||
|
loc = std::locale(loc_name);
|
||||||
|
has_locale = true;
|
||||||
|
} catch (const std::runtime_error &) {}
|
||||||
|
if (!has_locale) {
|
||||||
|
fmt::print("{} locale is missing.\n", loc_name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::ostringstream os;
|
||||||
|
os.imbue(loc);
|
||||||
|
auto time = std::tm();
|
||||||
|
time.tm_hour = 14;
|
||||||
|
os << std::put_time(&time, "%OH");
|
||||||
|
EXPECT_EQ(os.str(), fmt::format(loc, "{:%OH}", std::chrono::hours(14)));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
Loading…
x
Reference in New Issue
Block a user