【问题标题】:C++: converting Unix time to non-local timezoneC++:将 Unix 时间转换为非本地时区
【发布时间】:2018-01-17 16:54:17
【问题描述】:

我正在尝试将time_t 变量中保存的时间转换为某个时区中的struct tm*,该时区不是本地时区。

this post 的基础上,讨论了从struct tm*time_t 的逆运算,我编写了以下函数:

struct tm* localtime_tz(time_t* t, std::string timezone) {

  struct tm* ret;
  char* tz;

  tz = std::getenv("TZ"); // Store currently set time zone                                                                                                                           

  // Set time zone                                                                                                                                                                   
  setenv("TZ", timezone.c_str(), 1);
  tzset();

  std::cout << "Time zone set to " << std::getenv("TZ") << std::endl;

  ret = std::localtime(t); // Convert given Unix time to local time in time zone                                                                                                     
  std::cout << "Local time is: " << std::asctime(ret);
  std::cout << "UTC time is: " << std::asctime(std::gmtime(t));

  // Reset time zone to stored value                                                                                                                                                 
  if (tz)
    setenv("TZ", tz, 1);
  else
    unsetenv("TZ");
  tzset();

  return ret;

}

但是,转换失败,我得到了

Time zone set to CEST
Local time is: Wed Aug  9 16:39:38 2017
UTC time is: Wed Aug  9 16:39:38 2017

即本地时间设置为 UTC 时间,而不是 CEST 的 UTC+2。

【问题讨论】:

    标签: c++ time timezone posix


    【解决方案1】:

    date等工具显示的时区缩写不是时区名称。 IANA 时区数据库使用相关区域内的主要城市。原因是缩写不是唯一的,而且它们没有传达足够的信息来转换过去的日期,因为地点会切换时区。

    此外,POSIX 指定必须以特定方式解析 TZ 变量,并且它几乎建议像 UTC 一样处理裸时区缩写(但使用不同的缩写)。

    您必须使用实际的时区名称,例如 Europe/Berlin,或 POSIX 时区说明符,例如 CET-1CEST

    【讨论】:

      【解决方案2】:

      如果有人想以线程安全的方式执行此操作(无需设置诸如环境变量之类的全局变量),我将提供额外的答案。此答案需要 C++11(或更好)和Howard Hinnant's free, open-source timezone library,已移植到 linux、macOS 和 Windows。

      这个库建立在&lt;chrono&gt; 之上,将其扩展到日历和时区。可以通过该库与 C 计时 API(例如 struct tm)进行交互,但此类设施并未内置于库中。该库使 C API 变得不必要,除非您必须与使用 C API 的其他代码交互。

      例如,有一种称为date::zoned_seconds 的类型将system_clock::time_pointseconds 精度除外)与date::time_zone 结合起来,以在任意时区创建本地时间。作为described in more detail here,以下是将zoned_seconds 转换为std::tm 的方法:

      std::tm
      to_tm(date::zoned_seconds tp)
      {
          using namespace date;
          using namespace std;
          using namespace std::chrono;
          auto lt = tp.get_local_time();
          auto ld = floor<days>(lt);
          time_of_day<seconds> tod{lt - ld};  // <seconds> can be omitted in C++17
          year_month_day ymd{ld};
          tm t{};
          t.tm_sec  = tod.seconds().count();
          t.tm_min  = tod.minutes().count();
          t.tm_hour = tod.hours().count();
          t.tm_mday = unsigned{ymd.day()};
          t.tm_mon  = unsigned{ymd.month()} - 1;
          t.tm_year = int{ymd.year()} - 1900;
          t.tm_wday = unsigned{weekday{ld}};
          t.tm_yday = (ld - local_days{ymd.year()/jan/1}).count();
          t.tm_isdst = tp.get_info().save != minutes{0};
          return t;
      }
      

      使用此构建块,将time_t 转换为任意时区的tm 几乎变得微不足道(无需设置全局):

      std::tm
      localtime_tz(time_t t, const std::string& timezone)
      {
          using namespace date;
          using namespace std::chrono;
          return to_tm({timezone, floor<seconds>(system_clock::from_time_t(t))});
      }
      

      如果您使用的是 C++17,则可以删除 using namespace date,因为 floor&lt;seconds&gt; 将完全在 namespace std::chrono 中提供。

      上面的代码从stringtimezone创建了一个zoned_seconds,并从time_tt派生了一个system_clock::time_pointzoned_seconds 在概念上是 pair&lt;time_zone, system_clock::time_point&gt;,但具有任意精度。它也可以被认为是pair&lt;time_zone, local_time&gt;,因为time_zone 知道如何在UTC 和本地时间之间进行映射。所以上面大部分用户编写的代码只是从zoned_seconds中提取本地时间并将其放入struct tm中。

      上面的代码可以这样执行:

      auto tm = localtime_tz(time(nullptr), "America/Anchorage");
      

      如果您不需要tm 中的结果,而是可以保留在&lt;chrono&gt; 系统中,则timezone library 中有非常好的功能用于解析和格式化zoned_seconds,例如:

      cout << zoned_seconds{"America/Anchorage", floor<seconds>(system_clock::now())} << '\n';
      

      样本输出:

      2017-08-10 16:50:28 AKDT
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-06-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-05-20
        • 2015-12-22
        • 2019-03-24
        相关资源
        最近更新 更多