首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >在C++中将带有时区的日期时间字符串转换为UNIX时间戳的快速方法

在C++中将带有时区的日期时间字符串转换为UNIX时间戳的快速方法
EN

Stack Overflow用户
提问于 2019-05-17 17:43:56
回答 2查看 1.4K关注 0票数 6

我希望将包含日期时间字符串的大型文件转换为自C++中的UNIX (1970年1月1日)以来的秒数。我需要非常快的计算,因为我需要处理大量的日期时间。

到目前为止我已经尝试了两种选择。第一种方法是使用在time.h中定义的mktime。我尝试的第二个选项是Hinnant的带有时区扩展的数据库

下面是我用来比较mktime和Howard Hinnant tz性能的代码:

代码语言:javascript
运行
复制
for( int i=0; i<RUNS; i++){
    genrandomdate(&time_str);

    time_t t = mktime(&time_str);

}

auto tz = current_zone()
for( int i=0; i<RUNS; i++){

    genrandomdate(&time_str);
    auto ymd = year{time_str.tm_year+1900}/(time_str.tm_mon+1)/time_str.tm_mday;
    auto tcurr = make_zoned(tz, local_days{ymd} + 
            seconds{time_str.tm_hour*3600 + time_str.tm_min*60 + time_str.tm_sec}, choose::earliest);
    auto tbase = make_zoned("UTC", local_days{January/1/1970});
    auto dp = tcurr.get_sys_time() - tbase.get_sys_time() + 0s;

}

比较结果如下:

代码语言:javascript
运行
复制
time for mktime : 0.000142s
time for tz : 0.018748s

与mktime相比,tz的性能不佳。我想要比mktime更快的东西,因为当重复使用大量迭代时,mktime也非常慢。Java提供了一种非常快速的方法来实现这一点,但是当时区也在起作用时,我不知道有什么C++替代方案。

注意:在没有时区的情况下,Hinnant的日期工作非常快(甚至超过Java)。但这还不足以满足我的要求。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2019-05-18 02:47:09

我发现谷歌的CCTZ可以做同样的事情。

票数 -2
EN

Stack Overflow用户

发布于 2019-05-17 18:40:52

为了优化Hinnant的数据库的使用,您可以做一些事情

代码语言:javascript
运行
复制
auto tbase = make_zoned("UTC", local_days{January/1/1970});

查找时区(甚至是"UTC")需要对数据库进行二进制搜索,以查找具有该名称的时区。执行一次查找并重用结果会更快:

代码语言:javascript
运行
复制
// outside of loop:
auto utc_tz = locate_zone("UTC");

// inside of loop:
auto tbase = make_zoned(utc_tz, local_days{January/1/1970});

此外,我注意到tbase与循环无关,所以可以将整个东西移出循环之外:

代码语言:javascript
运行
复制
// outside of loop:
auto tbase = make_zoned("UTC", local_days{January/1/1970});

这里还有一个更小的优化要做。更改:

代码语言:javascript
运行
复制
auto dp = tcurr.get_sys_time() - tbase.get_sys_time() + 0s;

至:

代码语言:javascript
运行
复制
auto dp = tcurr.get_sys_time().time_since_epoch();

这完全消除了对tbase的需求。tcurr.get_sys_time().time_since_epoch()是从世界协调时1970-01:00:00开始的持续时间,以秒为单位.对于这个例子,秒的精度是正确的,因为输入有秒的精度。

Style nit:尽量避免将转换因素放入代码中。这意味着改变:

代码语言:javascript
运行
复制
auto tcurr = make_zoned(tz, local_days{ymd} + 
        seconds{time_str.tm_hour*3600 + time_str.tm_min*60 + time_str.tm_sec}, choose::earliest);

至:

代码语言:javascript
运行
复制
auto tcurr = make_zoned(tz, local_days{ymd} + hours{time_str.tm_hour} + 
                        minutes{time_str.tm_min} + seconds{time_str.tm_sec},
                        choose::earliest);

如果这个时区也是固定的,是否有办法避免这种二进制搜索。我的意思是,我们可以得到时区偏移量和DST偏移量,并手动调整时间点。

如果您不在Windows上,请尝试使用-DUSE_OS_TZDB=1进行编译。这使用了数据库的编译形式,可以具有更高的性能。

有一种方法可以获得偏移量并手动应用它(信息),但是,除非您知道偏移量不会随time_point值的变化而改变,否则您将最终重新发明make_zoned下的逻辑。

但是,如果您确信您的UTC偏移量是恒定的,那么您可以这样做:

代码语言:javascript
运行
复制
auto tz = current_zone();
// Use a sample time_point to get the utc_offset:
auto info = tz->get_info(
    local_days{year{time_str.tm_year+1900}/(time_str.tm_mon+1)/time_str.tm_mday}
      + hours{time_str.tm_hour} + minutes{time_str.tm_min}
      + seconds{time_str.tm_sec});
seconds utc_offset = info.first.offset;
for( int i=0; i<RUNS; i++){

    genrandomdate(&time_str);
    // Apply the offset manually:
    auto ymd = year{time_str.tm_year+1900}/(time_str.tm_mon+1)/time_str.tm_mday;
    auto tp = sys_days{ymd} + hours{time_str.tm_hour} +
              minutes{time_str.tm_min} + seconds{time_str.tm_sec} - utc_offset;
    auto dp = tp.time_since_epoch();
}

更新--我自己的定时测试

我使用Xcode 10.2.1运行macOS 10.14.4。我已经创建了一台相对安静的机器:时间机器备份没有运行。邮件没有运行。iTunes没有运行。

我有以下应用程序,它使用几种不同的技术实现愿望转换,这取决于预处理器的设置:

代码语言:javascript
运行
复制
#include "date/tz.h"
#include <cassert>
#include <iostream>
#include <vector>

constexpr int RUNS = 1'000'000;
using namespace date;
using namespace std;
using namespace std::chrono;

vector<tm>
gendata()
{
    vector<tm> v;
    v.reserve(RUNS);
    auto tz = current_zone();
    auto tp = floor<seconds>(system_clock::now());
    for (auto i = 0; i < RUNS; ++i, tp += 1s)
    {
        zoned_seconds zt{tz, tp};
        auto lt = zt.get_local_time();
        auto d = floor<days>(lt);
        year_month_day ymd{d};
        auto s = lt - d;
        auto h = floor<hours>(s);
        s -= h;
        auto m = floor<minutes>(s);
        s -= m;
        tm x{};
        x.tm_year = int{ymd.year()} - 1900;
        x.tm_mon = unsigned{ymd.month()} - 1;
        x.tm_mday = unsigned{ymd.day()};
        x.tm_hour = h.count();
        x.tm_min = m.count();
        x.tm_sec = s.count();
        x.tm_isdst = -1;
        v.push_back(x);
    }
    return v;
}


int
main()
{

    auto v = gendata();
    vector<time_t> vr;
    vr.reserve(v.size());
    auto tz = current_zone();  // Using date
    sys_seconds begin;         // Using date, optimized
    sys_seconds end;           // Using date, optimized
    seconds offset{};          // Using date, optimized

    auto t0 = steady_clock::now();
    for(auto const& time_str : v)
    {
#if 0  // Using mktime
        auto t = mktime(const_cast<tm*>(&time_str));
        vr.push_back(t);
#elif 1  // Using date, easy
        auto ymd = year{time_str.tm_year+1900}/(time_str.tm_mon+1)/time_str.tm_mday;
        auto tp = local_days{ymd} + hours{time_str.tm_hour} +
                  minutes{time_str.tm_min} + seconds{time_str.tm_sec};
        zoned_seconds zt{tz, tp};
        vr.push_back(zt.get_sys_time().time_since_epoch().count());
#elif 0  // Using date, optimized
        auto ymd = year{time_str.tm_year+1900}/(time_str.tm_mon+1)/time_str.tm_mday;
        auto tp = local_days{ymd} + hours{time_str.tm_hour} +
                  minutes{time_str.tm_min} + seconds{time_str.tm_sec};
        sys_seconds zt{(tp - offset).time_since_epoch()};
        if (!(begin <= zt && zt < end))
        {
            auto info = tz->get_info(tp);
            offset = info.first.offset;
            begin = info.first.begin;
            end = info.first.end;
            zt = sys_seconds{(tp - offset).time_since_epoch()};
        }
        vr.push_back(zt.time_since_epoch().count());
#endif
    }
    auto t1 = steady_clock::now();

    cout << (t1-t0)/v.size() << " per conversion\n";
    auto i = vr.begin();
    for(auto const& time_str : v)
    {
        auto t = mktime(const_cast<tm*>(&time_str));
        assert(t == *i);
        ++i;
    }
}

每个解决方案都是定时的,然后对照基线解决方案检查正确性。每个解决方案转换1,000,000个时间戳,这些时间戳在时间上都比较接近,并输出每次转换的平均时间。

我提出了四种解决方案,以及它们在我的环境中的时间安排:

1.使用mktime

输出:

代码语言:javascript
运行
复制
3849ns per conversion

2.以最简单的方式使用tz.hUSE_OS_TZDB=0

输出:

代码语言:javascript
运行
复制
3976ns per conversion

这比mktime解决方案稍慢。

3.以最简单的方式使用tz.hUSE_OS_TZDB=1

输出:

代码语言:javascript
运行
复制
55ns per conversion

这比上述两种解决方案要快得多。但是,此解决方案在Windows上(此时)不可用,而且在macOS上也不支持库中的闰秒部分(不在此测试中使用)。这两个限制都是由操作系统如何发布时区数据库造成的。

4.以一种优化的方式使用tz.h,利用临时分组时间戳的先验知识。如果假设是错误的,则性能会受到影响,但正确性不会受到损害。

输出:

代码语言:javascript
运行
复制
15ns per conversion

这个结果大致独立于USE_OS_TZDB设置。但是性能依赖于输入数据不经常更改UTC偏移量这一事实。对于不明确或不存在的本地时间点,此解决方案也是不小心的。这样的本地时间点没有到UTC的唯一映射。如果遇到这样的本地时间点,解决方案2和3将抛出异常。

运行时错误与USE_OS_TZDB

OP在Ubuntu上运行时得到了这个堆栈转储。这种崩溃发生在第一次访问时区数据库时。崩溃是由操作系统为p线程库提供的空存根函数造成的。修复方法是显式地链接到p线程库(包括命令行中的-lpthread )。

代码语言:javascript
运行
复制
==20645== Process terminating with default action of signal 6 (SIGABRT)
==20645==    at 0x5413428: raise (raise.c:54)
==20645==    by 0x5415029: abort (abort.c:89)
==20645==    by 0x4EC68F6: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25)
==20645==    by 0x4ECCA45: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25)
==20645==    by 0x4ECCA80: std::terminate() (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25)
==20645==    by 0x4ECCCB3: __cxa_throw (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25)
==20645==    by 0x4EC89B8: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25)
==20645==    by 0x406AF9: void std::call_once<date::time_zone::init() const::{lambda()#1}>(std::once_flag&, date::time_zone::init() const::{lambda()#1}&&) (mutex:698)
==20645==    by 0x40486C: date::time_zone::init() const (tz.cpp:2114)
==20645==    by 0x404C70: date::time_zone::get_info_impl(std::chrono::time_point<date::local_t, std::chrono::duration<long, std::ratio<1l, 1l> > >) const (tz.cpp:2149)
==20645==    by 0x418E5C: date::local_info date::time_zone::get_info<std::chrono::duration<long, std::ratio<1l, 1l> > >(std::chrono::time_point<date::local_t, std::chrono::duration<long, std::ratio<1l, 1l> > >) const (tz.h:904)
==20645==    by 0x418CB2: std::chrono::time_point<std::chrono::_V2::system_clock, std::common_type<std::chrono::duration<long, std::ratio<1l, 1l> >, std::chrono::duration<long, std::ratio<1l, 1l> > >::type> date::time_zone::to_sys_impl<std::chrono::duration<long, std::ratio<1l, 1l> > >(std::chrono::time_point<date::local_t, std::chrono::duration<long, std::ratio<1l, 1l> > >, date::choose, std::integral_constant<bool, false>) const (tz.h:947)
==20645== 
票数 12
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/56191222

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档