【问题标题】:How to parse a date string into a c++11 std::chrono time_point or similar?如何将日期字符串解析为 c++11 std::chrono time_point 或类似的?
【发布时间】:2014-01-28 01:44:47
【问题描述】:

考虑格式的历史日期字符串:

Thu Jan 9 12:35:34 2014

我想将这样的字符串解析为某种 C++ 日期表示,然后计算从那时起经过的时间量。

从生成的持续时间中,我需要访问秒数、分钟数、小时数和天数。

这可以通过新的 C++11 std::chrono 命名空间来完成吗?如果没有,我今天应该怎么做?

我使用的是 g++-4.8.1,但大概答案应该只针对 C++11 规范。

【问题讨论】:

  • POSIX 系统(如 Linux 或 OSX)有一个 strptime 将字符串解析为 tm 结构。不幸的是,它不存在于 Windows,但 there are alternatives.
  • @JoachimPileborg 是否支持最后的+0000
  • @remyabel,其实我弄错了。该后缀不存在。我已经更新了问题。
  • 那很好,因为似乎不支持时区后缀。 :)
  • 请注意,chrono 在设计时并未考虑日历功能,因此将time_point 与实际日期相关联并不是其功能的核心。 Boost 试图在其早于 Chrono 的 Date Time 库中解决这个问题。不幸的是,这两个库don't go together 非常顺利。

标签: c++ c++11 date-parsing datetime-parsing


【解决方案1】:
std::tm tm = {};
std::stringstream ss("Jan 9 2014 12:35:34");
ss >> std::get_time(&tm, "%b %d %Y %H:%M:%S");
auto tp = std::chrono::system_clock::from_time_t(std::mktime(&tm));

版本 5 之前的 GCC 没有实现 std::get_time。你也应该会写:

std::tm tm = {};
strptime("Thu Jan 9 2014 12:35:34", "%a %b %d %Y %H:%M:%S", &tm);
auto tp = std::chrono::system_clock::from_time_t(std::mktime(&tm));

【讨论】:

  • 但是 get_time() 还没有在 gcc 上实现?
  • @SChepurin 可能不是,但它在 C++ 规范中。
  • 根据 gcc 的bug 54354,这在 gcc 5 中得到了解决。对于 C++11 功能来说有点晚了。
  • @Xaqq std::tm t{}; // 应该这样做
  • @einpoklum,应该从 gcc 5.0 开始工作。我已经在 gcc 5.4 上进行了测试(即在当前稳定的 Ubuntu 16.04 中)。
【解决方案2】:

旧问题的新答案。新答案的理由:该问题是从其原始形式编辑的,因为当时的工具无法准确处理所询问的内容。最终接受的答案给出的行为与原始问题所要求的行为略有不同。

我并不是要放弃已接受的答案。这是一个很好的答案。只是 C API 如此令人困惑,这样的错误不可避免地会发生。

最初的问题是解析"Thu, 9 Jan 2014 12:35:34 +0000"。很明显,目的是解析代表 UTC 时间的时间戳。但是strptime(它不是标准的 C 或 C++,而是 POSIX)不会解析表明这是一个 UTC 时间戳的尾随 UTC 偏移量(它将使用%z 对其进行格式化,但不会对其进行解析)。

该问题随后被编辑为询问"Thu Jan 9 12:35:34 2014"。但是没有编辑了这个问题,以澄清这是 UTC 时间戳,还是计算机当前 本地 时区中的时间戳。由于使用了std::mktime,因此接受的答案隐式假定时间戳代表计算机的当前本地时区。

std::mktime 不仅将字段类型tm 转换为串行类型time_t,还执行从计算机本地时区到UTC 的偏移调整。

但是,如果我们想将 UTC 时间戳解析为原始(未经编辑的)问题,该怎么办?

今天可以使用更新的 free open-source library 来完成。

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

int
main()
{
    using namespace std;
    using namespace date;
    istringstream in{"Thu, 9 Jan 2014 12:35:34 +0000"};
    sys_seconds tp;
    in >> parse("%a, %d %b %Y %T %z", tp);
}

这个库可以解析%z。而date::sys_seconds 只是一个类型定义:

std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>

问题还问:

从生成的持续时间中,我需要访问秒数、分钟数、小时数和天数。

那部分仍未得到答复。以下是使用 this library 的方法。

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

int
main()
{
    using namespace std;
    using namespace date;
    istringstream in{"Thu, 9 Jan 2014 12:35:34 +0000"};
    sys_seconds tp;
    in >> parse("%a, %d %b %Y %T %z", tp);
    auto tp_days = floor<days>(tp);
    auto hms = hh_mm_ss<seconds>{tp - tp_days};
    std::cout << "Number of days    = " << tp_days.time_since_epoch() << '\n';
    std::cout << "Number of hours   = " << hms.hours() << '\n';
    std::cout << "Number of minutes = " << hms.minutes() << '\n';
    std::cout << "Number of seconds = " << hms.seconds() << '\n';
}

floor&lt;days&gt; 将秒精度 time_point 截断为 天精度 time_point。如果从 tp 中减去天数精度 time_point,则剩下的 duration 表示自午夜 (UTC) 以来的时间。

hh_mm_ss&lt;seconds&gt; 类型采用任何可转换为 secondsduration(在本例中为从午夜开始的时间),并为每个字段创建一个带有 getter 的 {hours, minutes, seconds} 字段类型。如果持续时间的精度小于秒,则此字段类型还将具有亚秒的吸气剂。在 C++17 之前,必须将更精细的持续时间指定为模板参数。在 C++17 及以后的版本中可以推导出来:

auto hms = hh_mm_ss{tp - tp_days};

最后,可以打印出所有这些持续时间。此示例输出:

Number of days    = 16079d
Number of hours   = 12h
Number of minutes = 35min
Number of seconds = 34s

所以 2014-01-09 是 1970-01-01 之后的 16079 天。

这是完整的示例,但在 milliseconds 精度:

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

int
main()
{
    using namespace std;
    using namespace std::chrono;
    using namespace date;

    istringstream in{"Thu, 9 Jan 2014 12:35:34.123 +0000"};
    sys_time<milliseconds> tp;
    in >> parse("%a, %d %b %Y %T %z", tp);
    auto tp_days = floor<days>(tp);
    hh_mm_ss hms{tp - tp_days};
    std::cout << tp << '\n';
    std::cout << "Number of days         = " << tp_days.time_since_epoch() << '\n';
    std::cout << "Number of hours        = " << hms.hours() << '\n';
    std::cout << "Number of minutes      = " << hms.minutes() << '\n';
    std::cout << "Number of seconds      = " << hms.seconds() << '\n';
    std::cout << "Number of milliseconds = " << hms.subseconds() << '\n';
}

输出:

2014-01-09 12:35:34.123
Number of days         = 16079d
Number of hours        = 12h
Number of minutes      = 35min
Number of seconds      = 34s
Number of milliseconds = 123ms

这个库现在是 C++20 的一部分,但在 namespace std::chrono 中,可以在标题 &lt;chrono&gt; 中找到。

【讨论】:

  • 毫秒精度的例子正是我想要的。不幸的是,当我精确 --std=c++20 选项时,gcc 10.2 和 clang 11.0.0 都找不到 std::chrono::parse 。知道什么时候可以使用吗?
  • 不知道。但与此同时,date/date.h 是免费的、开源的且仅限标头。
  • @HowardHinnant 我喜欢你指出 strptime 的一个令人讨厌的限制。但事实证明,这整个回应是对这个新的神奇开源库的无耻宣传,当你点击它时,你会惊讶地发现它恰好是你创作的。当然,用户贡献的代码是堆栈溢出的核心,但是在这里隐藏你的作者身份让我很反感。我宁愿公开,为了回应这些讨厌的错误,我写了一些你可以在“这里”找到的开源代码。
  • 对不起@BillGale。我并不是要隐藏我的作者身份。事实上,与许多其他人(不是你自己)不同,我的 Stack Overflow ID 和 github ID 都使用我的全名。如果我试图掩盖我的作者身份,人们会认为我会比这更聪明一点。此外,可以解释为添加“我写了它!”作为吹牛。我既不想吹牛,也不想掩饰。而且我还没有从这个软件中赚到一分钱,我也不想这样做。我的主要目标是为 C++ 标准提案寻求现场经验。任务完成。在 C++20 中标准化的库。
【解决方案3】:

这是相当 C-ish 并且不像 Simple 的答案那样优雅的解决方案,但我认为它可能有效。 这个答案可能是错误的,但我会保留它,以便有人可以发布更正。

#include <iostream>
#include <ctime>

int main ()
{
  struct tm timeinfo;
  std::string buffer = "Thu, 9 Jan 2014 12:35:00";

  if (!strptime(buffer.c_str(), "%a, %d %b %Y %T", &timeinfo))
    std::cout << "Error.";

  time_t now;
  struct tm timeinfo2;
  time(&now);
  timeinfo2 = *gmtime(&now);

  time_t seconds = difftime(mktime(&timeinfo2), mktime(&timeinfo));
  time(&seconds);
  struct tm result;
  result = *gmtime ( &seconds );
  std::cout << result.tm_sec << " " << result.tm_min << " "
            << result.tm_hour << " " << result.tm_mday;
  return 0;
}

【讨论】:

    【解决方案4】:

    涵盖的案例(代码如下):

    • 从给定日期到现在

      long int min0 = getMinutesSince( "2005-02-19 12:35:00" );

    • 从时代到现在

      long int min1 = getMinutesSince1970( );

    • 两个日期+小时之间(从纪元到给定日期)

      long int min0 = getMinutesSince1970Until( "2019-01-18 14:23:00" );

      long int min1 = getMinutesSince1970Until( "2019-01-18 14:27:00" );

      cout &lt;&lt; min1 - min0 &lt;&lt; endl;

    完整代码:

    #include <iostream>
    #include <chrono>
    #include <sstream>
    #include <string>
    #include <iomanip>
    
    using namespace std;
    
    // ------------------------------------------------
    // ------------------------------------------------
    long int getMinutesSince1970Until( string dateAndHour ) {
    
      tm tm = {};
      stringstream ss( dateAndHour );
      ss >> get_time(&tm, "%Y-%m-%d  %H:%M:%S");
    
      chrono::system_clock::time_point tp = chrono::system_clock::from_time_t(mktime(&tm));
    
    
      return
        chrono::duration_cast<chrono::minutes>(
                                               tp.time_since_epoch()).count();
    
    } // ()
    // ------------------------------------------------
    // ------------------------------------------------
    long int getMinutesSince1970() {
      chrono::system_clock::time_point now = chrono::system_clock::now();
    
      return
        chrono::duration_cast<chrono::minutes>( now.time_since_epoch() ).count();
    } // ()
    
    // ------------------------------------------------
    // ------------------------------------------------
    long int getMinutesSince( string dateAndHour ) {
    
      tm tm = {};
      stringstream ss( dateAndHour );
      ss >> get_time(&tm, "%Y-%m-%d  %H:%M:%S");
    
      chrono::system_clock::time_point then =
        chrono::system_clock::from_time_t(mktime(&tm));
    
      chrono::system_clock::time_point now = chrono::system_clock::now();
    
      return
        chrono::duration_cast<chrono::minutes>(
                                               now.time_since_epoch()-
                                               then.time_since_epoch()
                                               ).count();
    } // ()
    
    
    // ------------------------------------------------
    // ------------------------------------------------
    int main () {
    
      long int min = getMinutesSince1970Until( "1970-01-01 01:01:00" );
    
      cout << min << endl;
    
    
      long int min0 = getMinutesSince1970Until( "2019-01-18 14:23:00" );
      long int min1 = getMinutesSince1970Until( "2019-01-18 14:27:00" );
    
      if ( (min1 - min0) != 4 ) {
        cout << " something is wrong " << endl;
      } else {
        cout << " it appears to work !" << endl;
      }
    
      min0 = getMinutesSince( "1970-01-01 01:00:00" );
      min1 = getMinutesSince1970( );
    
      if ( (min1 - min0) != 0 ) {
        cout << " something is wrong " << endl;
      } else {
        cout << " it appears to work !" << endl;
      }
    
    } // ()
    

    【讨论】:

      猜你喜欢
      • 2016-04-23
      • 2018-07-27
      • 2012-10-01
      • 2013-05-17
      • 2020-05-30
      • 1970-01-01
      • 1970-01-01
      • 2018-02-08
      相关资源
      最近更新 更多