我正在尝试用一个一致的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年5月25日09:45:18.1964787+0059>
期望值是当我使用旧类执行相同操作时得到的值。问题似乎出现在我试图获得lclNow
和now
之间的差异的地方。
目前我在UTC+1(由于夏令时生效)。然而,diff
的值总是35999995635
。在Windows中的Visual C++上,刻度是100纳秒,因此每秒有10000000个刻度,这意味着diff
的值是3599.9995秒,这还差一个小时所需的3600秒。
当我使用相同的格式打印两个时间值时,我可以看到它们正好相隔一个小时。因此,时区翻译似乎不是问题所在。
问题似乎来自我尝试的时区转换(正如SamVarshavchik所指出的)。不幸的是,我无法使用Howard Hinnant非常完整的日期和tz库,因为它们需要一种机制来更新IANA时区数据库,这是它们工作所必需的,所以我不得不包装Windows本地调用来进行时区转换;即TzSpecificLocalTimeToSystemTime和SystemTimeToTzSpecific LocalTime函数。
然而,这些仅适用于SYSTEMTIME,而不适用于time_point
。这意味着我可以快速而简单地选择将time_point
转换为FILETIME
(只需修改"epoch"),将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