localtime 为在 cygwin shell 上运行的 Windows 程序返回 GMT

2021-12-23 00:00:00 time cygwin c c++

考虑以下代码:

time_t t;
t = time( NULL );
elog << "timezone: " << getenv( "TZ" ) 
     << ", current local time: " << asctime( localtime( &t ));

如果我使用 MSVC 构建此代码,并在 Windows DOS shell 下运行它,我会得到正确的本地时间:

If I build this code using MSVC, and run it under the windows DOS shell, I get the correct local time:

timezone: , current local time: Wed Jul 25 13:05:08 2012

但是如果我在像 bash 这样的 cygwin shell 下运行相同的程序,此代码将返回 GMT!

But if I run the same program under a cygwin shell like bash, this code returns GMT!

timezone: America/New_York, current local time: Wed Jul 25 18:05:08 2012

如果我在 Linux 或 OsX 中运行这个程序,它也会返回正确的本地时间.

If I run this program in Linux or OsX, it also returns the correct local time.

为什么?

@Update:现在一年过去了,我发现我在下面给出的答案并不总是有效.

@Update: It is now a year later and I found that the answer I gave below does not always work.

似乎对于某些程序来说,取消设置 TZ 并不总是有效.我不知道为什么.但是有一个麻烦的解决方法.基本上,在您取消设置 TZ 之后,您必须检查本地时间确实不再返回 GMT,但前??提是您实际上不在 GMT 时区,并在您调用 localtime() 或maketime()

It seems that for some programs unsetting TZ does not always work. I don't know why. But there is a cumbersome workaround. Basically, right after you unset TZ, you have to check that local time is indeed no longer returning GMT, but only if you aren't actually in the GMT time zone, and compute a manual adjustment to time_t's when you call localtime() or maketime()

u64 localTimeOffset = 0;

static void unsetTz()
{
   static bool unsetTZ = false;
   if ( !unsetTZ )
   {
      putenv( "TZ=" );
      unsetTZ = true;

      // unsetting TZ does not always work. So we have to check if it worked
      // and make a manual adjustment if it does not. For long running programs
      // that may span DST changes, this may cause the DST change to not take 
      // effect.
      s32 tzOffset = getTzOffset();

      if ( tzOffset )
      {
         static char timeBuf[48];
         char* s = &(timeBuf[0]);
         struct tm* timeInfoGMT;
         struct tm* timeInfoLocal;

         time_t zero = 86400;
         timeInfoGMT = gmtime( &zero );
         u32 GMTHour = timeInfoGMT->tm_hour;

         timeInfoLocal = localtime( &zero );
         u32 localHour = timeInfoLocal->tm_hour;

         if ( localHour == GMTHour )
         {
            // unsetting tz failed. So we have to make a manual adjustment
            localTimeOffset = tzOffset * 60 * 60;
         }
      }
   }
}

s32 getTzOffset()
{
   TIME_ZONE_INFORMATION tzInfo;
   GetTimeZoneInformation( &tzInfo );
   s32 tz = ( tzInfo.Bias / 60 );
   return tz;
}

拨打本地时间:

  time_t t = getAtTimeFromSomewhere();
  t -= localTimeOffset;
  timeInfo = localtime( &t );

调用maketime:

A call to maketime:

 struct tm timestr;
 makeATMFromAStringForExample( time, timestr );
 time_t timet = mktime( &timestr );
 timet += localTimeOffset;

美好时光.

推荐答案

这让我花了一些时间才弄明白,我希望它对其他人有用.

This took me some time to figure out, and I'm hoping it will be useful to others.

localtime 这样的 POSIX 函数将使用环境变量 TZ 来确定要使用的时区.如果 TZ 未设置,它将使用系统的默认时区.

POSIX functions like localtime will use the environment variable TZ to determine what timezone to use. If TZ is not set it will use the system's default timezone.

如果我在 Linux 或 OS X 下运行,TZ 设置正确并且一切正常.如果我在 Windows 上的 shell 中运行此程序,则未设置 TZ,因此该函数返回操作系统的默认时区,从而再次产生正确的结果.

If I run under Linux or OS X, TZ is set correctly and everything works. If I run this program in the shell on Windows, TZ is not set, so the function returns the operating system's default timezone, which again produces correct results.

如果我在 Cygwin shell 中运行,TZ 已设置 - 但由于我使用 MSVC 构建程序,使用 MSVC 自己的 stdc 库 - 它无法解释 Cygwin 的 TZ 变量.所以它默认为 GMT.

If I run in a Cygwin shell, TZ is set - but since I built the program using MSVC, using MSVC's own stdc library - it cannot interpret Cygwin's TZ variable. So it defaults to GMT.

如果程序是在 Cygwin 下用 GCC 构建的,我敢打赌它会在 Cygwin shell 中正常工作.

Had the program been built with GCC under Cygwin I bet it would work correctly in Cygwin shells.

所以答案是确保在调用诸如 localtime() 之类的 POSIX 时间函数的程序中,如果您希望时间函数在 Cygwin shell 下正常工作,您必须取消设置 TZ.

So the answer is to make sure in programs that call POSIX time functions like localtime(), if you want the time functions to work right under Cygwin shells you have to unset TZ.

我是这样做的:

void getLocalTime()
{
   #ifdef WIN32
   static bool unsetTZ = false;
   if ( !unsetTZ )
   {
      putenv( "TZ=" );
      unsetTZ = true;
   }
   #endif // !WIN32

   time_t t;
   t = time( NULL );
   elog << "timezone: " << getenv( "TZ" ) 
        << ", current local time: " << asctime( localtime( &t ));
}

相关文章