简体   繁体   English

C ++:将Unix时间转换为非本地时区

[英]C++: converting Unix time to non-local timezone

I am trying to convert a time held in a variable of time_t to a struct tm* in some time zone which is not the local time zone. 我正在尝试将time_t变量中保存的时间转换为不是本地时区的某个时区的struct tm*

Building on this post , which discusses the inverse operation going from struct tm* to time_t , I have written the following function: 此文章的基础上, 该文章讨论了从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;

}

However, the conversion fails, and I get 但是,转换失败,我得到了

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

ie local time is set to UTC time, instead of UTC+2 for CEST. 即本地时间设置为UTC时间,而不是CEST的UTC + 2。

The time zone abbreviations shown by date and other tools are not the names of time zones. date和其他工具显示的时区缩写不是时区的名称。 The IANA time zone database uses a major city within the relevant region instead. IANA时区数据库改为使用相关区域内的主要城市。 The reason for that is that the abbreviations are not unique, and they do not convey sufficient information to convert past dates because places switch time zones. 这样做的原因是,缩写不是唯一的,并且它们不能传递足够的信息来转换过去的日期,因为位置会切换时区。

In addition, POSIX specifies that the TZ variable has to be parsed in a specific way, and it pretty much suggests to treat a bare time zone abbreviation like UTC (but with a different abbreviation). 另外,POSIX指定必须以特定的方式解析TZ变量,并且它几乎建议像UTC那样处理裸机时区缩写(但缩写不同)。

Instead, you have to use the actual time zone name, such as Europe/Berlin , or a POSIX time zone specifier such as CET-1CEST . 相反,您必须使用实际的时区名称(例如Europe/Berlin )或POSIX时区说明符(例如CET-1CEST

I'm providing an additional answer in case someone wants to do this in a way that is thread safe (without setting a global such as an environment variable). 如果有人想要以线程安全的方式(无需设置诸如环境变量之类的全局变量)来执行此操作,我将提供一个附加的答案。 This answer requires C++11 (or better) and Howard Hinnant's free, open-source timezone library , which has been ported to linux, macOS and Windows. 要获得此答案,需要C ++ 11(或更高版本)和Howard Hinnant的免费开源时区库 ,该已移植到linux,macOS和Windows。

This library builds on <chrono> , extending it to calendars and timezones. 该库建立在<chrono>之上,并将其扩展到日历和时区。 One can interface with the C timing API such as struct tm with this library, but such facilities aren't built in to the library. 可以使用该库与C计时API(例如struct tm进行接口,但是此类功能并未内置于该库中。 This library makes the C API unnecessary, unless you have to interface with other code that uses the C API. 该库使C API变得不必要,除非您必须与使用C API的其他代码交互。

For example there is a type called date::zoned_seconds that couples a system_clock::time_point (except with seconds precision) with a date::time_zone to create local time in an arbitrary time zone. 例如,有一个名为date::zoned_seconds的类型, date::zoned_seconds system_clock::time_point (精度为seconds除外)与date::time_zone耦合date::zoned_seconds ,以在任意时区中创建本地时间。 As described in more detail here , here is how you can convert a zoned_seconds into a std::tm : 这里更详细的描述 ,这是如何将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;
}

Using this building block, it becomes almost trivial to convert a time_t to a tm in an arbitrary time zone (without setting a global): 使用此构造块,在任意时区(无需设置全局)中将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))});
}

If you're using C++17, you can remove using namespace date as floor<seconds> will be completely provided within namespace std::chrono . 如果您使用的是C ++ 17,则可以using namespace date删除,因为floor<seconds>将完全在namespace std::chrono

The above code creates a zoned_seconds from the string timezone and a system_clock::time_point derived from the time_t t . 上面的代码从string timezone创建一个zoned_seconds ,并从time_t t派生一个system_clock::time_point The zoned_seconds is conceptually a pair<time_zone, system_clock::time_point> but of arbitrary precision. zoned_seconds在概念上是pair<time_zone, system_clock::time_point>但具有任意精度。 And it can also be thought of as a pair<time_zone, local_time> since the time_zone knows how to map between UTC and local time. 由于time_zone知道如何在UTC和本地时间之间进行映射,因此也可以将其视为pair<time_zone, local_time> So most of the user-written code above is just in extracting the local time from the zoned_seconds and placing that into a struct tm . 因此,上面大多数用户编写的代码只是从zoned_seconds中提取本地时间并将其放入struct tm

The above code can be exercised like this: 上面的代码可以这样执行:

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

If you don't need the result in a tm but can instead stay within the <chrono> system, there exists very nice features in the timezone library for parsing and formatting the zoned_seconds , for example: 如果您不需要tm的结果,而是可以留在<chrono>系统中,则时区库中存在非常好的功能,可用于解析和格式化zoned_seconds ,例如:

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

Sample output: 样本输出:

2017-08-10 16:50:28 AKDT

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM