8

我想将以年、月、日等数字形式给出的 UTC 日期和时间转换为 time_t。一些系统提供类似mkgmtimetimegm用于此目的的功能,但这不是标准的,并且在我的 Solaris 系统上不存在。

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

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

你认为最好的解决方案是什么?

4

4 回答 4

11

我决定实现我自己的 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 类总是对程序启动执行快速检查,并证明几个有意义的值的一致性。

于 2012-09-10T13:44:57.117 回答
10

为了完整起见,这里是 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;
}
于 2015-11-06T18:48:06.783 回答
1

如上所述,虽然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;
}

可以通过删除pltpt在其位置使用并省略localtime()andg_free(plt)调用)来进一步优化这一点。

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

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

于 2018-01-23T23:19:50.137 回答
1

这是我在标准库中找不到任何东西来为我做这件事后为自己想出的一个解决方案。此方法仅使用基本算术进行计算,使其比从 1970 年到提供的日期之间的每年循环要快得多。但是与之前的大多数答案一样,这取决于使用 Unix/Epoch 时间实现的 time_t,并且不适用于早于 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;
}
于 2019-12-19T01:48:33.657 回答