19

I'm using Visual Studio c++ Compiler ( 2010 ), but the library has different implementation of ANSI C and POSIX libraries function.

What is the difference between ANSI C function and Windows CRT implementation? For example what is the difference between tzset() and _tzset() or setenv() ans _setenv()? It seems the do the same thing in the same way...

I'm using msvc ( 2010 ), have I to prefer the Windows CRT Implementation?

EDIT 1

Well I want convert in a portable way a struct tm expressed in UTC in a time_t, but there's no portable way to do that. I've to write the function for different platform (Android, Linux, Windows, Windows CE ).

I've seen this stackoverflow post that uses setenv, getenv and tzset

Edit2

Unfortunately after some test I've discovered that getenv("TZ") returns a null pointer on windows. But why is so difficult transform a UTC time struct to a time_t?

Edit 3

From Boost I discovered this fragment of code in boost/chrono/io/time_point_io.hpp. Hope this helps me.

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;
}
Tim Sylvester
  • 21,850
  • 2
  • 69
  • 92
Elvis Dukaj
  • 6,441
  • 10
  • 38
  • 77
  • Please see [the deprecated list](http://msdn.microsoft.com/en-us/library/ms235384%28v=vs.80%29.aspx). Also, I don't think `setenv` is available with MSVC. – Jesse Good May 20 '13 at 10:56
  • setenv is not present... but ther's putenv – Elvis Dukaj May 20 '13 at 11:02
  • Cool that you found the boost code. This suggests that it is rather easy (and probably the best idea) to implement/reimplement a 100% portable `timegm()` (assuming you cannot use boost directly). – Kristian Spangsege Apr 20 '14 at 22:00

5 Answers5

28

I use the following macro on Windows:

#define timegm _mkgmtime

as _mkgmtime does the same.

Naszta
  • 7,104
  • 2
  • 30
  • 48
  • 4
    Note mingw-w64 is missing this on 32bit: https://sourceforge.net/p/mingw-w64/bugs/473/ – Jeroen Dec 22 '16 at 14:41
  • For any R package devs out there who find this post, find a solution from @jeroen at https://github.com/ropensci/redland-bindings/pull/44/files#diff-8ce830d74583e158caa8f0ec89ddf5d3cce85abb500e2c16b4e1703aeabb589eR8 – John M Oct 24 '20 at 17:24
9

When David Cutler's team started on the Windows NT design, back in 1989, they didn't yet know which api was going to be dominant. So they created three of them. Win32 was an adaption of the 16-bit version of the Windows api. OS/2 was supported, the operating system that was supposed to supplant DOS but didn't. And Posix was the third, added because the USA government back then specified that they would only consider using operating systems that followed the emerging Posix standard.

The tzset() function you mention is a left-over from the Posix api. You probably misspelled putenv(), same story. The subsystem didn't fare well, Win32 won the api battle in a big way and Posix support was removed from Windows in 2001. Microsoft kept the support for the Posix functions but renamed them with a leading underscore since they are not part of the standard C library. You are supposed to get deprecation warnings when you use the non-prefixed version of the functions. Sounds like you #defined _CRT_NONSTDC_NO_DEPRECATE to suppress them. Best to not do that. Favor the standard C library functions.

Hans Passant
  • 873,011
  • 131
  • 1,552
  • 2,371
3

For most functions, that I know of, there is no difference.

The underscore in the names is there to emphasize that these are not standard C functions: AFAIK, there are not tzset nor setenv functions in ANSI C. They are mostly POSIX functions that are implemented by the MS CRT as an aid for portability from other operating systems.

But they don't claim POSIX compatibility, that's why the underscore. And that's why you should be careful and read the MS documentation about these functions... there are demons there!

rodrigo
  • 79,651
  • 7
  • 121
  • 162
2
// Algorithm: http://howardhinnant.github.io/date_algorithms.html
int days_from_civil(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;
}

time_t timegm(tm const* t)     // It  does not modify broken-down time
{
    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_1970 = days_from_civil(year, month + 1, t->tm_mday);

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

This is a portable way to convert tm in UTC to time_t.

Note that it does not modify/normalise tm structure and it does not change any tz settings.

Sergey D
  • 545
  • 5
  • 9
1

My implementation of timegm is working on 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;
}

Should be fine.

Bowdzone
  • 3,651
  • 11
  • 39
  • 49
Siewca
  • 27
  • 1
  • 4
    Doesn't account for non-whole-hour offsets, for instance UTC−09:30, UTC+05:45 – Tim R. Mar 14 '17 at 00:26
  • 2
    This will fail for time zones outside of +/- 12 hours. There are a few. – John Zwinck Apr 19 '17 at 06:49
  • 2
    this will fail during hour when time zone is switched from summer to winter. There is an ambiguity at those hour in local time because the same local hour is used twice per night. – Sergey Apr 20 '18 at 11:59