如何在一个特定的时区(用偏移量)打印一个特定的“时间”值?

时间:2022-02-08 22:53:47

I'm writing an application in C that parses data files recorded by an external program (over which I have no control). It's storing binary data, one field of which is a time in standard UNIX "epoch" format (seconds since January 1st, 1970, UTC).

我正在用C编写一个应用程序,用于解析外部程序记录的数据文件(在这个程序中我没有控制权)。它存储二进制数据,其中一个字段是标准UNIX“纪元”格式的时间(从1970年1月1日开始的秒,UTC)。

Another field is the timezone, stored as an offset in seconds from UTC.

另一个字段是时区,以秒为单位存储在UTC。

Cool, I've got everything I need to make a date/time string representing that information in the timezone it was recorded in, right? Well... it doesn't seem so, and/or I'm not sure how to do it.

很好,我已经有了我需要的所有东西来创建一个日期/时间字符串来表示它所记录的时区中的信息,对吧?嗯…看起来不是这样的,我也不知道该怎么做。

I've boiled things down to a fairly simple test case:

我将其归结为一个相当简单的测试案例:

#include <stdio.h>
#include <time.h>

int main(void)
{
    time_t t;
    struct tm *tm;
    char buf[BUFSIZ];

    int offset = 4980; /* slightly bizarre, just to test this - an hour
                        * and 23 minutes ahead of UTC */

    t = time(NULL);
    tm = localtime(&t);

    strftime(buf, BUFSIZ, "%FT%T%z", tm);
    printf("before: %s\n", buf);

    /* since we're not telling localtime anything different,
     * compensate here (by subtracting applied offset, and adding
     * desired one): */
    t += offset - tm->tm_gmtoff;
    tm = localtime(&t);

    tm->tm_zone = "XYZ"; // not used -- but it was in an earlier version
    tm->tm_gmtoff = offset;

    // on macos, I used to also have %+, which referenced tm_zone
    strftime(buf, BUFSIZ, "%FT%T%z", tm);
    printf("after:  %s\n", buf);

    return 0;
}

When I run this on MacOS X 10.6, I get:

当我在MacOS X 10.6上运行这个时,我得到:

before: 2011-02-23T00:53:04-0800
after:  2011-02-23T10:16:04-0800

What I would expect (and in fact get, on a Linux box) would be:

我所期望的(事实上,在Linux上)是:

before: 2011-02-23T00:53:04-0800
after:  2011-02-23T10:16:04+0123

Do I need to change the TZ environment variable (and maybe call tzset)? It seems like there ought to be a way to manipulate the data structures and get the right thing, but the above certainly isn't working (on MacOS X 10.6, anyway -- works great on Linux).

是否需要更改TZ环境变量(或者调用tzset)?似乎应该有一种方法来操作数据结构并得到正确的结果,但是上面的方法显然不能工作(无论如何,在MacOS X 10.6上——在Linux上工作得很好)。

As a workaround, I suppose I can drop the %z from my format string and create that part myself.

作为一个解决方案,我假设我可以从我的格式字符串中删除%z并自己创建那个部分。

Ideally, though, I'd like to have either a modification of my struct tm, or some other function call that I can use (like strftime, but with an extra parameter or something, or perhaps an alternate form of localtime, instead), that would make things do the right thing.

不过,理想情况下,我希望对我的struct tm进行修改,或者使用一些其他函数调用(比如strftime,但是使用一个额外的参数或其他东西,或者使用另一种localtime形式),这样可以让事情做正确的事情。

Since Linux seems to behave (though even there, the above solution isn't quite ideal, because I'm fudging my time_t value; I'd prefer to have a parameter that changes how the struct tm is calculated), is this something that I should report as a bug against MacOS?

因为Linux似乎表现得很好(即使在那里,上面的解决方案也不是很理想,因为我在伪造我的time_t值;我希望有一个参数来更改struct tm的计算方式),这是我应该报告的针对MacOS的bug吗?

Alternately, is there a different set of library routines I could call, even if that ends up requiring a third-party (something from the GNU folks, I'm imagining) library? I'd prefer to keep to C, though I'd consider ObjC or C++ options.

或者,我是否可以调用另一组库例程,即使这最终需要一个第三方库(我想象的是出自GNU团队)?我倾向于保留C,尽管我认为ObjC或c++选项。

3 个解决方案

#1


2  

I prefer to use mktime() instead of modifying the time_t value. However, this applies the TZ offset (just like the localtime() solution), so a mkgmtime() implementation is required.

我更喜欢使用mktime()而不是修改time_t值。但是,这将应用TZ偏移量(就像localtime()解决方案一样),因此需要一个mkgmtime()实现。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

static void set_timezone(char *tz)
{
  static char var[1024];

  snprintf(var, sizeof(var), "TZ=%s", tz);
  putenv(var);
  tzset();
}

static time_t mkgmtime(struct tm *tm)
{
  char var[1024];
  time_t t;

  snprintf(var, sizeof(var), "%s", getenv("TZ") ? getenv("TZ") : "");

  set_timezone("GMT0");

  t = mktime(tm);

  set_timezone(var);

  return t;
}

int main(void)
{
  time_t t;
  struct tm tm;
  char buf[BUFSIZ];
  int offset = 4980; /* slightly bizarre, just to test this - an hour
                      * and 23 minutes ahead of UTC */

  t = time(NULL);
  tm = *localtime(&t);
  strftime(buf, BUFSIZ, "%FT%T%z", &tm);
  printf("before: %s\n", buf);

  tm = *gmtime(&t);
  tm.tm_sec += offset;
  mkgmtime(&tm);

  strftime(buf, BUFSIZ, "%FT%T%z", &tm);
  printf("after:  %s\n", buf);

  return 0;
}

#2


0  

I think that the best approach would be

我认为最好的办法是

  1. read your value into a time_t variable;

    将值读入time_t变量;

  2. set the TZ environment variable following these guidelines. See also this page for info on how you should specify TZ;

    按照这些指导原则设置TZ环境变量。有关如何指定TZ的信息,请参阅本页;

  3. invoke localtime().

    调用本地时间()。

This should be guaranteed to work, althought I can't test it on a MacOS platform.

这是可以保证的,我不能在MacOS平台上测试它。

#3


-1  

I personally like to embed small python scripts in my bash code. Python has many powerful out-of-the-box libraries for functionalities like you require.

我个人喜欢在我的bash代码中嵌入小的python脚本。Python有许多功能强大的开箱即用的库,用于您需要的功能。

You can embed python code in a bash script as follows (Assuming python is installed)

可以将python代码嵌入到bash脚本中,如下所示(假设已经安装了python)

python << END
from pytz import timezone    

south_africa = timezone('Africa/Johannesburg')
sa_time = datetime.now(south_africa)
print sa_time.strftime('%Y-%m-%d_%H-%M-%S')
END

You can change the timezone setting as you wish to display in different timezones. Have a look at http://pytz.sourceforge.net/ for more details.

您可以更改时区设置,以便在不同的时区显示。有关详细信息,请参阅http://pytz.sourceforge.net/。

#1


2  

I prefer to use mktime() instead of modifying the time_t value. However, this applies the TZ offset (just like the localtime() solution), so a mkgmtime() implementation is required.

我更喜欢使用mktime()而不是修改time_t值。但是,这将应用TZ偏移量(就像localtime()解决方案一样),因此需要一个mkgmtime()实现。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

static void set_timezone(char *tz)
{
  static char var[1024];

  snprintf(var, sizeof(var), "TZ=%s", tz);
  putenv(var);
  tzset();
}

static time_t mkgmtime(struct tm *tm)
{
  char var[1024];
  time_t t;

  snprintf(var, sizeof(var), "%s", getenv("TZ") ? getenv("TZ") : "");

  set_timezone("GMT0");

  t = mktime(tm);

  set_timezone(var);

  return t;
}

int main(void)
{
  time_t t;
  struct tm tm;
  char buf[BUFSIZ];
  int offset = 4980; /* slightly bizarre, just to test this - an hour
                      * and 23 minutes ahead of UTC */

  t = time(NULL);
  tm = *localtime(&t);
  strftime(buf, BUFSIZ, "%FT%T%z", &tm);
  printf("before: %s\n", buf);

  tm = *gmtime(&t);
  tm.tm_sec += offset;
  mkgmtime(&tm);

  strftime(buf, BUFSIZ, "%FT%T%z", &tm);
  printf("after:  %s\n", buf);

  return 0;
}

#2


0  

I think that the best approach would be

我认为最好的办法是

  1. read your value into a time_t variable;

    将值读入time_t变量;

  2. set the TZ environment variable following these guidelines. See also this page for info on how you should specify TZ;

    按照这些指导原则设置TZ环境变量。有关如何指定TZ的信息,请参阅本页;

  3. invoke localtime().

    调用本地时间()。

This should be guaranteed to work, althought I can't test it on a MacOS platform.

这是可以保证的,我不能在MacOS平台上测试它。

#3


-1  

I personally like to embed small python scripts in my bash code. Python has many powerful out-of-the-box libraries for functionalities like you require.

我个人喜欢在我的bash代码中嵌入小的python脚本。Python有许多功能强大的开箱即用的库,用于您需要的功能。

You can embed python code in a bash script as follows (Assuming python is installed)

可以将python代码嵌入到bash脚本中,如下所示(假设已经安装了python)

python << END
from pytz import timezone    

south_africa = timezone('Africa/Johannesburg')
sa_time = datetime.now(south_africa)
print sa_time.strftime('%Y-%m-%d_%H-%M-%S')
END

You can change the timezone setting as you wish to display in different timezones. Have a look at http://pytz.sourceforge.net/ for more details.

您可以更改时区设置,以便在不同的时区显示。有关详细信息,请参阅http://pytz.sourceforge.net/。