纳秒和Chrono C++

6
2018-10-01 00:06:16.700000000

我有一个时间序列的数据文件,其中包含上述时间戳。我需要将其转换为距离纪元开始的纳秒数,然后需要对时间戳进行毫秒、微秒或纳秒的加法(偏移)。最后,对于某些记录,需要将其重新格式化为上述格式。

我在时间点创建方面遇到了问题 - 如何表示纳秒...使用微秒时可以正常工作。

请帮忙修复下面的代码片段...并且一旦我获得了距离纪元开始的纳秒数,如何回到上述的时间戳格式。

std::string ts("23 01 2020 20:59:59.123456789");

XXXX (ts);

void XXXXX (string timestamp)
{
     stringstream temp_ss(timestamp);

     tm temp_time_object = {};

     temp_ss >> get_time (&temp_time_object, "%Y/%m/%d %H:%M:%S");

     chrono::system_clock::time_point temp_time_point = system_clock::from_time_t(mktime(&temp_time_object));
    // chrono::high_resolution_clock::time_point temp_time_point1 = temp_time_point;

    auto  nsecs           =  stol (timestamp.substr (timestamp.find_first_of('.')+1,9));

// +++ This is where I GET stuck forming the time_point....
// I've tried this so many different ways .... 
// Is it that the system_clock cannot accept nanos? 


    temp_time_point += nanoseconds (nsecs);

     auto micro_from_epoch = duration_cast<nanoseconds> (temp_time_point.time_since_epoch()).count();

     cout << micro_from_epoch << endl;

}

请提供您收到的错误信息。 - bolov
以上代码的问题在于,纳秒没有 += 运算符。如果我尝试使用比率为 1:100000000 的 duration 显式添加 - 它仍然会抱怨我不能将其添加到时间点上。 - Admiral Kirk
你的基于字符串的时间戳是本地时间还是UTC?如果是本地时间,是根据计算机当前的本地时区设置还是其他时区? - Howard Hinnant
假设所有时间戳均为GMT,UTC + 0。 - Admiral Kirk
2个回答

5
我在时间点的创建上遇到了麻烦,不知道如何表示纳秒...使用微秒的话是可以工作的。 这表明您的 `system_clock::time_point` 的精度比纳秒粗(在 llvm 上是微秒,在 Windows 上是1/10微秒)。将纳秒添加到这样的 `time_point` 最简单的方法是:
auto tp = temp_time_point + nanoseconds (nsecs);

这将形成一个基于 system_clock,但具有 system_clock::durationnanoseconds 的 "common type" 精度的 time_point。在实践中,这只是 nanoseconds

假设所有时间戳都是 GMT,即 UTC + 0

现在的问题是,mktime 将从本地的 tm 转换为 UTC time_t。但你想要将其从 UTC 字段类型转换为 UTC 串行类型。

在 C++20 中可以轻松解决此问题(我知道您还没有它,请听我说):

#include <chrono>
#include <iostream>
#include <sstream>

std::chrono::sys_time<std::chrono::nanoseconds>
XXXXX(std::string const& timestamp)
{
    using namespace std;
    using namespace std::chrono;
    istringstream temp_ss{timestamp};
    sys_time<nanoseconds> tp;
    temp_ss >> parse("%F %T", tp);
    return tp;
}

int
main()
{
    auto tp = XXXXX("2018-10-01 00:06:16.700000000");
    std::cout << tp.time_since_epoch().count() << "ns\n";
    std::string s = std::format("{:%F %T}", tp);
    std::cout << s << '\n';
}

这将把 string 转换为一个 chrono::time_point<system_clock, nanoseconds>,显然不同于你的 system_clock::time_point,只是精度比 nanoseconds 更低。

然后使用 formattime_point 转换回 string

输出:

1538352376700000000ns
2018-10-01 00:06:16.700000000

现在我知道一个完全符合C++20标准的<chrono>库目前还是比较少见的(正在推出中)。在它来到之前,有一个预览版 C++20 <chrono>,它兼容 C++11,并且是免费和开源的。只需要很少的语法更改即可使用。

#include "date/date.h"
#include <chrono>
#include <iostream>
#include <sstream>

date::sys_time<std::chrono::nanoseconds>
XXXXX(std::string const& timestamp)
{
    using namespace date;
    using namespace std;
    using namespace std::chrono;
    istringstream temp_ss{timestamp};
    sys_time<nanoseconds> tp;
    temp_ss >> parse("%F %T", tp);
    return tp;
}

int
main()
{
    using namespace date;
    auto tp = XXXXX("2018-10-01 00:06:16.700000000");
    std::cout << tp.time_since_epoch().count() << "ns\n";
    std::string s = format("%F %T", tp);
    std::cout << s << '\n';
}

输出:

1538352376700000000ns
2018-10-01 00:06:16.700000000

你使用date.h的最终示例非常简单易懂。我刚试着将其应用到Mac OS上,但在执行"./bootstrap-vcpkg.sh"时出现了错误。我会在明天再次查看并尝试解决这个问题。错误信息为:eval: line 162: unexpected EOF while looking for matching `''。 - Admiral Kirk
对于这个使用案例,基于CMake的构建工具(或任何其他包管理工具)会带来更多问题而不是解决问题。date.h是一个仅包含头文件的库。只需包含它,你就可以开始使用了。如果这仍然无法解决你的问题,请告诉我,我会帮助你启动。 - Howard Hinnant
非常简单 - 头文件完美地工作了... 我在哪里可以找到日期格式说明符的列表?例如,如果我的日期是以'/'分隔或其他格式,我该怎么办?我还注意到库会检测是否提供了毫秒,并将其转换为纳秒等... 谢谢您的帮助。 - Admiral Kirk
以下是所有的解析标志:https://howardhinnant.github.io/date/date.html#from_stream_formatting 格式化标志非常相似,位于此表格上方的单独表格中。 - Howard Hinnant

1
您需要直接从文本转换为纳秒级分辨率的工作。主要有两个关键库:
  • CCTZ是由谷歌工程师开发的,尽管(像许多项目一样)不是谷歌公司正式发布的产品

  • date是Howard Hinnant开发的,他可能会在我还在打字时回答;他的库是C ++20中内容的基础

我已经使用R(通过Rcpp)包装了这两个库,并且有很多示例。但是这两个存储库中也有示例,所以可以从那里开始?

因此,由于现在缺乏更好的CCTZ示例,这是一个使用R包的示例,您可以看到输入:

R> library(RcppCCTZ)
R> example(parseDatetime)

prsDttR> ds <- getOption("digits.secs")

prsDttR> options(digits.secs=6) # max value

prsDttR> parseDatetime("2016-12-07 10:11:12",        "%Y-%m-%d %H:%M:%S")   # full seconds
[1] "2016-12-07 10:11:12 UTC"

prsDttR> parseDatetime("2016-12-07 10:11:12.123456", "%Y-%m-%d %H:%M:%E*S") # fractional seconds
[1] "2016-12-07 10:11:12.123456 UTC"

prsDttR> parseDatetime("2016-12-07T10:11:12.123456-00:00")  ## default RFC3339 format
[1] "2016-12-07 10:11:12.123456 UTC"

prsDttR> now <- trunc(Sys.time())

prsDttR> parseDatetime(formatDatetime(now + 0:4))               # vectorised
[1] "2020-05-01 02:16:27 UTC" "2020-05-01 02:16:28 UTC"
[3] "2020-05-01 02:16:29 UTC" "2020-05-01 02:16:30 UTC"
[5] "2020-05-01 02:16:31 UTC"

prsDttR> options(digits.secs=ds)
R> 

调用的解析器函数是(忽略与 R 相关的部分)

Rcpp::DatetimeVector parseDatetime(Rcpp::CharacterVector svec,
                                   std::string fmt = "%Y-%m-%dT%H:%M:%E*S%Ez",
                                   std::string tzstr = "UTC") {
    cctz::time_zone tz;
    load_time_zone(tzstr, &tz);
    sc::system_clock::time_point tp;
    cctz::time_point<cctz::sys_seconds> unix_epoch =
        sc::time_point_cast<cctz::sys_seconds>(sc::system_clock::from_time_t(0));

    // if we wanted a 'start' timer
    //sc::system_clock::time_point start = sc::high_resolution_clock::now();

    auto n = svec.size();
    Rcpp::DatetimeVector dv(n, tzstr.c_str());
    for (auto i=0; i<n; i++) {
        std::string txt(svec(i));

        if (!cctz::parse(fmt, txt, tz, &tp)) Rcpp::stop("Parse error on %s", txt);

        // time since epoch, with fractional seconds added back in
        // only microseconds guaranteed to be present
        double dt = sc::duration_cast<sc::microseconds>(tp - unix_epoch).count() * 1.0e-6;

        // Rcpp::Rcout << "tp: " << cctz::format(fmt, tp, tz) << "\n"
        //             << "unix epoch: " << cctz::format(fmt, unix_epoch, tz) << "\n"
        //             << "(tp - unix.epoch).count(): " << (tp - unix_epoch).count() << "\n"
        //             << "dt: " << dt << std::endl;

        dv(i) = Rcpp::Datetime(dt);
    }

    return dv;
}

它检查字符串向量的输入向量,并将每个字符串转换为其他格式。

编辑:这是另一个例子,使用我们的nanotime包,该包利用并使用CCTZ解析器:

R> library(nanotime)
R> as.nanotime("2020-01-29 13:12:00.000000001 America/New_York")
[1] 2020-01-29T18:12:00.000000001+00:00
R> 

使用基于纳秒自纪元以来的底层精度,完全与 std::chrono 互操作的全9 + 9位数字精度。

不好意思,你的速度太快了。 :-) - Howard Hinnant
哇。这以前从未发生过。不过还是感谢你的关注! - Dirk Eddelbuettel
在代码中,我添加了micros和milliseconds可以用于时间点。但我不知道如何创建一个具有纳秒分辨率的完整日期时间的时间点。你上面的示例只到微秒级别,而不是纳秒。 - Admiral Kirk
如果我没记错的话,微秒和纳秒之间的区别取决于实现/操作系统。我使用Linux,并且我们非常支持纳秒,因为我曾经共同编写了一个nanotime包,它利用了(Rcpp)CCTZ(现在也有(Rcpp)Date),可以很好地获取纳秒 - 我现在每天都在使用它。 - Dirk Eddelbuettel
也许你指的是我展示的例子,我可能展示了一个不好的例子,因为它转换为 R 的默认时间类型,而那些确实是微秒。让我再添加一个第二个例子。 - Dirk Eddelbuettel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接