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

4124. Cannot format zoned_time with resolution coarser than seconds

Section: 30.12 [time.format] Status: Voting Submitter: Jonathan Wakely Opened: 2024-07-26 Last modified: 2024-11-19

Priority: Not Prioritized

View other active issues in [time.format].

View all other issues in [time.format].

View all issues with Voting status.

Discussion:

The std::formatter<std::chrono::zoned_time<Duration, TimeZonePtr>> specialization calls tp.get_local_time() for the object it passes to its base class' format function. But get_local_time() does not return a local_time<Duration>, it returns local_time<common_type_t<Duration, seconds>>. The base class' format function is only defined for local_time<Duration>. That means this is ill-formed, even though the static assert passes:

using namespace std::chrono;
  static_assert( std::formattable<zoned_time<minutes>, char> );
  zoned_time<minutes> zt;
  (void) std::format("{}", zt); // error: cannot convert local_time<seconds> to local_time<minutes>

Additionally, it's not specified what output you should get for:

std::format("{}", local_time_format(zt.get_local_time()));
30.12 [time.format] p7 says it's formatted as if by streaming to an ostringstream, but there is no operator<< for local-time-format-t. Presumably it should give the same result as operator<< for a zoned_time, i.e. "{:L%F %T %Z}" with padding adjustments etc.

The proposed resolution below has been implemented in libstdc++.

[2024-08-02; Reflector poll]

Set status to Tentatively Ready after seven votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N4986.

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

    template<classDuration, class charT>
      struct formatter<chrono::local-time-format-t<Duration>, charT>;
    

    -17- Let f be a locale-time-format-t<Duration> object passed to formatter::format.

    -18- Remarks: If the chrono-specs is omitted, the result is equivalent to using %F %T %Z as the chrono-specs. If %Z is used, it is replaced with *f.abbrev if f.abbrev is not a null pointer value. If %Z is used and f.abbrev is a null pointer value, an exception of type format_error is thrown. If %z (or a modified variant of %z) is used, it is formatted with the value of *f.offset_sec if f.offset_sec is not a null pointer value. If %z (or a modified variant of %z) is used and f.offset_sec is a null pointer value, then an exception of type format_error is thrown.

      template<class Duration, class TimeZonePtr, class charT>
      struct formatter<chrono::zoned_time<Duration, TimeZonePtr>, charT>
          : formatter<chrono::local-time-format-t<common_type_t<Duration, seconds>>, charT> {
        template<class FormatContext>
          typename FormatContext::iterator
          format(const chrono::zoned_time<Duration, TimeZonePtr>& tp, FormatContext& ctx) const;
      };
    
    template<class FormatContext>
      typename FormatContext::iterator
        format(const chrono::zoned_time<Duration, TimeZonePtr>& tp, FormatContext& ctx) const;
    

    -19- Effects: Equivalent to:

    sys_info info = tp.get_info();
    return formatter<chrono::local-time-format-t<common_type_t<Duration, seconds>>, charT>::
             format({tp.get_local_time(), &info.abbrev, &info.offset}, ctx);