3359. <chrono> leap second support should allow for negative leap seconds

Section: 30.11.8 [time.zone.leap] Status: C++20 Submitter: Asher Dunn Opened: 2019-12-16 Last modified: 2021-02-25

Priority: 3

View all other issues in [time.zone.leap].

View all issues with C++20 status.

Discussion:

class leap (which is expected to be renamed by P1981R0 to leap_second) defined in 30.11.8 [time.zone.leap] should include support for both positive leap seconds (23:59:60 added to UTC at a specified time) and negative leap seconds (23:59:59 removed from UTC at a specified time). While only positive leap seconds have been inserted to date, the definition of UTC allows for both.

Update 30.11.8 [time.zone.leap] to specify the value of leap seconds in addition to their insertion date, and update wording and examples in 30.7 [time.clock] and 30.12 [time.format] that involve leap seconds to account for both types of leap second.

[2020-01 Priority set to 3 after review on the reflector.]

Previous resolution [SUPERSEDED]:

This wording is relative to N4842.

  1. Modify 30.7.3.2 [time.clock.utc.members] as indicated:

    template<class Duration>
      static sys_time<common_type_t<Duration, seconds>>
        to_sys(const utc_time<Duration>& u);
    

    -2- Returns: A sys_time t, such that from_sys(t) == u if such a mapping exists. Otherwise u represents a time_point during a positive leap second insertion, the conversion counts that leap second as not inserted, and the last representable value of sys_time prior to the insertion of the leap second is returned.

    template<class Duration>
      static utc_time<common_type_t<Duration, seconds>>
        from_sys(const sys_time<Duration>& t);
    

    -3- Returns: A utc_time u, such that u.time_since_epoch() - t.time_since_epoch() is equal to the numbersum of leap seconds that were inserted between t and 1970-01-01. If t is exactly the date of leap second insertion, then the conversion counts that leap second as inserted.

    […]

  2. Modify 30.7.3.3 [time.clock.utc.nonmembers] as indicated:

    template<class Duration>
      leap_second_info get_leap_second_info(const utc_time<Duration>& ut);
    

    -6- Returns: A leap_second_info where is_leap_second is true if ut is during a positive leap second insertion, and otherwise false. elapsed is the numbersum of leap seconds between 1970-01-01 and ut. If is_leap_second is true, the leap second referred to by ut is included in the count.

  3. Modify 30.7.4.1 [time.clock.tai.overview] as indicated:

    -1- The clock tai_clock measures seconds since 1958-01-01 00:00:00 and is offset 10s ahead of UTC at this date. That is, 1958-01-01 00:00:00 TAI is equivalent to 1957-12-31 23:59:50 UTC. Leap seconds are not inserted into TAI. Therefore every time a leap second is inserted into UTC, UTC falls another second behindshifts another second with respect to TAI. For example by 2000-01-01 there had been 22 positive and 0 negative leap seconds inserted so 2000-01-01 00:00:00 UTC is equivalent to 2000-01-01 00:00:32 TAI (22s plus the initial 10s offset).

  4. Modify 30.7.5.1 [time.clock.gps.overview] as indicated:

    -1- The clock gps_clock measures seconds since the first Sunday of January, 1980 00:00:00 UTC. Leap seconds are not inserted into GPS. Therefore every time a leap second is inserted into UTC, UTC falls another second behindshifts another second with respect to GPS. Aside from the offset from 1958y/January/1 to 1980y/January/Sunday[1], GPS is behind TAI by 19s due to the 10s offset between 1958 and 1970 and the additional 9 leap seconds inserted between 1970 and 1980.

  5. Modify 30.11.8.1 [time.zone.leap.overview] as indicated:

    namespace std::chrono {
      class leap {
      public:
        leap(const leap&) = default;
        leap& operator=(const leap&) = default;
    
        // unspecified additional constructors
    
        constexpr sys_seconds date() const noexcept;
        constexpr seconds value() const noexcept;
      };
    }
    

    -1- Objects of type leap representing the date and value of the leap second insertions are constructed and stored in the time zone database when initialized.

    -2- [Example:

    for (auto& l : get_tzdb().leaps)
      if (l <= 2018y/March/17d)
        cout << l.date() << ": " << l.value() << '\n';
    

    Produces the output:

    1972-07-01 00:00:00: 1s
    1973-01-01 00:00:00: 1s
    1974-01-01 00:00:00: 1s
    1975-01-01 00:00:00: 1s
    1976-01-01 00:00:00: 1s
    1977-01-01 00:00:00: 1s
    1978-01-01 00:00:00: 1s
    1979-01-01 00:00:00: 1s
    1980-01-01 00:00:00: 1s
    1981-07-01 00:00:00: 1s
    1982-07-01 00:00:00: 1s
    1983-07-01 00:00:00: 1s
    1985-07-01 00:00:00: 1s
    1988-01-01 00:00:00: 1s
    1990-01-01 00:00:00: 1s
    1991-01-01 00:00:00: 1s
    1992-07-01 00:00:00: 1s
    1993-07-01 00:00:00: 1s
    1994-07-01 00:00:00: 1s
    1996-01-01 00:00:00: 1s
    1997-07-01 00:00:00: 1s
    1999-01-01 00:00:00: 1s
    2006-01-01 00:00:00: 1s
    2009-01-01 00:00:00: 1s
    2012-07-01 00:00:00: 1s
    2015-07-01 00:00:00: 1s
    2017-01-01 00:00:00: 1s
    

    end example]

  6. Modify 30.11.8.2 [time.zone.leap.members] as indicated:

    constexpr sys_seconds date() const noexcept;
    

    -1- Returns: The date and time at which the leap second was inserted.

    constexpr seconds value() const noexcept;
    

    -?- Returns: The value of the leap second. Always +1s to indicate a positive leap second or -1s to indicate a negative leap second. All leap seconds inserted up through 2017 were positive leap seconds.

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

    template<class Duration, class charT>
      struct formatter<chrono::utc_time<Duration>, charT>;
    

    -7- Remarks: If %Z is used, it is replaced with STATICALLY-WIDEN<charT>("UTC"). If %z (or a modified variant of %z) is used, an offset of 0min is formatted. If the argument represents a time during a positive leap second insertion, and if a seconds field is formatted, the integral portion of that format is STATICALLY-WIDEN<charT>("60").

[2020-02-14; Prague]

LWG Review. Some wording improvements have been made and lead to revised wording.

Proposed resolution:

This wording is relative to N4849.

  1. Modify 30.7.3.2 [time.clock.utc.members] as indicated:

    template<class Duration>
      static sys_time<common_type_t<Duration, seconds>>
        to_sys(const utc_time<Duration>& u);
    

    -2- Returns: A sys_time t, such that from_sys(t) == u if such a mapping exists. Otherwise u represents a time_point during a positive leap second insertion, the conversion counts that leap second as not inserted, and the last representable value of sys_time prior to the insertion of the leap second is returned.

    template<class Duration>
      static utc_time<common_type_t<Duration, seconds>>
        from_sys(const sys_time<Duration>& t);
    

    -3- Returns: A utc_time u, such that u.time_since_epoch() - t.time_since_epoch() is equal to the numbersum of leap seconds that were inserted between t and 1970-01-01. If t is exactly the date of leap second insertion, then the conversion counts that leap second as inserted.

    […]

  2. Modify 30.7.3.3 [time.clock.utc.nonmembers] as indicated:

    template<class Duration>
      leap_second_info get_leap_second_info(const utc_time<Duration>& ut);
    

    -6- Returns: A leap_second_info, lsi, where lsi.is_leap_second is true if ut is during a positive leap second insertion, and otherwise false. lsi.elapsed is the numbersum of leap seconds between 1970-01-01 and ut. If lsi.is_leap_second is true, the leap second referred to by ut is included in the countsum.

  3. Modify 30.7.4.1 [time.clock.tai.overview] as indicated:

    -1- The clock tai_clock measures seconds since 1958-01-01 00:00:00 and is offset 10s ahead of UTC at this date. That is, 1958-01-01 00:00:00 TAI is equivalent to 1957-12-31 23:59:50 UTC. Leap seconds are not inserted into TAI. Therefore every time a leap second is inserted into UTC, UTC falls another second behindshifts another second with respect to TAI. For example by 2000-01-01 there had been 22 positive and 0 negative leap seconds inserted so 2000-01-01 00:00:00 UTC is equivalent to 2000-01-01 00:00:32 TAI (22s plus the initial 10s offset).

  4. Modify 30.7.5.1 [time.clock.gps.overview] as indicated:

    -1- The clock gps_clock measures seconds since the first Sunday of January, 1980 00:00:00 UTC. Leap seconds are not inserted into GPS. Therefore every time a leap second is inserted into UTC, UTC falls another second behindshifts another second with respect to GPS. Aside from the offset from 1958y/January/1 to 1980y/January/Sunday[1], GPS is behind TAI by 19s due to the 10s offset between 1958 and 1970 and the additional 9 leap seconds inserted between 1970 and 1980.

  5. Modify 30.11.8.1 [time.zone.leap.overview] as indicated:

    namespace std::chrono {
      class leap {
      public:
        leap(const leap&) = default;
        leap& operator=(const leap&) = default;
    
        // unspecified additional constructors
    
        constexpr sys_seconds date() const noexcept;
        constexpr seconds value() const noexcept;
      };
    }
    

    -1- Objects of type leap representing the date and value of the leap second insertions are constructed and stored in the time zone database when initialized.

    -2- [Example:

    for (auto& l : get_tzdb().leaps)
      if (l <= 2018y/March/17d)
        cout << l.date() << ": " << l.value() << '\n';
    

    Produces the output:

    1972-07-01 00:00:00: 1s
    1973-01-01 00:00:00: 1s
    1974-01-01 00:00:00: 1s
    1975-01-01 00:00:00: 1s
    1976-01-01 00:00:00: 1s
    1977-01-01 00:00:00: 1s
    1978-01-01 00:00:00: 1s
    1979-01-01 00:00:00: 1s
    1980-01-01 00:00:00: 1s
    1981-07-01 00:00:00: 1s
    1982-07-01 00:00:00: 1s
    1983-07-01 00:00:00: 1s
    1985-07-01 00:00:00: 1s
    1988-01-01 00:00:00: 1s
    1990-01-01 00:00:00: 1s
    1991-01-01 00:00:00: 1s
    1992-07-01 00:00:00: 1s
    1993-07-01 00:00:00: 1s
    1994-07-01 00:00:00: 1s
    1996-01-01 00:00:00: 1s
    1997-07-01 00:00:00: 1s
    1999-01-01 00:00:00: 1s
    2006-01-01 00:00:00: 1s
    2009-01-01 00:00:00: 1s
    2012-07-01 00:00:00: 1s
    2015-07-01 00:00:00: 1s
    2017-01-01 00:00:00: 1s
    

    end example]

  6. Modify 30.11.8.2 [time.zone.leap.members] as indicated:

    constexpr sys_seconds date() const noexcept;
    

    -1- Returns: The date and time at which the leap second was inserted.

    constexpr seconds value() const noexcept;
    

    -?- Returns: +1s to indicate a positive leap second or -1s to indicate a negative leap second. [Note: All leap seconds inserted up through 2019 were positive leap seconds. — end note]

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

    template<class Duration, class charT>
      struct formatter<chrono::utc_time<Duration>, charT>;
    

    -7- Remarks: If %Z is used, it is replaced with STATICALLY-WIDEN<charT>("UTC"). If %z (or a modified variant of %z) is used, an offset of 0min is formatted. If the argument represents a time during a positive leap second insertion, and if a seconds field is formatted, the integral portion of that format is STATICALLY-WIDEN<charT>("60").