C++ boost date_input_facet 似乎使用传递给 facet 构造函数的不正确格式意外地解析日期

标签 c++ date parsing boost

我用于测试的 coliru 中的玩具代码: http://coliru.stacked-crooked.com/a/4039865d8d4dad52

在长期中断 C++ 之后,我又开始习惯它了。我正在编写解析 CSV 的代码,该 CSV 可能包含多个包含日期或空值的列。我的假设是每个日期列都有一种有效的日期格式,尽管不同的列可能有不同的格式。

对于我拥有的每个日期列,我找到第一个被成功解析为日期的值,给定一个带有 boost date_input_facet 对象的潜在语言环境的 std::vector。正确解析的第一个日期将返回我的工作区域设置数组中的索引。一旦我为第一个可解析日期设置了合适的格式,我就想永远修复该格式,这样我就不必再浪费 CPU 时间来检测格式。

这是我的语言环境数组:

const std::vector<std::locale> Date::date_formats = {
    std::locale(std::locale::classic(), new date_input_facet("%Y-%m-%d")),
    std::locale(std::locale::classic(), new date_input_facet("%Y/%m/%d")),
    std::locale(std::locale::classic(), new date_input_facet("%m-%d-%Y")),
    std::locale(std::locale::classic(), new date_input_facet("%m/%d/%Y")),
    std::locale(std::locale::classic(), new date_input_facet("%d-%b-%Y")),
    std::locale(std::locale::classic(), new date_input_facet("%Y%m%d")),
};

我使用从 20170101 到 20170131 的日期字符串数组来对此进行测试。然后我打印出原始日期字符串、已解析的日期以及用于解析的 date_formats vector 的索引。

对于 20170101 到 201700129,它表示第 0 个索引有效,它应该具有带破折号的“%Y-%m-%d”格式?!?!此外,破折号所在的地方,我有数字,所以它被读取为 20170101 为 2017-10- 然后删除最后一个破折号并将其解释为 2017 年 10 月,没有日期的是 2017 年 10 月 1 日。为什么它不是那样做它应该使用的格式?

从我的 coliru 中可以看到的一些结果(pY 是解析年份等):

YYYYMMDD    pY     pM   pD  format_index
20170101    2017    Oct 1   0
20170102    2017    Oct 1   0
20170103    2017    Oct 1   0
20170104    2017    Oct 1   0
20170105    2017    Oct 1   0

对于 20170130、20170131,报告了“%Y%m%d”的正确格式索引(第 5 个)。

有什么想法吗?我只想使用我传递的精确格式字符串。

最佳答案

我自己制作了一个支持多种格式的日期时间解析器。我也发现很难/不可能使用标准库和 boost 中的工具来严格解析。

我最终使用了 strptime - 主要是¹。

adaptive_parser

旨在按支持的格式列表播种 偏爱。默认情况下,解析器不是自适应的(模式是 fixed)。

在自适应模式下,格式可能需要

  • sticky(始终重复使用第一个匹配的格式)
  • ban_failed(从列表中删除失败的模式;禁止仅发生 成功解析以避免禁止无效输入的所有模式)
  • mru(保留列表但重新排序以 boost 性能)
  • Caution:
    If formats are ambiguous (e.g. mm-dd-yyyy vs dd-mm-yyyy) allowing re-ordering results in unpredictable results.

    ⇒ Only use mru when there are no ambiguous formats

  • NOTE:
    The function object is stateful. In algorithms, pass it by reference (std::ref(obj)) to avoid copying the patterns and to ensure correct adaptive behaviour

演示

我在您的测试数据上尝试了解析器:

#include "adaptive_parser.h"
#include <boost/date_time/gregorian/greg_date.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

class Date{
public:
    Date() : y(0), m(0), d(0) {}
    Date(int yy, int mm, int dd) : y(yy), m(mm), d(dd) {}
    Date(boost::gregorian::date dt) : y(dt.year()), m(dt.month()), d(dt.day()) {}
    Date(std::string const& delimitedString);

    std::string to_string() const;

    int getYear()  const { return y; }
    int getMonth() const { return m; }
    int getDay()   const { return d; }
 private:
    using parser_t = mylib::datetime::adaptive_parser;
    parser_t parser { parser_t::full_match, 
        {
            "%Y-%m-%d", "%Y/%m/%d",
            "%m-%d-%Y", "%m/%d/%Y",
            "%d-%b-%Y",
            "%Y%m%d",
        } };

    int y, m, d;
};

Date::Date(const std::string& delimitedString)
{
    using namespace boost::posix_time;

    auto t = ptime({1970,1,1}) + seconds(parser(delimitedString).count());

    *this = Date(t.date());
}

std::string Date::to_string() const
{
    std::ostringstream os;

    os << std::setfill('0')
       << std::setw(4) << y 
       << std::setw(2) << m 
       << std::setw(2) << d;

    return os.str();
}

int main() {
    std::vector<Date> vec(31);
    std::generate(vec.begin(), vec.end(), [i=1]() mutable { return Date(2017,1,i++); });

    std::vector<std::string> strvec;
    std::transform(vec.begin(), vec.end(), back_inserter(strvec), std::mem_fn(&Date::to_string));

    std::cout << "YYYYMMDD\tpY\tpM\tpD\tformat_index\n";

    for (auto& str : strvec) {
        Date parsed(str);

        std::cout << str 
            << "\t" << parsed.getYear()
            << "\t" << parsed.getMonth()
            << "\t" << parsed.getDay()
            << "\t" << "?"
            << "\n";
    }
}

打印:

YYYYMMDD    pY  pM  pD  format_index
20170101    2017    1   1   ?
20170102    2017    1   2   ?
20170103    2017    1   3   ?
20170104    2017    1   4   ?
20170105    2017    1   5   ?
20170106    2017    1   6   ?
20170107    2017    1   7   ?
20170108    2017    1   8   ?
20170109    2017    1   9   ?
20170110    2017    1   10  ?
20170111    2017    1   11  ?
20170112    2017    1   12  ?
20170113    2017    1   13  ?
20170114    2017    1   14  ?
20170115    2017    1   15  ?
20170116    2017    1   16  ?
20170117    2017    1   17  ?
20170118    2017    1   18  ?
20170119    2017    1   19  ?
20170120    2017    1   20  ?
20170121    2017    1   21  ?
20170122    2017    1   22  ?
20170123    2017    1   23  ?
20170124    2017    1   24  ?
20170125    2017    1   25  ?
20170126    2017    1   26  ?
20170127    2017    1   27  ?
20170128    2017    1   28  ?
20170129    2017    1   29  ?
20170130    2017    1   30  ?
20170131    2017    1   31  ?

¹ 主要是时区方面需要调整

关于C++ boost date_input_facet 似乎使用传递给 facet 构造函数的不正确格式意外地解析日期,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46474237/

相关文章:

c++ - 优化使用 Boost 的项目构建的最佳方法是什么?

c++ - 如何测试两种算法并确定哪种算法更快?

c++ - PPL critical_section/parallel_for 在 Visual Studio 2010 下崩溃?

mysql - 获取最接近按类型分组的特定日期的记录

python - IMAP4.按主题名称搜索 python

c++ - SendInput 不适用于 Print Screen 键

javascript - 从停止处开始递增和递减日期

java - 使用 Java 代码中的 sql 将当前日期和时间更新到数据库

android - 在此行 : - The markup in the document following the root element must be well- formed 找到多个注释

php - 解析在引号括起的文本中包含逗号的 csv 数据