timegm跨平台

20
我正在使用Visual Studio c++编译器(2010年版),但该库对ANSI C和POSIX库函数有不同的实现。
ANSI C函数和Windows CRT实现之间有什么区别?例如,tzset()_tzset()setenv()_setenv()之间有什么区别?它们似乎以相同的方式执行相同的操作...
我正在使用msvc(2010年版),我应该选择Windows CRT实现吗? 编辑1 好的,我想以便携的方式将一个以UTC表示的struct tm转换为time_t,但没有便携的方法来做到这一点。我必须为不同的平台(Android、Linux、Windows、Windows CE)编写该函数。
我看到了这个stackoverflow帖子,它使用setenvgetenvtzset编辑2 抱歉,在进行一些测试后,我发现在Windows上getenv("TZ")返回一个空指针。但是为什么将UTC时间结构转换为time_t如此困难呢? 编辑3 从Boost中,我发现了boost/chrono/io/time_point_io.hpp中的这段代码片段。希望这可以帮助我。
inline int32_t is_leap(int32_t year)
{
  if(year % 400 == 0)
  return 1;
  if(year % 100 == 0)
  return 0;
  if(year % 4 == 0)
  return 1;
  return 0;
}
inline int32_t days_from_0(int32_t year)
{
  year--;
  return 365 * year + (year / 400) - (year/100) + (year / 4);
}
inline int32_t days_from_1970(int32_t year)
{
  static const int days_from_0_to_1970 = days_from_0(1970);
  return days_from_0(year) - days_from_0_to_1970;
}
inline int32_t days_from_1jan(int32_t year,int32_t month,int32_t day)
{
  static const int32_t days[2][12] =
  {
    { 0,31,59,90,120,151,181,212,243,273,304,334},
    { 0,31,60,91,121,152,182,213,244,274,305,335}
  };
  return days[is_leap(year)][month-1] + day - 1;
}

inline time_t internal_timegm(std::tm const *t)
{
  int year = t->tm_year + 1900;
  int month = t->tm_mon;
  if(month > 11)
  {
    year += month/12;
    month %= 12;
  }
  else if(month < 0)
  {
    int years_diff = (-month + 11)/12;
    year -= years_diff;
    month+=12 * years_diff;
  }
  month++;
  int day = t->tm_mday;
  int day_of_year = days_from_1jan(year,month,day);
  int days_since_epoch = days_from_1970(year) + day_of_year;

  time_t seconds_in_day = 3600 * 24;
  time_t result = seconds_in_day * days_since_epoch + 3600 * t->tm_hour + 60 * t->tm_min + t->tm_sec;

  return result;
}

请参阅已弃用列表。另外,我认为MSVC不支持setenv - Jesse Good
setenv不在...但有putenv。 - Elvis Dukaj
很酷,你找到了提升代码。这表明实现/重新实现100%可移植的timegm()(假设您无法直接使用boost)相当容易(也可能是最好的想法)。 - Kristian Spangsege
5个回答

33

我在Windows上使用以下宏:

#define timegm _mkgmtime

就像_mkgmtime一样。


4
请注意,mingw-w64在32位系统上缺少这个功能:https://sourceforge.net/p/mingw-w64/bugs/473/ - Jeroen Ooms
对于任何R软件包开发人员,如果您在这篇文章中找到了问题,请从@jeroen的https://github.com/ropensci/redland-bindings/pull/44/files#diff-8ce830d74583e158caa8f0ec89ddf5d3cce85abb500e2c16b4e1703aeabb589eR8中找到解决方案。 - John M

10
// Algorithm: http://howardhinnant.github.io/date_algorithms.html
int days_from_epoch(int y, int m, int d)
{
    y -= m <= 2;
    int era = y / 400;
    int yoe = y - era * 400;                                   // [0, 399]
    int doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;  // [0, 365]
    int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;           // [0, 146096]
    return era * 146097 + doe - 719468;
}

// It  does not modify broken-down time
time_t timegm(struct tm const* t)     
{
    int year = t->tm_year + 1900;
    int month = t->tm_mon;          // 0-11
    if (month > 11)
    {
        year += month / 12;
        month %= 12;
    }
    else if (month < 0)
    {
        int years_diff = (11 - month) / 12;
        year -= years_diff;
        month += 12 * years_diff;
    }
    int days_since_epoch = days_from_epoch(year, month + 1, t->tm_mday);

    return 60 * (60 * (24L * days_since_epoch + t->tm_hour) + t->tm_min) + t->tm_sec;
}

这是一种将UTC时间转换为time_t的便携方式。

请注意,它不会修改/规范化tm结构,并且不会更改任何时区设置。


跨平台的timegm。 - Fox
请您将变量名days_since_epoch更正为days_since_1970,以使答案完美无误! - Hsu Pu

9
当David Cutler的团队于1989年开始Windows NT设计时,他们还不知道哪个api将会占主导地位。所以他们创建了三个api。Win32是Windows api的16位版本的改编版。支持OS/2,这是一个旨在取代DOS但没有成功的操作系统。Posix是第三个,因为当时美国政府规定他们只考虑使用遵循新兴Posix标准的操作系统。
你提到的tzset()函数是Posix api的剩余部分。你可能拼错了putenv(),同样的故事。该子系统表现不佳,Win32在api战争中大获全胜,Posix支持于2001年从Windows中删除。Microsoft保留了对Posix函数的支持,但在它们的前面加上了下划线,因为它们不是标准C库的一部分。当您使用非前缀版本的函数时,应该收到弃用警告。听起来像你定义了_CRT_NONSTDC_NO_DEPRECATE来抑制它们。最好不要这样做。优先使用标准C库函数。

3
对于大多数函数,据我所知,没有区别。
名称中的下划线是为了强调这些不是标准的C函数:据我所知,ANSI C中没有tzset和setenv函数。它们主要是POSIX函数,作为MS CRT的实现,以帮助从其他操作系统进行可移植性。
但它们并未声称符合POSIX标准,这就是为什么有下划线。因此,您应该小心并阅读有关这些函数的MS文档......那里面有恶魔!

1

我的timegm实现在Windows上可以正常工作。

time_t timegm(struct tm * a_tm)
{
    time_t ltime = mktime(a_tm);
    struct tm tm_val;
    gmtime_s(&tm_val, &ltime);
    int offset = (tm_val.tm_hour - a_tm->tm_hour);
    if (offset > 12)
    {
        offset = 24 - offset;
    }
    time_t utc = mktime(a_tm) - offset * 3600;
    return utc;
}

应该没问题。

4
不能计算非整点时区偏移,例如UTC-09:30,UTC+05:45。 - Tim R.
2
这将对+/- 12小时以外的时区失败。有几个。 - John Zwinck
2
当时区从夏令时切换到冬令时时,此操作将失败。在当地时间的这些小时存在歧义,因为同一小时会在每晚使用两次。 - Sergey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接