5

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.

harmic
  • 28,606
  • 5
  • 67
  • 91

2 Answers2

5

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

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
0

Per the POSIX standard for tzset():

SYNOPSIS

#include <time.h>

extern int daylight;
extern long timezone;

extern char *tzname[2];
void tzset(void);

...

The tzset() function also shall set the external variable daylight to 0 if Daylight Savings Time conversions should never be applied for the timezone in use; otherwise, non-zero. The external variable timezone shall be set to the difference, in seconds, between Coordinated Universal Time (UTC) and local standard time.

You should be able to call tzset() to set the value in timezone, then use mktime() to get the time in the current timezone, then apply the difference in the timezone variable to to the result from mktime() to convert that result to UTC.

I don't have access to Solaris right now to test that.

Andrew Henle
  • 32,625
  • 3
  • 24
  • 56
  • Thanks. I had thought of that, but the difference between UTC and local time isn't constant, it depends on the time being converted. For example if I convert '2016-12-01 10:00' in my timezone to UTC the difference would be different to '2016-06-01 10:00' because of daylight savings. – harmic Dec 01 '16 at 23:27