fmtlib / fmt

A modern formatting library
https://fmt.dev
Other
20.74k stars 2.49k forks source link

[Question] `chrono_specs` no longer allow left hand side literals since fmt 10? #3445

Closed tt4g closed 1 year ago

tt4g commented 1 year ago

I ask this question to clarify the cause of the spdlog library issue (gabime/spdlog#2735).

After updating the fmt version from 9.1.0 to 10.0.0 in spdlog library, the following code now causes "invalid format" error. fmt 10 is not yet available for selection in Compiler Explorer, so I am checking the behavior with the latest sources.

Compiler Explorer

#include <fmt/chrono.h>
#include <ctime>
#include <time.h>

int main() {
    std::time_t now_t = ::time(nullptr);
    std::tm tm{};
    ::localtime_r(&now_t, &tm);

    auto format = fmt::format("{{:{}}}", "example-%Y-%m-%d.log");
    fmt::print(format, tm);
}

Tip The last format string will be fmt::print("{:example-%Y-%m-%d.log}", tm);.

I could not find the relevant spec change in the version 10 CHANGELOG. However, looking at the chrono_specs, it appears that left hand side literals were not allowed.

https://fmt.dev/10.0.0/syntax.html#chrono-format-specifications

   chrono_format_spec: [[`fill`]`align`][`width`]["." `precision`][`chrono_specs`]
   chrono_specs: [`chrono_specs`] `conversion_spec` | `chrono_specs` `literal_char`
   conversion_spec: "%" [`modifier`] `chrono_type`
   literal_char: <a character other than '{', '}' or '%'>
   modifier: "E" | "O"
   chrono_type: "a" | "A" | "b" | "B" | "c" | "C" | "d" | "D" | "e" | "F" |
              : "g" | "G" | "h" | "H" | "I" | "j" | "m" | "M" | "n" | "p" |
              : "q" | "Q" | "r" | "R" | "S" | "t" | "T" | "u" | "U" | "V" |
              : "w" | "W" | "x" | "X" | "y" | "Y" | "z" | "Z" | "%"

Change the code as follows and the error will no longer occur with fmt 10.

 #include <fmt/chrono.h>
 #include <ctime>
 #include <time.h>

 int main() {
     std::time_t now_t = ::time(nullptr);
     std::tm tm{};
     ::localtime_r(&now_t, &tm);

-    auto format = fmt::format("{{:{}}}", "example-%Y-%m-%d.log");
+    auto format = fmt::format("{{:{}}}", "%Y-%m-%d.log");
     fmt::print(format, tm);
 }

In fmt 9.1.0 and earlier, literals could appear on the left hand side of chrono_specs. However, I am aware that the behavior from fmt 10 is correct. Is this correct?

vitaut commented 1 year ago

This was a bug in an earlier version that didn't match the standard spec (missing format string validation). It is mentioned in the changelog although somewhat briefly:

Improved validation of format specifiers for std::chrono::duration (https://github.com/fmtlib/fmt/issues/3219, https://github.com/fmtlib/fmt/pull/3232).

tt4g commented 1 year ago

Thanks for letting me know.