C++:将 Unix 时间转换为非本地时区
C++: converting Unix time to non-local timezone
我正在尝试将 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。
date
等工具显示的时区缩写,并非时区名称。 IANA 时区数据库使用相关区域内的主要城市。原因是缩写不是唯一的,它们没有传达足够的信息来转换过去的日期,因为地方会切换时区。
此外,POSIX 指定必须以特定方式解析 TZ
变量,它几乎建议像 UTC 一样处理纯时区缩写(但使用不同的缩写)。
相反,您必须使用实际的时区名称,例如 Europe/Berlin
,或 POSIX 时区说明符,例如 CET-1CEST
.
如果有人想以线程安全的方式执行此操作(无需设置全局变量,例如环境变量),我将提供一个额外的答案。这个答案需要 C++11(或更好)和 Howard Hinnant's free, open-source timezone library,它已移植到 linux、macOS 和 Windows.
这个库建立在 <chrono>
的基础上,将其扩展到日历和时区。可以使用此库与 C 时序 API(例如 struct tm
)进行交互,但库中并未内置此类功能。该库使 C API 变得不必要,除非您必须与使用 C API.
的其他代码交互
例如,有一个名为 date::zoned_seconds
的类型,它将 system_clock::time_point
(精度为 seconds
的除外)与 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<seconds>
将在 namespace std::chrono
.
中完全提供
上面的代码从 string
timezone
创建一个 zoned_seconds
和从 time_t
t
派生的 system_clock::time_point
。 zoned_seconds
在概念上是 pair<time_zone, system_clock::time_point>
但具有任意精度。它也可以被认为是 pair<time_zone, local_time>
因为 time_zone
知道如何在 UTC 和本地时间之间映射。所以上面的大部分用户编写的代码只是从 zoned_seconds
中提取本地时间并将其放入 struct tm
.
上面的代码可以这样练习:
auto tm = localtime_tz(time(nullptr), "America/Anchorage");
如果您不需要 tm
中的结果,而是可以留在 <chrono>
系统中,则 timezone library 中有非常好的功能用于解析和格式化zoned_seconds
,例如:
cout << zoned_seconds{"America/Anchorage", floor<seconds>(system_clock::now())} << '\n';
示例输出:
2017-08-10 16:50:28 AKDT
我正在尝试将 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。
date
等工具显示的时区缩写,并非时区名称。 IANA 时区数据库使用相关区域内的主要城市。原因是缩写不是唯一的,它们没有传达足够的信息来转换过去的日期,因为地方会切换时区。
此外,POSIX 指定必须以特定方式解析 TZ
变量,它几乎建议像 UTC 一样处理纯时区缩写(但使用不同的缩写)。
相反,您必须使用实际的时区名称,例如 Europe/Berlin
,或 POSIX 时区说明符,例如 CET-1CEST
.
如果有人想以线程安全的方式执行此操作(无需设置全局变量,例如环境变量),我将提供一个额外的答案。这个答案需要 C++11(或更好)和 Howard Hinnant's free, open-source timezone library,它已移植到 linux、macOS 和 Windows.
这个库建立在 <chrono>
的基础上,将其扩展到日历和时区。可以使用此库与 C 时序 API(例如 struct tm
)进行交互,但库中并未内置此类功能。该库使 C API 变得不必要,除非您必须与使用 C API.
例如,有一个名为 date::zoned_seconds
的类型,它将 system_clock::time_point
(精度为 seconds
的除外)与 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<seconds>
将在 namespace std::chrono
.
上面的代码从 string
timezone
创建一个 zoned_seconds
和从 time_t
t
派生的 system_clock::time_point
。 zoned_seconds
在概念上是 pair<time_zone, system_clock::time_point>
但具有任意精度。它也可以被认为是 pair<time_zone, local_time>
因为 time_zone
知道如何在 UTC 和本地时间之间映射。所以上面的大部分用户编写的代码只是从 zoned_seconds
中提取本地时间并将其放入 struct tm
.
上面的代码可以这样练习:
auto tm = localtime_tz(time(nullptr), "America/Anchorage");
如果您不需要 tm
中的结果,而是可以留在 <chrono>
系统中,则 timezone library 中有非常好的功能用于解析和格式化zoned_seconds
,例如:
cout << zoned_seconds{"America/Anchorage", floor<seconds>(system_clock::now())} << '\n';
示例输出:
2017-08-10 16:50:28 AKDT