Search code examples
c++datec++-chronofmt

fmt with Howard Hinnant's date: why "{}" from fmt::to_string? Best practice for fmt and date?


TL;DR: I am implementing custom formatter for fmt and Howard Hinnant's date library. With date::year_month_day d{};, why does fmt::to_string(d) return "{}" while fmt::format("{}", d) works fine, returning "0000-00-00"? Example on godbolt.


I'm using date library and trying to switch from iostreams to fmt. I need to format date::year_month_day in YYYY-MM-DD format so I wrote a custom formatter (template specialization of fmt::formatter for date::year_month_day):

template<>
struct fmt::formatter<date::year_month_day> {
    template<typename ParseContext>
    auto parse(ParseContext & ctx) { return ctx.begin(); }

    template<typename FormatContext>
    auto format(const date::year_month_day & ymd, FormatContext & ctx) -> decltype(ctx.out()) {
        const int year = (int)ymd.year();
        const unsigned month = (unsigned)ymd.month();
        const unsigned day = (unsigned)ymd.day();
        return fmt::format_to(ctx.out(), "{:04}-{:02}-{:02}", year, month, day);
    }
};

Complete minimal working example, with two working examples and one failing, is available on godbolt.

Using that custom formatter, fmt::format("{}", date::year_month_day{}) works as expected, returning "0000-00-00". fmt::to_string(date::year_month_day{}) is supposed to return the same result, but returns "{}" instead.

What is the best way of formatting date::year_month_day and other types from date with fmt, including fmt::to_string()? Why do I get that "{}"?

I can't use compiler with C++20 date support yet: as far as I know, there are no complete implementations of calendar and timezone parts of C++20's std::chrono as of June 2020.

Update: using custom formatter for custom empty struct works fine, giving the same result for format("{}", s) and to_string(s), see examples on the godbolt link above. So there must be something special about date::year_month_day which breaks fmt::to_string().


Solution

  • This is an unfortunate effect of the argument-dependent lookup: fmt::to_string calls format which ends up being resolved to date::format instead of fmt::format. The fix is to fully qualify the call to format in fmt::to_string since it depends on a user-defined type.

    Update: this has been fixed in https://github.com/fmtlib/fmt/commit/2cac8a9d2e36cd920a9a60ec5b776c187e843fbb.