【问题标题】:boost::gregorian input_facet unexpected resultsboost::gregorian input_facet 意外结果
【发布时间】:2016-01-15 13:29:18
【问题描述】:

我有一个关于从格式化字符串中读取 boost::gregorian::date 对象的问题。当输入字符串具有指定的格式时,它会按预期工作。例如,下面的代码

std::string fmt = "%Y-%m-%d";
std::string date_str = "2008-10-23";
boost::gregorian::date date;

boost::gregorian::date_input_facet* i_facet(new  boost::gregorian::date_input_facet());
i_facet->format(fmt.c_str());
std::stringstream ss;
ss.exceptions(std::ios_base::failbit);
ss.imbue(std::locale(ss.getloc(), i_facet));
ss << date_str;

ss >> date;

std::cout << date << std::endl;

产生正确的输出。

2008-Oct-23

但是,如果格式与输入字符串不对应,则将字符串流式传输到日期对象会产生错误的结果:

// all the code is the same except input string is as follows: std::string date_str = "20081023";

给予 2008-Feb-01,

那么,问题是为什么它会产生错误的结果而不是抛出异常,尽管 failbit 标志为 ON?

我尝试过使用不同的格式和输入字符串,似乎任何类型的可能分隔符的每种混合都可以,除非上面的示例中根本没有分隔符。 此外,既没有查看 boost 文档,也没有调查代码本身,我找到了解决方案。

*使用 g++ (Ubuntu 4.8.2-19ubuntu1) 4.8.2 编译,boost 版本 1.55

【问题讨论】:

    标签: c++ boost boost-date-time


    【解决方案1】:

    是的,我同意这种行为很奇怪。

    发生的事情是解析器根本不会验证分隔符!来自boost::date_time::format_date_parser的代码:

    相反,代码只是盲目地跳过输入字符,假设它是一个分隔符。这意味着在200810231 被解析为格式规范中的-

    接下来,两位数 (02) 被用作 %m 说明符(so,Feb)。

    最后,3 被解析为- 分隔符。显然,所有字段都被视为可选字段,因此未指定的日期默认为1

    其中很多事情让我觉得非常草率。我会在这里写下我自己的解析,很快。

    演示

    Live On Coliru

    #include <boost/date_time/gregorian/gregorian.hpp>
    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/qi_match.hpp>
    #include <iostream>
    
    struct as_yyyy_mm_dd {
        boost::gregorian::date& _into;
    
        friend std::istream& operator>>(std::istream& is, as_yyyy_mm_dd&& manip) {
            using namespace boost::spirit::qi;
    
            unsigned short y,m,d;
            if (is >> match(
                        uint_parser<unsigned short, 10, 4, 4>() >> '-' >>
                        uint_parser<unsigned short, 10, 2, 2>() >> '-' >>
                        uint_parser<unsigned short, 10, 2, 2>(), 
                        y, m, d))
            {
                manip._into = { y, m, d };
            }
    
            return is;
        };
    };
    
    int main() {
        boost::gregorian::date date;
    
        for (auto input : { "20081023", "2008-10-23" })
        {
            std::cout << "Parsing: '" << input << "' ";
            std::stringstream ss(input);
            //ss.exceptions(std::ios_base::failbit);
    
            if (ss >> as_yyyy_mm_dd{ date })
                std::cout << "Parsed: " << date << std::endl;
            else
                std::cout << "Parse failed\n";
        }
    }
    

    打印:

    Parsing: '20081023' Parse failed
    Parsing: '2008-10-23' Parsed: 2008-Oct-23
    

    【讨论】:

    • 添加了一个简单手动解析器的现场演示:as_yyyy_mm_dd。如果您需要动态格式,这当然会受到伤害。无论如何,请在邮件列表中提交错误报告(我认为这很重要。它甚至可能导致安全漏洞,尽管这在很大程度上是测试其使用情况的人的责任:))
    • 感谢您的深刻回答。现在,我和你刚才提议的解析器保持一致。如果我需要更复杂的 - 将尝试切换到正则表达式。
    • @pausag 听起来有点颠倒。这个,你仍然可以使用正则表达式。如果你需要更复杂的,像 Spirit 这样的解析器生成器将变得可取。无论如何me writing that parser live (experiment)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-25
    • 1970-01-01
    • 2021-08-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多