[英]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.