【发布时间】:2012-06-22 19:08:51
【问题描述】:
我该怎么做?有 mktime 函数,但它会将输入视为以本地时间表示,但是如果我的输入 tm 变量恰好是 UTC,我该如何执行转换。
【问题讨论】:
我该怎么做?有 mktime 函数,但它会将输入视为以本地时间表示,但是如果我的输入 tm 变量恰好是 UTC,我该如何执行转换。
【问题讨论】:
使用 timegm() 代替 mktime()
【讨论】:
timegm() 而不是mktime();在没有解释的情况下,我被包括在内以支持 Dana 的论点。
windows 用户可以使用以下功能:
_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 的单位.) 请注意,它使用 hack,可以这么说,来确定机器的时区,然后相应地调整 mktime 的结果。
/*
returns the utc timezone offset
(e.g. -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;
}
【讨论】:
timeptr 的时间偏移与zero 的时间偏移不同,这不会失败吗?换句话说,我看不出它是如何正确解释夏令时转换的。
timegm(1) 的以下实现可以在 Android 上流畅运行,并且可能也可以在其他 Unix 变体上运行:
time_t timegm( struct tm *tm ) {
time_t t = mktime( tm );
return t + localtime( &t )->tm_gmtoff;
}
【讨论】:
mktime() 和 localtime() 都可能引入错误,因为使用了错误的 UTC 偏移量。
time_t y。此答案在极端情况下失败,因为使用的tm_gmtoff 值基于t 而不是y。因此,如果 tm_gmtoff 与 t 和 y 不同,则此代码会生成错误的答案。
Loki Astari 的回答是一个好的开始,timegm 是可能的解决方案之一。但是,timegm 的手册页提供了它的可移植版本,因为timegm 不符合 POSIX。这里是:
#include <time.h>
#include <stdlib.h>
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;
}
【讨论】:
tzset 的 POSIX 页面描述了全局变量 extern long timezone,其中包含本地时区,作为与 UTC 的秒数偏移量。这将出现在所有符合 POSIX 的系统上。
为了使时区包含正确的值,您可能需要在程序初始化期间调用tzset()。
然后,您可以从 mktime 的输出中减去 timezone 以获得 UTC 输出。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
time_t utc_mktime(struct tm *t)
{
return (mktime(t) - timezone) - ((t->tm_isdst > 0) * 3600);
}
int main(int argc, char **argv)
{
struct tm t = { 0 };
tzset();
utc_mktime(&t);
}
注意:技术上是 tzset() 和 mktime() aren't guaranteed to be threadsafe。
如果一个线程直接访问 tzname、[XSI] [Option Start] 日光或 timezone [Option End],而另一个线程正在调用 tzset() 或任何需要或允许设置时区信息的函数好像通过调用 tzset(),行为未定义。
...但大多数实现都是。 GNU C 在tzset() 中使用互斥锁来避免同时修改它设置的全局变量,mktime() 在没有同步的线程程序中看到了非常广泛的用途。我怀疑如果有人遇到副作用,那就是使用setenv() 来改变TZ 的值,就像@liberforce 的回答中所做的那样。
【讨论】:
mktime(t) - timezone。此外,mktime 在 t 处转换为 DST,这根本不考虑。
这是我的看法,它完全基于time_t/tm 转换函数,它对time_t 的唯一假设是它是线性的:
tm 结构保持当地时间(如果有人问,非DST;没关系,但必须与步骤3 一致),将其转换为time_t。tm 结构,但这次以 UTC 表示。tm 结构有更好的了解 也持有本地(如果有人要求,非 DST,但更重要的是与步骤 1 一致),并再次将其转换为 time_t。 time_t 结果中,我现在可以计算本地时间(如果有人问,非夏令时)和UTC 之间的时间差,单位为time_t。time_t 结果中可以让我获得正确的 UTC 时间。请注意,差异的计算可以想象一次,然后在以后应用到所需的任意多个日期;这可能是解决gmtime 中缺乏线程安全问题的一种方法。
(编辑:再说一次,如果时区在用于计算偏移的日期和要转换的日期之间发生更改,这可能会导致问题。)
tm tt;
// populate tt here
tt.tm_isdst = 0;
time_t tLoc = mktime(&tt);
tt = *gmtime(&tLoc);
tt.tm_isdst = 0;
time_t tRev = mktime(&tt);
time_t tDiff = tLoc - tRev;
time_t tUTC = tLoc + tDiff;
警告:如果系统使用基于 TAI 的 time_t(或其他任何尊重闰秒的东西),如果应用于接近闰秒插入的时间点,则结果时间可能会延迟 1 秒。
【讨论】:
这确实是一条注释,其中包含解决 Leo Accend 答案的代码: 请尝试以下操作:
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
/*
* 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);
}
【讨论】:
我也被 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) 来获取时间差。希望对您有所帮助。
【讨论】:
time_t 代表秒,但不一定是这样(尽管通常是这样)
对于所有时区和所有时间,如果不是不可能的话,这将是极其困难的。您需要准确记录所有各种任意时区和夏令时 (DST) 法令。有时,不清楚地方当局是谁,更不用说什么时候颁布的法令了。例如,如果跨越闰秒,大多数系统会在正常运行时间(系统已启动的时间)或启动时间(系统启动的时间戳)关闭一秒。一个好的测试将是一个曾经在 DST 但现在不在的日期(反之亦然)。 (不久前在美国发生了变化。)
【讨论】: