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

时光怂恿深爱的人放手 提交于 2019-11-30 04:40:07

问题


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?


回答1:


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.




回答2:


For completeness, here's a version of mkgmtime() that takes a struct tm* as argument:

static time_t mkgmtime(const struct tm *ptm) {
    time_t secs = 0;
    // tm_year is years since 1900
    int year = ptm->tm_year + 1900;
    for (int y = 1970; y < year; ++y) {
        secs += (IsLeapYear(y)? 366: 365) * SecondsPerDay;
    }
    // tm_mon is month from 0..11
    for (int m = 0; m < ptm->tm_mon; ++m) {
        secs += DaysOfMonth[m] * SecondsPerDay;
        if (m == 1 && IsLeapYear(year)) secs += SecondsPerDay;
    }
    secs += (ptm->tm_mday - 1) * SecondsPerDay;
    secs += ptm->tm_hour       * SecondsPerHour;
    secs += ptm->tm_min        * SecondsPerMinute;
    secs += ptm->tm_sec;
    return secs;
}



回答3:


As noted above, while time_t usually represents seconds elapsed since Jan 1, 1970, this is not specified anywhere. An implementation which uses a different internal representation may show up any time, and any code that makes assumptions about the inner workings of time_t will not work correctly there.

After giving it some thought, I came up with the following:

time_t mkgmtime(struct tm * pt) {
    time_t ret;

    /* GMT and local time */
    struct tm * pgt, * plt;

    ret = mktime(pt);

    pgt = g_memdup(gmtime(ret), sizeof(struct tm));
    plt = g_memdup(localtime(ret), sizeof(struct tm));

    plt->tm_year -= pgt->tm_year - plt->tm_year;
    plt->tm_mon -= pgt->tm_mon - plt->tm_mon;
    plt->tm_mday -= pgt->tm_mday - plt->tm_mday;
    plt->tm_hour -= pgt->tm_hour - plt->tm_hour;
    plt->tm_min -= pgt->tm_min - plt->tm_min;
    plt->tm_sec -= pgt->tm_sec - plt->tm_sec;

    ret = mktime(plt);

    g_free(pgt);
    g_free(plt);

    return ret;
}

One could probably optimize this further by dropping plt (using pt in its place and omitting the localtime() and g_free(plt) calls).

This should work across all implementations which expose mktime(), gmtime() and localtime(), including across DST switchover dates. (mktime() will “normalize” out-of-range values, e.g. turning Jan 35 into Feb 4; I would also expect 9:50 DST in the middle of winter to become 8:50 standard time.)

It does suffer from one potential bug: if a time zone’s UTC offset changes for reasons not reflected in the DST flag, timestamps around the cutover time may get interpreted incorrectly: The standard case is when a legislation changes its time zone (e.g. Lithuania changed from Soviet time to CET after independence, and to EET a few years later). Some legislations had double DST in mid-summer, cycling through 3 different UTC offsets per year, which the DST flag cannot represent.



来源:https://stackoverflow.com/questions/12353011/how-to-convert-a-utc-date-time-to-a-time-t-in-c

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!