This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.

3856. Unclear which conversion specifiers are valid for each chrono type

Section: 29.12 [time.format] Status: New Submitter: Tam S. B. Opened: 2023-01-14 Last modified: 2023-05-30

Priority: 3

View other active issues in [time.format].

View all other issues in [time.format].

View all issues with New status.

Discussion:

29.12 [time.format]/3:

If the formatted object does not contain the information the conversion specifier refers to, an exception of type format_error is thrown.
29.12 [time.format]/6:
If the type being formatted does not contain the information that the format flag needs, an exception of type format_error is thrown.

It's unclear how to determine if a type contain the needed information, and implementations diverge.

For example, consider

#include <chrono>
#include <format>

auto f(std::chrono::month_day_last mdl) {
  return std::format("{:%j}", mdl);
}

Both libstdc++ and libc++ produce a compile-time error, claiming that the argument does not contain the information, while MSVC STL throws format_error at run time unless mdl is `January/last`, in which case the function returns "031".

Another interesting case is format("{:%d}", mdl) where the value can be printed for all months except February, which requires a year to know how many days it has.

A related example from Jonathan Wakely:

std::chrono::weekday_indexed wdi(Monday, 7);  // 7th Monday in the month
assert( ! wdi.ok() );
assert( wdi.weekday().ok() );
std::format("{:%a}", wdi);

For %a the required information is "a valid weekday", and arguably this does contain a valid weekday. On the other hand, there's no 7th Monday, so this isn't valid. Should this throw or not?

This was discussed by LWG and Howard Hinnant summarized the intended behaviour as:

"The intention of 29.12 [time.format]/6 is to address things like formatting a duration with %F. A duration doesn't contain the calendrical information that %F requires (year, month, day). Ditto for using %a (weekday name) with a year. It is meant to address mismatching types and flags, and not meant to address values."

The type chrono::weekday does contain the information needed to print a weekday. A specific invalid value doesn't change that. The type chrono::month_day_last does not contain the information needed to print the day of the year. A specific value where the day can be known doesn't change that. The day of month is more interesting, and might need more discussion.

Jonathan proposed adding more examples to clarify the intention that only the type matters, and not the value. There is some redundancy between p3 and p6. Referring to "the formatted object" in p3 seems unclear. Saying "type" as in p6 is better. But p6 refers to "format flag" which is not defined, whereas p3 uses "conversion specifier" (defined at the start of that paragraph). The two uses of "flag" in p6 look like remnants from the earlier chrono::format feature that was replaced by integration with std::format.

[2023-02-01; Reflector poll]

Set priority to 3 after reflector poll.

[2023-05-30; Jonathan adds wording]

Proposed resolution:

This wording is relative to N4950.

  1. Modify 29.12 [time.format] as indicated:

    -3- Each conversion specifier conversion-spec is replaced by appropriate characters as described in Table 101 ([tab:time.format.spec]); the formats specified in ISO 8601:2004 shall be used where so described. Some of the conversion specifiers depend on the formatting locale. If the string literal encoding is a Unicode encoding form and the locale is among an implementation-defined set of locales, each replacement that depends on the locale is performed as if the replacement character sequence is converted to the string literal encoding. If the formatted objecttype being formatted does not contain the information the conversion specifier refers to, an exception of type format_error is thrown.

    [Example ?: A duration does not contain enough information to format as a weekday using %w. A weekday_indexed does contain enough information to format using %w and Monday[7] can be formatted as "1" even though Monday[7].ok() is false. A month_day does not contain enough information to format as the day of the year using %j, even when the month() part is January. — end example]

    However, if a flag refers to a "time of day" (e.g., %H, %I, %p, etc.), then a specialization of duration is interpreted as the time of day elapsed since midnight.

    -4- The result of formatting a std::chrono::duration instance holding a negative value, or an hh_mm_ss object h for which h.is_negative() is true, is equivalent to the output of the corresponding positive value, with a STATICALLY-WIDEN<charT>("-") character sequence placed before the replacement of the initial conversion specifier.

    [Example 1:

    
    cout << format("{:%T}", -10'000s);          // prints: -02:46:40
    cout << format("{:%H:%M:%S}", -10'000s);    // prints: -02:46:40
    cout << format("minutes {:%M, hours %H, seconds %S}", -10'000s);
                                                // prints: minutes -46, hours 02, seconds 40
    
    end example]

    -5- Unless explicitly requested, the result of formatting a chrono type does not contain time zone abbreviation and time zone offset information. If the information is available, the conversion specifiers %Z and %z will format this information (respectively).

    [Note 1: If the information is not available and a %Z or %z conversion specifier appears in the chrono-format-spec, an exception of type format_error is thrown, as described above. — end note]

    -6- If the type being formatted does not contain the information that the format flag needs, an exception of type format_error is thrown, as described above.

    [Example 2: A duration does not contain enough information to format as a weekday. — end example]

    However, if a flag refers to a "time of day" (e.g., %H, %I, %p, etc.), then a specialization of duration is interpreted as the time of day elapsed since midnight.