将日期和时间数字转换为time_t并指定时区

时间:2012-06-27 02:57:18

标签: c++ datetime timezone time-t

我有以下整数:

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

他们的价值分别为:20120627124753。我想代表“2012/06/27 12:47:53 UTC”的日期时间,如果我在我的应用程序中的其他地方选择了“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. 即可
  3. 如果不是UNIX
    1. 使用UTC纪元和本地纪元之间的差异作为偏移来进行数学运算
      • 预订:数学可能不正确
    2. 或者,暂时将“TZ”环境变量设置为UTC
      • 预订:如果/此代码需要多线程,将会绊倒
  4. 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
    }
    

    N.B。 StringBuilder是一个内部类,对于这个问题而言无关紧要。

    更多信息:

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

    timegm似乎可以解决这个问题,但它似乎不是C / POSIX标准的一部分。此代码目前在多个平台(Linux,OSX,WIndows,iOS,Android(NDK))上编译,因此我需要找到一种方法使其在所有这些平台上运行,即使解决方案涉及#ifdef $PLATFORM输入东西。

6 个答案:

答案 0 :(得分:4)

如果您想要的只是将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,然后系统将尽力在给定时间为您找出DST。假设我们信任系统,那么您可以使用此信息来应用更正:

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

答案 1 :(得分:4)

这让我想稍微呕吐,但你可以将它转换为strftime()的字符串,替换字符串中的时区,然后用strptime()将其转换回来使用time_t加入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对自定义时区有很大的支持。还有其他库可以优雅地完成这项工作。

答案 2 :(得分:2)

如果您使用的是Linux或其他UNIx或类UNIX系统,那么您可能拥有可以执行所需操作的timegm功能。链接的手册页有一个便携式实现,因此您可以自己创建。在Windows上,我知道没有这样的功能。

答案 3 :(得分:0)

在试图获得适用于Android的timegm(1)功能(其中没有附带功能)的几天之后,我对抗了这个问题,我终于发现了这个简单而优雅的解决方案,它可以很好地运行:

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

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

我希望这有帮助!

答案 4 :(得分:0)

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

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

实际上我还没有测试它是否真的有用,因为我还有一些移植要做,但它会编译。

答案 5 :(得分:0)

这是我的解决方法:

#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均适用