如何转换UTC日期和时间在C ++中使用time_t的时间?

时间:2012-09-10 13:44:57

标签: c++ time-t

我想转换UTC日期&以年,月,日等数字给出的时间到time_t。有些系统为此目的提供mkgmtimetimegm等功能,但这不是标准功能,在我的Solaris系统上不存在。

到目前为止,我找到的唯一解决方案是使用setenv将本地时区设置为UTC,然后调用mktime。但是,这种方法不是线程安全的,慢的,不可移植的,甚至会在我的系统上产生内存泄漏。

我还看到了尝试使用gmtime确定当前UTC偏移量然后将其添加到mktime结果的方法。但据我所知,所有这些方法都有差距。毕竟,从本地时间到UTC的转换并不是唯一的。

您认为最佳解决方案是什么?

4 个答案:

答案 0 :(得分:10)

我决定实现我自己的mkgmtime版本,这比我想象的要容易。

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;
}

我主要担心的是mkgmtime必须与gmtime一致。这样gmtime(mktime(t))返回原始输入值。因此,我比较了time_t的0和MAX_INT之间61的所有倍数的结果,它们确实相等(至少在我的系统上)。因此上述例程是正确的。

这个结果也意味着C库不会考虑闰秒,这本身就是一件坏事,但对我的目的有好处。这两个功能将长期保持一致。为了绝对肯定,我使用此函数的Timestamp类总是快速检查程序启动并证明一些有意义的值的一致性。

答案 1 :(得分:9)

为了完整性,这里有一个mkgmtime()版本,它以struct tm *作为参数:

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;
}

答案 2 :(得分:0)

如上所述,虽然time_t通常表示自1970年1月1日以来经过的秒数,但未在任何地方指定。使用不同内部表示的实现可能会出现在任何时间,任何对time_t的内部工作进行假设的代码都无法正常工作。

在考虑之后,我想出了以下内容:

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;
}

有人可以通过放弃plt(使用pt取而代之而放弃localtime()g_free(plt)来电来进一步优化这一点。

这应该适用于所有公开mktime()gmtime()localtime()的实施,包括跨DST切换日期。 (mktime()将“标准化”超出范围的值,例如将1月35日变为2月4日;我还预计冬季中期的9:50 DST将成为标准时间的8:50。)

它确实存在一个潜在的错误:如果时区的UTC偏移因DST标志中未反映的原因而发生变化,则切换时间周围的时间戳可能会被错误地解释:标准情况是立法改变其时区(例如独立后立陶宛从苏维埃时代变为CET,几年后又改为EET。一些立法在夏季中期有两倍的DST,每年通过3种不同的UTC偏移量进行循环,这是DST旗帜无法代表的。

答案 3 :(得分:0)

在我没有在标准库中找到任何可以为我完成的任务之后,这是我为自己想出的解决方案。此方法仅使用基本算术进行计算,这比从1970年到提供的日期之间每年循环都要快得多。但是,与大多数以前的答案一样,该答案取决于time_t是使用Unix / Epoch时间实现的,并且不适用于早于1970年的时间戳,这对我来说不是必需的。

#include <ctime>
#include <cassert>

constexpr unsigned int count_leapyears(unsigned int year) {
    assert(year > 0);
    return year / 4 - year / 100 + year / 400;
}

time_t timeutc(tm utc) {
    assert(utc.tm_year >= 70);
    constexpr unsigned int const leaps_before_epoch = count_leapyears(1970);
    unsigned int leapdays = count_leapyears(utc.tm_year + 1899) - leaps_before_epoch;
    unsigned int unix_time;

    unix_time = ((utc.tm_year - 70) * 365 + leapdays) * 86400;
    unix_time += utc.tm_yday * 86400 + utc.tm_hour * 3600 + utc.tm_min * 60 + utc.tm_sec;

    return unix_time;
}