【问题标题】:Convert date and time numbers to time_t AND specify the timezone将日期和时间数字转换为 time_t 并指定时区
【发布时间】:2012-06-28 10:44:33
【问题描述】:

我有以下整数:

int y, mon, d, h, min, s;

它们的值分别为:20120627124753。如果我在我的应用程序的其他地方选择了“UTC”,我想代表“2012/06/27 12:47:53 UTC”的日期时间,或者如果我选择了“2012/06/27 12:47:53 AEST”的日期时间在我的应用程序的其他地方选择了“AEST”。

我想将其转换为time_t,这是我目前用来执行此操作的代码:

struct tm timeinfo;
timeinfo.tm_year = year - 1900;
timeinfo.tm_mon = mon - 1;
timeinfo.tm_mday = day;
timeinfo.tm_hour = hour;
timeinfo.tm_min = min;
timeinfo.tm_sec = sec;
//timeinfo.tm_isdst = 0; //TODO should this be set?

//TODO find POSIX or C standard way to do covert tm to time_t without in UTC instead of local time
#ifdef UNIX
return timegm(&timeinfo);
#else
return mktime(&timeinfo); //FIXME Still incorrect
#endif

所以我使用的是tm structmktime,但这并不好用,因为它总是假设我的本地时区。

这样做的正确方法是什么?

所以下面是我到目前为止提出的解决方案。 它基本上做以下三件事之一:

  1. 如果是 UNIX,只需使用 timegm
  2. 如果不是 UNIX
    1. 或者,使用 UTC 纪元和本地纪元之间的差异作为偏移量进行数学运算
      • 保留:数学可能不正确
    2. 或者,将“TZ”环境变量临时设置为 UTC
      • 保留:如果/当此代码需要多线程时,将会出错
namespace tmUtil
{
    int const tm_yearCorrection = -1900;
    int const tm_monthCorrection = -1;
    int const tm_isdst_dontKnow = -1;

#if !defined(DEBUG_DATETIME_TIMEGM_ENVVARTZ) && !(defined(UNIX) && !defined(DEBUG_DATETIME_TIMEGM))
    static bool isLeap(int year)
    {
        return
            (year % 4) ? false
            : (year % 100) ? true
            : (year % 400) ? false
            : true;
    }

    static int daysIn(int year)
    {
        return isLeap(year) ? 366 : 365;
    }
#endif
}

time_t utc(int year, int mon, int day, int hour, int min, int sec)
{
    struct tm time = {0};
    time.tm_year = year + tmUtil::tm_yearCorrection;
    time.tm_mon = mon + tmUtil::tm_monthCorrection;
    time.tm_mday = day;
    time.tm_hour = hour;
    time.tm_min = min;
    time.tm_sec = sec;
    time.tm_isdst = tmUtil::tm_isdst_dontKnow;

    #if defined(UNIX) && !defined(DEBUG_DATETIME_TIMEGM) //TODO remove && 00
        time_t result;
        result = timegm(&time);
        return result;
    #else
        #if !defined(DEBUG_DATETIME_TIMEGM_ENVVARTZ)
            //TODO check that math is correct
            time_t fromEpochUtc = mktime(&time);

            struct tm localData;
            struct tm utcData;
            struct tm* loc = localtime_r (&fromEpochUtc, &localData);
            struct tm* utc = gmtime_r (&fromEpochUtc, &utcData);
            int utcYear = utc->tm_year - tmUtil::tm_yearCorrection;
            int gmtOff =
                (loc-> tm_sec - utc-> tm_sec)
                + (loc-> tm_min - utc-> tm_min) * 60
                + (loc->tm_hour - utc->tm_hour) * 60 * 60
                + (loc->tm_yday - utc->tm_yday) * 60 * 60 * 24
                + (loc->tm_year - utc->tm_year) * 60 * 60 * 24 * tmUtil::daysIn(utcYear);

            #ifdef UNIX
                if (loc->tm_gmtoff != gmtOff)
                {
                    StringBuilder err("loc->tm_gmtoff=", StringBuilder((int)(loc->tm_gmtoff)), " but gmtOff=", StringBuilder(gmtOff));
                    THROWEXCEPTION(err);
                }
            #endif

            int resultInt = fromEpochUtc + gmtOff;
            time_t result;
            result = (time_t)resultInt;
            return result;
        #else
            //TODO Find a way to do this without manipulating environment variables
            time_t result;
            char *tz;
            tz = getenv("TZ");
            setenv("TZ", "", 1);
            tzset();
            result = mktime(&time);
            if (tz)
                setenv("TZ", tz, 1);
            else
                unsetenv("TZ");
            tzset();
            return result;
        #endif
    #endif
}

注意StringBuilder 是一个内部类,对于这个问题来说并不重要。

更多信息:

我知道这可以使用 boost 等轻松完成。但这不是选项。我需要它以数学方式完成,或者使用 c 或 c++ 标准函数,或它们的组合。

timegm 似乎解决了这个问题,但是,它似乎不是 C / POSIX 标准的一部分。这段代码目前在多个平台(Linux、OSX、Windows、iOS、Android (NDK))上编译,所以我需要找到一种方法让它在所有这些平台上工作,即使解决方案涉及#ifdef $PLATFORM 类型的东西.

【问题讨论】:

  • 您还有其他可用的库吗?您有时区列表及其偏移量吗?
  • @Zac 不,我不想使用任何库,因为它们在交叉编译时会产生很多开发开销。另外,不,我没有时区列表及其偏移量。看看我上面的更新,我有一种计算时区偏移的方法 - 你看起来正确吗?
  • 您可以使用strftime() 将其转换为字符串,替换字符串中的时区,然后将其转换回mktime(strptime()) HACK>,但我会说服增强的力量实际上是一种选择。
  • @smocking 你能把它作为答案发布吗?
  • 当然,但实际上我有点羞愧,我什至将它作为评论发布:-)

标签: c++ datetime timezone time-t


【解决方案1】:

这让我有点想吐在嘴里,但你可以用strftime()将其转换为字符串,替换字符串中的时区,然后用strptime()将其转换回@987654323 @ 与mktime()。详细:

#ifdef UGLY_HACK_VOIDS_WARRANTY
time_t convert_time(const struct tm* tm)
{
    const size_t BUF_SIZE=256;
    char buffer[BUF_SIZE];
    strftime(buffer,256,"%F %H:%M:%S %z", tm);
    strncpy(&buffer[20], "+0001", 5); // +0001 is the time-zone offset from UTC in hours
    struct tm newtime = {0};
    strptime(buffer, "%F %H:%M:%S %z", &newtime);
    return mktime(&newtime);
}
#endif

但是,我强烈建议您说服权力,毕竟提升是一种选择。 Boost 对自定义时区有很好的支持。还有其他库也可以优雅地做到这一点。

【讨论】:

  • @smocking : 赏金给你,因为它有效,唯一的问题是它很丑
【解决方案2】:

如果您只想将 UTC 中的 struct tm 转换为 time_t,那么您可以这样做:

#include <time.h>

time_t utc_to_time_t(struct tm* timeinfo)
{
    tzset(); // load timezone information (this can be called just once)

    time_t t = mktime(timeinfo);
    return t - timezone;
}

这基本上将 UTC 时间转换为 time_t,就好像给定时间是本地时间一样,然后对结果应用时区校正以将其恢复为 UTC。

在 gcc/cygwin 和 Visual Studio 2010 上测试。

我希望这会有所帮助!

更新:正如您很好指出的那样,当查询日期的夏令时状态与当前时间不同时,我上面的解决方案可能会返回一个小时的 time_t 值.

该问题的解决方案是增加一个函数,它可以告诉您日期是否在 DST 区域内,并使用该函数和当前 DST 标志来调整 mktime 返回的时间。这实际上很容易做到。当您调用mktime() 时,您只需将tm_dst 成员设置为-1,然后系统将尽力在给定时间为您计算夏令时。假设我们对此信任系统,那么您可以使用此信息进行更正:

#include <time.h>

time_t utc_to_time_t(struct tm* timeinfo)
{
    tzset(); // load timezone information (this can be called just once)

    timeinfo->tm_isdst = -1; // let the system figure this out for us
    time_t t = mktime(timeinfo) - timezone;

    if (daylight == 0 && timeinfo->tm_isdst != 0)
        t += 3600;
    else if (daylight != 0 && timeinfo->tm_isdst == 0)
        t -= 3600;
    return t;
}

【讨论】:

  • 米格尔:感谢您的回答。我看到的一个问题是time.htimezone 的值表示当前与UTC 的时间差,而不是您正在计算的时间点。这意味着当您计算与您目前所处的夏令时相反的时间(每年 6 个月)时,计算的时间将减少一小时。你有没有考虑到这一点;或者如果我弄错了请纠正我?
  • 嗯。您实际上是对的,我的解决方案可能会出现一小时的错误,因此解决此问题的唯一方法是拥有一个历史数据库,其中包含您想要支持的所有位置的夏令时更改。原来这样的数据库存在,你可以从iana.org/time-zones得到它。它们提供数据以及使用它的时间函数。我认为修改他们的 mktime 实现以假设 UTC 时区是相当容易的。这个库中的函数可以在 Unix 上编译,但是将它们移植到 windows 看起来并不需要很多工作。
  • 否则,你可以看看 PHP 的 DateTime 实现,它基于同一个数据库,到处编译。
  • @bguiz:我相信我已经找到了一个完整的解决方案,仍然非常便携。这是一个非常有趣的问题!
【解决方案3】:

如果您使用的是 Linux 或其他 UNIX 或类 UNIX 系统,那么您可能有一个 timegm 函数来满足您的需求。链接的手册页具有可移植的实现,因此您可以自己制作。在 Windows 上,我知道没有这样的功能。

【讨论】:

  • 感谢您的意见 - 虽然不能完全解决我的问题,请参阅我对问题的更新。
  • 只是 ping 你,让你得到我的最新更新!看看吧,我想听听你们的cmets。
【解决方案4】:

在尝试获得适用于 Android 的 timegm(1) 函数几天后,我终于发现了这个简单而优雅的解决方案,效果很好:

time_t timegm( struct tm *tm ) {
  time_t t = mktime( tm );
  return t + localtime( &t )->tm_gmtoff;
}

我不明白为什么这不是一个合适的跨平台解决方案。

我希望这会有所帮助!

【讨论】:

  • 不合适,因为tm_gmtoff 不可移植。
【解决方案5】:
time_t my_timegm2(struct tm *tm)
{
    time_t ret = tm->tm_sec + tm->tm_min*60 + tm->tm_hour*3600 + tm->tm_yday*86400;
    ret += ((time_t)31536000) * (tm->tm_year-70);
    ret += ((tm->tm_year-69)/4)*86400 - ((tm->tm_year-1)/100)*86400 + ((tm->tm_year+299)/400)*86400;
    return ret;
}

【讨论】:

    【解决方案6】:

    似乎有一个更简单的解决方案:

    #include <time64.h>
    time_t timegm(struct tm* const t) 
    {
      return (time_t)timegm64(t);
    }
    

    实际上我还没有 testet 如果它真的有效,因为我还有一些移植要做,但它可以编译。

    【讨论】:

      【解决方案7】:

      这是我的解决方案:

      #ifdef WIN32
      #   define timegm _mkgmtime
      #endif
      
      struct tm timeinfo;
      timeinfo.tm_year = year - 1900;
      timeinfo.tm_mon = mon - 1;
      timeinfo.tm_mday = day;
      timeinfo.tm_hour = hour;
      timeinfo.tm_min = min;
      timeinfo.tm_sec = sec;
      
      return timegm(&timeinfo);
      

      这应该适用于 unix 和 windows

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-11-01
        • 2017-03-17
        • 1970-01-01
        • 1970-01-01
        • 2015-08-18
        • 2012-09-03
        • 2017-07-29
        相关资源
        最近更新 更多