Search code examples
c++datetimeembedded-linuxmicrocontrollerepoch

Does gmtime() function take into account leap years?


I am working on embedded linux and the system/controller time is kept in the form of milliseconds since 1/1/1970. I am trying to use gmtime but can't get accurate results. Any example to convert this time (milliseconds) to real time hours:minutes:seconds:day:month will be helpful.


Solution

  • You could use civil_from_days which is derived and explained in detail in Howard Hinnant's chrono-Compatible Low-Level Date Algorithms. This function takes a count of days since 1/1/1970 and converts it into a {y, m, d} field. Once you have that done, you just have to deal with subtracting off the number of days from your milliseconds timestamp to get milliseconds since midnight and then break that down into h:M:s.ms.

    Here's complete code:

    #include <iostream>
    #include <iomanip>
    #include <cstdint>
    
    int
    main()
    {
        using namespace std;
        int64_t t = 1490285505123;       // milliseconds since epoch
        int32_t z = (t >= 0 ? t : t - (1000*86400-1))/(1000*86400); // days since epoch
        t -= z * (1000LL * 86400);       // milliseconds since midnight
        z += 719468;
        int32_t era = (z >= 0 ? z : z - 146096) / 146097;
        int32_t doe = z - era * 146097;
        int32_t yoe = (doe - doe/1460 + doe/36524 - doe/146096) / 365;
        int32_t y = yoe + era * 400;
        int32_t doy = doe - (365*yoe + yoe/4 - yoe/100);
        int32_t m = (5*doy + 2)/153;
        int32_t d = doy - (153*m + 2)/5 + 1;  // day
        m += m < 10 ? 3 : -9;                 // month
        y += m <= 2;                          // year
        int32_t h = t / (1000 * 3600);        // hour
        t -= h * (1000 * 3600);
        int32_t M = t / (1000 * 60);          // minute
        t -= M * (1000 * 60);
        int32_t s = t / 1000;                 // second
        int32_t ms = t - s * 1000;            // ms
        cout.fill('0');
        cout << setw(4) << y << '-' << setw(2) << m << '-' << setw(2) << d
                        << ' ' << setw(2) << h << ':' << setw(2) << M
                        << ':' << setw(2) << s << '.' << setw(3) << ms << '\n';
    }
    

    Just as an example I've used as input 1490285505123ms, and the output is:

    2017-03-23 16:11:45.123
    

    This takes leap years into account. It does not take leap seconds into account. It is very unlikely that your embedded linux system/controller does either, so it would be incorrect to try and do so.

    The above algorithm has a very large range of validity for t:

    -5877641-06-23 00:00:00.000 <= t <= 5880010-09-09 23:59:59.999
    

    (+/- 5.8 million years)

    If you don't mind restricting the lower limit of t to 0000-03-01 00:00:00.000 then you can simplify the computation of era to:

    int32_t era = z / 146097;
    

    If you can restrict the lower limit of t to 1970-01-01 00:00:00.000 then the computation of z can be simplified to:

    int32_t z = t / (1000 * 86400);  // days since epoch
    

    And finally, if you are willing to restrict t to this 400 year range:

    2000-03-01 00:00:00.000 <= t <= 2400-02-29 23:59:59.999
    

    Then era can become simply:

    int32_t const era = 5;
    

    Fwiw, here is a high-level date/time library leveraging the C++11/14 <chrono> library that does the exact same computations, just with cleaner syntax. Your std::lib would have to support <chrono> to use this library:

    #include "date.h"
    #include <iostream>
    
    int
    main()
    {
        using namespace date;
        using namespace std;
        using namespace std::chrono;
        cout << sys_time<milliseconds>(1490285505123ms) << '\n';
    }