时间偏移计算关闭一分钟

问题描述 投票:0回答:2

我正在尝试用一个一致的 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 秒还短。

当我使用相同的格式打印两个时间值时,我可以看到它们正好相隔一小时。所以看来时区转换不是问题。

c++ datetime visual-c++ timezone
2个回答
3
投票

问题似乎来自我尝试时的时区转换(正如 SamVarshavchik 指出的那样)。不幸的是,我无法使用 Howard Hinnant 非常完整的 date 和 tz 库,因为它们需要一种机制来更新它们工作所需的 IANA 时区数据库,所以我当时采取了包装 Windows 本机调用的方式 -区域转换;即 TzSpecificLocalTimeToSystemTimeSystemTimeToTzSpecificLocalTime 函数。

但是,这些仅适用于 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...”版本。


2
投票

考虑使用这个免费的开源时区库,它可以通过非常简单的语法完全满足您的需求,并且适用于 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 更新

该库现在是 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';
}

演示

© www.soinside.com 2019 - 2024. All rights reserved.