Boost logo

Boost Users :

Subject: Re: [Boost-users] Boost.DateTime and leap year/leap seconds
From: Howard Hinnant (howard.hinnant_at_[hidden])
Date: 2015-07-22 15:29:09


I’ve been following this thread with interest.

On Jul 22, 2015, at 8:59 AM, Auer, Jens <jens.auer_at_[hidden]> wrote:
> Reading the documentation I think that will not be correct when there were leap seconds.

Correct. As Jeff said earlier, leap second support is not in the current library, but might be a feature in a future boost date-time 2.0.

If you are using a C++11 or C++14 compiler, there is a library that will handle this problem with leap seconds taken into account for you:

http://howardhinnant.github.io/tz.html

I doubt you can use this library as the space industry equates up-to-date compilers with increased risk. However I mention it, as it is open source and perhaps you can crib some of its ideas onto the current boost date-time library.

In a nutshell, this C++11/14 library handles leap seconds along the lines of what Jeff suggested. Though in this case it creates a std::chrono - compatible clock which is leap second aware:

    class utc_clock
    {
    public:
        using duration = std::chrono::system_clock::duration;
        using rep = duration::rep;
        using period = duration::period;
        using time_point = std::chrono::time_point<utc_clock>;
        static constexpr bool is_steady = true;

        static time_point now() noexcept;

        template <class Duration>
            static
            std::chrono::time_point<utc_clock,
                typename std::common_type<Duration, std::chrono::seconds>::type>
            sys_to_utc(std::chrono::time_point<std::chrono::system_clock, Duration> t);

        template <class Duration>
            static
            std::chrono::time_point<std::chrono::system_clock,
                typename std::common_type<Duration, std::chrono::seconds>::type>
            utc_to_sys(std::chrono::time_point<utc_clock, Duration> t);
    };

and includes conversions to and from std::chrono::system_clock::time_point (which like boost date-time is based on Unix time). These conversions are simply lookups into an ordered list of leap second transitions, resulting in the addition or subtraction of the proper number of leap seconds.

This could be used like so:

    struct CCSDS
    {
        std::uint16_t days;
        std::uint32_t ms;
        std::uint16_t us;
    };

    CCSDS
    to_CCSDS(date::year y, std::chrono::microseconds us)
    {
        using namespace date;
        using namespace std::chrono;
        auto utc = utc_clock::sys_to_utc(day_point(y/jan/1)) + us;
        auto sys = utc_clock::utc_to_sys(utc);
        auto dp = floor<days>(sys);
        auto d = dp - day_point(1958_y/jan/1);
        us = utc - utc_clock::sys_to_utc(dp);
        auto ms = duration_cast<milliseconds>(us);
        us -= ms;
        return {static_cast<std::uint16_t>(d.count()),
                static_cast<std::uint32_t>(ms.count()),
                static_cast<std::uint16_t>(us.count())};
    }

The variable utc holds the “year + us” as a time point with microseconds precision. This time point counts microseconds, including leap seconds, since 1970-01-01 00:00:00 UTC.

The next step is find when the day started that is associated with utc. To do this one must convert utc back to “Unix time”, and then truncate that time point to a precision of days, resulting in the variable dp. dp is a count of days since 1970-01-01. Since your epoch is 1958-01-01, this is taken into account in creating “d”, the first value needed in your return type.

Now the number of microseconds since the start of the day needs to be computed. The start of the day, dp, is converted back into the leap-second aware system, and subtracted from the microsecond time point: utc. The variable us is reused to hold “microseconds since midnight”. Now it is a simple computation to split this into milliseconds since midnight, and microseconds since the last millisecond.

This has all been tested with:

    void
    test_to_CCSDS()
    {
        using namespace std::chrono;
        using namespace date;
        for (auto d = days{179}; d <= days{181}; ++d)
        {
            auto start = d + 86399s + 999999us + 0us;
            for (auto us = start; us <= start + 3s; us += 1s)
            {
                auto t = to_CCSDS(2015_y, us);
                std::cout << t.days << ' ' << t.ms << ' ' << t.us << '\n';
            }
            std::cout << '\n';
        }
    }

which yields:

    20998 86399999 999
    20999 999 999
    20999 1999 999
    20999 2999 999

    20999 86399999 999
    20999 86400999 999
    21000 999 999
    21000 1999 999

    21000 86398999 999
    21000 86399999 999
    21001 999 999
    21001 1999 999

Interpretation: On 2015-06-29 23:59:59.999999 we are 1us away from the next day. 1s later we are well into 2015-06-30.

24hrs later, we see a different pattern as the leap second is inserted. The last microsecond of the day is 1s later at 2015-06030 23:59:60.999999.

24hrs later we are back to days with only 86400s.

Assuming you have some conversion data to test against, this will hopefully match up with those test cases.

Howard


Boost-users list run by williamkempf at hotmail.com, kalb at libertysoft.com, bjorn.karlsson at readsoft.com, gregod at cs.rpi.edu, wekempf at cox.net