我正在尝试用一个一致的 API 替换许多不同的时间类。但是我最近遇到了一个问题,无法正确序列化时区偏移量。请注意,我正在尝试复制系统中已广泛使用的现有格式。
格式应为
YYYY-mm-DD HH:MM:SS.xxxxxxx -HHMM
,其中 x
表示亚秒精度,最后一个 -HHMM
是相对于 UTC 的 TZ 偏移量。
代码:
using namespace My::Time;
namespace chrn = std::chrono;
time_point now = clock::now();
time_point lclNow = getDefaultCalendarProvider()->toLocal(now);
duration diff{ lclNow - now };
std::wstring sign = diff > duration::zero() ? L" +" : L" -";
duration ms{ now.time_since_epoch().count() % duration::period::den };
int diffHrs = popDurationPart<chrn::hours>(diff).count();
int diffMins{ abs(chrn::duration_cast<chrn::minutes>(diff).count()) };
std::cout << Format{ lclNow, TimeZone::UTC, L" %Y-%m-%d %H:%M:%S." } << ms.count()
<< sign << std::setfill(L'0') << std::setw(2) << diffHrs
<< std::setfill(L'0') << std::setw(2) << diffMins << std::endl;
问题:
预计:<2016-05-25 09:45:18.1970000 +0100> 实际:< 2016-05-25 09:45:18.1964787 +0059>
预期值是我使用旧类进行相同操作时得到的值。问题似乎出在我试图找出
lclNow
和 now
之间的区别。
目前我处于 UTC +1(由于 DST 生效)。然而,
diff
值始终为 35999995635
。在 Windows 中的 Visual C++ 上,刻度为 100 ns,因此每秒有 10000000 个刻度,这意味着 diff
值是 3599.9995 秒,这比我需要 1 小时所需的 3600 秒还短。
当我使用相同的格式打印两个时间值时,我可以看到它们正好相隔一小时。所以看来时区转换不是问题。
问题似乎来自我尝试时的时区转换(正如 SamVarshavchik 指出的那样)。不幸的是,我无法使用 Howard Hinnant 非常完整的 date 和 tz 库,因为它们需要一种机制来更新它们工作所需的 IANA 时区数据库,所以我当时采取了包装 Windows 本机调用的方式 -区域转换;即 TzSpecificLocalTimeToSystemTime 和 SystemTimeToTzSpecificLocalTime 函数。
但是,这些仅适用于 SYSTEMTIME 而不是
time_point
。这意味着我采取了快速而简单的选择,将 time_point
转换为 FILETIME
(只需修改“纪元”)并将 FILETIME
转换为 SYSTEMTIME
,然后再将其传递给上述两个函数之一。这导致时间值在被推入 SYSTEMTIME
结构(仅保存毫秒分辨率)时被截断。结果是,虽然我的日期准确,但将日期转换回原始值时并不完全准确。
新解决方案没有将基本
time_point
到 time_point
翻译进行日历映射。它使用以下代码计算 std::chrono::minutes
中的偏移量(其中 zoneInfo
是 TIME_ZONE_INFORMATION):
time_point WindowsTzDateProvider::doToUtc(const time_point& inLocal) const {
return inLocal + getBias(inLocal);
}
time_point WindowsTzDateProvider::doToLocal(const time_point& inUtc) const {
return inUtc - getBias(inUtc);
}
std::chrono::minutes WindowsTzDateProvider::doGetBias(const time_point& input) const {
bool isDst = CalendarDateProvider::isDstInEffect(input);
minutes baseBias{ zoneInfo.Bias };
minutes extraBias{ isDst ? zoneInfo.DaylightBias : zoneInfo.StandardBias };
return baseBias + extraBias;
}
bool CalendarDateProvider::isDstInEffect(const time_point& t) {
time_t epochTime = clock::to_time_t(t);
tm out;
#ifdef WIN32
localtime_s(&out, &epochTime);
#else
localtime_r(&out, &epochTime);
#endif
return out.tm_isdst > 0;
}
注意:我对类使用非虚拟接口习惯用法,因此方法的“do...”版本。
考虑使用这个免费的开源时区库,它可以通过非常简单的语法完全满足您的需求,并且适用于 VS-2013 及更高版本:
#include "tz.h"
#include <iostream>
int
main()
{
using namespace date;
using namespace std::chrono;
auto t = make_zoned(current_zone(), system_clock::now());
std::cout << format("%F %T %z", t) << '\n';
}
这应该为您输出:
2016-05-25 09:45:18.1970000 +0100
该库现在是 C++20 的一部分
<chrono>
并随 MSVC 一起提供。语法与上面所示略有不同:
#include <chrono>
#include <format>
#include <iostream>
int
main()
{
std::chrono::zoned_time t{std::chrono::current_zone(),
std::chrono::system_clock::now()};
std::cout << std::format("{:%F %T %z}", t) << '\n';
}