我有以下整数:
int y, mon, d, h, min, s;
它们的值分别为:2012
、06
、27
、12
、47
、53
。如果我在应用程序中的其他地方选择了"UTC",我想表示"2012/06/27 12:47:53 UTC"的日期时间,如果我在应用程序中的其他地方选择了"AEST",则表示"2012/06/27 12:47:53 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 struct
和mktime
,但这效果不佳,因为它总是假设我的本地时区。
正确的方法是什么?
因此,以下是我到目前为止提出的解决方案。它基本上做以下三件事之一:
- 如果是 UNIX,只需使用
timegm
- 如果不是 UNIX
- 或者,使用 UTC 纪元和局部纪元之间的差异作为偏移量进行数学运算
- 预订:数学可能不正确
- 或者,将"TZ"环境变量暂时设置为 UTC
- 保留:如果/当此代码需要多线程时,将跳闸
- 或者,使用 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
类型的东西。
这让我想在嘴里吐一点,但你可以用strftime()
将其转换为字符串,替换字符串中的时区,然后用strptime()
将其转换回并转换为带有mktime()
的time_t
。详细地:
#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对自定义时区有很好的支持。还有其他库也可以优雅地做到这一点。
如果您只想将 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;
}
如果你使用的是Linux或其他UNIx或类UNIX系统,那么你可能有一个timegm
函数可以做你想要的。链接的手册页具有可移植的实现,因此您可以自己制作。在Windows上,我知道没有这样的功能。
获得一个适用于 Android 的 timegm(1)
功能(它没有附带一个)之后,我终于发现了这个简单而优雅的解决方案,它运行得很好:
time_t timegm( struct tm *tm ) {
time_t t = mktime( tm );
return t + localtime( &t )->tm_gmtoff;
}
我不明白为什么这不是一个合适的跨平台解决方案。
我希望这有帮助!
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;
}
似乎有一个更简单的解决方案:
#include <time64.h>
time_t timegm(struct tm* const t)
{
return (time_t)timegm64(t);
}
实际上,如果它真的有效,我还没有测试,因为我仍然有一些移植要做,但它可以编译。
的解决方案:
#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。