harmic harmic - 8 days ago 7
C++ Question

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.

Answer

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).