将struct tm(以UTC表示)转换为time_t类型的简便方法
我该怎么办? 有mktime函数,但是将输入视为以本地时间表示,但如果我的输入tm变量恰好是UTC,我该如何执行转换。
使用timegm()而不是mktime()
对于Windows上的用户,可以使用以下function:
_mkgmtime
链接了解更多信息: https : //docs.microsoft.com/en-us/cpp/c-runtime-library/reference/mkgmtime-mkgmtime32-mkgmtime64
这是我使用的解决方案(不记得我发现它的位置)当它不是Windows平台时
time_t _mkgmtime(const struct tm *tm) { // Month-to-day offset for non-leap-years. static const int month_day[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; // Most of the calculation is easy; leap years are the main difficulty. int month = tm->tm_mon % 12; int year = tm->tm_year + tm->tm_mon / 12; if (month < 0) { // Negative values % 12 are still negative. month += 12; --year; } // This is the number of Februaries since 1900. const int year_for_leap = (month > 1) ? year + 1 : year; time_t rt = tm->tm_sec // Seconds + 60 * (tm->tm_min // Minute = 60 seconds + 60 * (tm->tm_hour // Hour = 60 minutes + 24 * (month_day[month] + tm->tm_mday - 1 // Day = 24 hours + 365 * (year - 70) // Year = 365 days + (year_for_leap - 69) / 4 // Every 4 years is leap... - (year_for_leap - 1) / 100 // Except centuries... + (year_for_leap + 299) / 400))); // Except 400s. return rt < 0 ? -1 : rt; }
timegm()
有效,但并不存在于所有系统上。
这是一个只使用ANSI C的版本。(编辑:严格来说不是ANSI C!我在time_t上做数学,假设单位是自纪元以来的秒数.AFAIK,标准没有定义time_t的单位。)注意,它利用黑客,可以说,确定机器的时区,然后相应地调整mktime的结果。
/* returns the utc timezone offset (eg -8 hours for PST) */ int get_utc_offset() { time_t zero = 24*60*60L; struct tm * timeptr; int gmtime_hours; /* get the local time for Jan 2, 1900 00:00 UTC */ timeptr = localtime( &zero ); gmtime_hours = timeptr->tm_hour; /* if the local time is the "day before" the UTC, subtract 24 hours from the hours to get the UTC offset */ if( timeptr->tm_mday < 2 ) gmtime_hours -= 24; return gmtime_hours; } /* the utc analogue of mktime, (much like timegm on some systems) */ time_t tm_to_time_t_utc( struct tm * timeptr ) { /* gets the epoch time relative to the local time zone, and then adds the appropriate number of seconds to make it UTC */ return mktime( timeptr ) + get_utc_offset() * 3600; }
/* returns the utc timezone offset (eg -8 hours for PST) */ int get_utc_offset() { time_t zero = 24*60*60L; struct tm * timeptr; int gmtime_hours; /* get the local time for Jan 2, 1900 00:00 UTC */ timeptr = localtime( &zero ); gmtime_hours = timeptr->tm_hour; /* if the local time is the "day before" the UTC, subtract 24 hours from the hours to get the UTC offset */ if( timeptr->tm_mday < 2 ) gmtime_hours -= 24; return gmtime_hours; } /* the utc analogue of mktime, (much like timegm on some systems) */ time_t tm_to_time_t_utc( struct tm * timeptr ) { /* gets the epoch time relative to the local time zone, and then adds the appropriate number of seconds to make it UTC */ return mktime( timeptr ) + get_utc_offset() * 3600; }
以下timegm(1)
实现在Android上运行,并且可能也适用于其他Unix变体:
time_t timegm( struct tm *tm ) { time_t t = mktime( tm ); return t + localtime( &t )->tm_gmtoff; }
Loki Astari的答案是一个良好的开端, timegm
是可能的解决方案之一。 但是, timegm
的手册页提供了它的可移植版本,因为timegm
不符合POSIX标准。 这里是:
#include #include time_t my_timegm(struct tm *tm) { time_t ret; char *tz; tz = getenv("TZ"); if (tz) tz = strdup(tz); setenv("TZ", "", 1); tzset(); ret = mktime(tm); if (tz) { setenv("TZ", tz, 1); free(tz); } else unsetenv("TZ"); tzset(); return ret; }
这是一个代码,用于解决Leo Accend的答案:尝试以下方法:
#include #include #include /* * A bit of a hack that lets you pull DST from your Linux box */ time_t timegm( struct tm *tm ) { // From Leo's post, above time_t t = mktime( tm ); return t + localtime( &t )->tm_gmtoff; } main() { struct timespec tspec = {0}; struct tm tm_struct = {0}; if (gettimeofday(&tspec, NULL) == 0) // clock_gettime() is better but not always avail { tzset(); // Not guaranteed to be called during gmtime_r; acquire timezone info if (gmtime_r(&(tspec.tv_sec), &tm_struct) == &tm_struct) { printf("time represented by original utc time_t: %s\n", asctime(&tm_struct)); // Go backwards from the tm_struct to a time, to pull DST offset. time_t newtime = timegm (&tm_struct); if (newtime != tspec.tv_sec) // DST offset detected { printf("time represented by new time_t: %s\n", asctime(&tm_struct)); double diff = difftime(newtime, tspec.tv_sec); printf("DST offset is %g (%f hours)\n", diff, diff / 3600); time_t intdiff = (time_t) diff; printf("This amounts to %s\n", asctime(gmtime(&intdiff))); } } } exit(0); }
tzset的 POSIX页面,描述全局变量extern long timezone
,其中包含本地时区作为UTC的秒数偏移量。 这将出现在所有符合POSIX标准的系统上。
为了使时区包含正确的值,您可能需要在程序初始化期间调用tzset()
。
然后,您只需将timezone
添加到mktime
的输出中即可获得UTC输出。
#include #include #include time_t utc_mktime(struct tm *t) { return mktime(t) + timezone; } int main(int argc, char **argv) { struct tm t = { 0 }; tzset(); utc_mktime(&t); }
注意:从技术上讲, tzset()
和mktime()
不保证是线程安全的 。
如果一个线程直接访问tzname,[XSI] [Option Start]日光或timezone [Option End],而另一个线程正在调用tzset(),或者需要或允许设置时区信息的任何函数,就像通过调用tzset(),行为是未定义的。
……但是大多数实现都是。 GNU C在tzset()
使用互斥锁来避免对它设置的全局变量进行并发修改,并且mktime()
在没有同步的线程程序中看到非常广泛的用途。 我怀疑如果有人遇到副作用,那就是使用setenv()
改变TZ
的值,就像@liberforce的回答一样。
我也被mktime()的问题困扰了。 我的解决方案如下
time_t myTimegm(std::tm * utcTime) { static std::tm tmv0 = {0, 0, 0, 1, 0, 80, 0, 0, 0}; //1 Jan 1980 static time_t utcDiff = std::mktime(&tmv0) - 315532801; return std::mktime(utcTime) - utcDiff; }
我们的想法是通过使用已知时间(在本例中为1980/01/01)调用std :: mktime()来获取时间差,并减去其时间戳(315532801)。 希望能帮助到你。