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

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

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

Priority: 3

View all issues with C++20 status.

Discussion:

`class leap` (which is expected to be renamed by P1981R0 to `leap_second`) defined in 27.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 27.11.8 [time.zone.leap] to specify the value of leap seconds in addition to their insertion date, and update wording and examples in 27.7 [time.clock] and 27.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 27.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 number sum 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 27.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 number sum 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 27.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 behind shifts 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 27.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 behind shifts 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 27.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 27.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 27.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 27.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 number sum 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 27.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 number sum 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 count sum.

3. Modify 27.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 behind shifts 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 27.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 behind shifts 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 27.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 27.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 27.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")`.