Search code examples
c++solarissolaris-10

Alternative to timegm on Solaris


I have a program that was originally written for Linux, but I now have a requirement to get it running on Solaris 10.

Part of this program uses the timegm function to convert a struct tm into a time_t epoch seconds value. The input time is referenced to UTC.

Trying to compile this program on Solaris, it fails because timegm cannot be found. After some googling I realized that this function has been removed from Solaris a long time ago (and even the Linux manpage recommends against using it, because it isn't standardized).

However I have so far not been able to find an alternative function, that takes a struct tm referenced to UTC and converts to epoch time. Most references I found on the net recommend using mktime, however that function interprets the inputs with reference to the system local time zone.

Note that I do not wish to use tzset to force the timezone to UTC, as that would have other side effects on the program.

So my question is: how can I convert a struct tm broken down time value, expressed with respect to UTC, into an epoch time, in the absence of timegm?

The program is written in C++ so I'm not limited to C solutions, although I would prefer not to embark on a wholesale rewrite to use some additional time library.


Solution

  • You could use days_from_civil which is described here in detail

    // Returns number of days since civil 1970-01-01.  Negative values indicate
    //    days prior to 1970-01-01.
    // Preconditions:  y-m-d represents a date in the civil (Gregorian) calendar
    //                 m is in [1, 12]
    //                 d is in [1, last_day_of_month(y, m)]
    //                 y is "approximately" in
    //                   [numeric_limits<Int>::min()/366, numeric_limits<Int>::max()/366]
    //                 Exact range of validity is:
    //                 [civil_from_days(numeric_limits<Int>::min()),
    //                  civil_from_days(numeric_limits<Int>::max()-719468)]
    template <class Int>
    constexpr
    Int
    days_from_civil(Int y, unsigned m, unsigned d) noexcept
    {
        static_assert(std::numeric_limits<unsigned>::digits >= 18,
                 "This algorithm has not been ported to a 16 bit unsigned integer");
        static_assert(std::numeric_limits<Int>::digits >= 20,
                 "This algorithm has not been ported to a 16 bit signed integer");
        y -= m <= 2;
        const Int era = (y >= 0 ? y : y-399) / 400;
        const unsigned yoe = static_cast<unsigned>(y - era * 400);      // [0, 399]
        const unsigned doy = (153*(m + (m > 2 ? -3 : 9)) + 2)/5 + d-1;  // [0, 365]
        const unsigned doe = yoe * 365 + yoe/4 - yoe/100 + doy;         // [0, 146096]
        return era * 146097 + static_cast<Int>(doe) - 719468;
    }
    

    to convert the {year, month, day} triple in the tm to a count of days since the epoch (1970-01-01). Be careful when converting these fields from tm for their eccentricities (e.g. tm_year + 1900).

    Multiply this count of days by 86400 and add to that the {hours, minutes, seconds} data from the tm (each converted to seconds).

    And you're done. Don't worry about leap seconds, timegm didn't worry about them either. If you're really concerned about leap seconds I have a C++11/14 solution available to deal with that, but I'm guessing that is more than you want to get into.

    Don't be put off by the C++14 syntax shown above. It is trivial to convert this algorithm to C (or any other language for that matter).