Search code examples
c++time-t

How to convert a UTC date & time to a time_t in C++?


I want to convert a UTC date & time given in numbers for year, month, day, etc. to a time_t. Some systems offer functions like mkgmtime or timegm for this purpose but that is not standard and does not exist on my Solaris system.

The only solution I have found so far involves setting the local time zone to UTC with setenv and then call mktime. However this approach is not thread-safe, slow, not portable and even generates a memory leak on my system.

I have also seen approaches that tried to determine the current UTC offset using gmtime and then adding that to the result of mktime. But as far as I have seen all those approaches had gaps. After all, the conversion from the local time to UTC is not unique.

What do you think is the best solution?


Solution

  • I have decided to implement my own version of mkgmtime and it was easier than I thought.

    const int SecondsPerMinute = 60;
    const int SecondsPerHour = 3600;
    const int SecondsPerDay = 86400;
    const int DaysOfMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    
    bool IsLeapYear(short year)
    {
        if (year % 4 != 0) return false;
        if (year % 100 != 0) return true;
        return (year % 400) == 0;
    }
    
    time_t mkgmtime(short year, short month, short day, short hour, short minute, short second)
    {
        time_t secs = 0;
        for (short y = 1970; y < year; ++y)
            secs += (IsLeapYear(y)? 366: 365) * SecondsPerDay;
        for (short m = 1; m < month; ++m) {
            secs += DaysOfMonth[m - 1] * SecondsPerDay;
            if (m == 2 && IsLeapYear(year)) secs += SecondsPerDay;
        }
        secs += (day - 1) * SecondsPerDay;
        secs += hour * SecondsPerHour;
        secs += minute * SecondsPerMinute;
        secs += second;
        return secs;
    }
    

    My main concern was that mkgmtime must be consistent with gmtime. Such that gmtime(mktime(t)) returns the original input values. Therefore I have compared the results for all multiples of 61 between 0 and MAX_INT for time_t and they are indeed equal (at least on my system). Therefore the above routine is correct.

    This outcome also means that the C library does not take leap seconds into account, which is a bad thing in itself but good for my purpose. The two functions will stay consistent for a long time. To be absolutely sure, my Timestamp class that uses this function always performs a quick check on program start and proves the consistency for a couple of meaningful values.