【问题标题】:Convert wide character strings to boost dates将宽字符串转换为提升日期
【发布时间】:2008-11-29 16:27:01
【问题描述】:

我需要将存储为宽字符串的数百万个日期转换为提升日期

以下代码有效。但是,它会生成可怕的编译器警告,而且似乎效率不高。

有没有更好的办法?

#include "boost/date_time/gregorian/gregorian.hpp"
using namespace boost::gregorian;

#include <string>
using namespace std;


    wstring ws( L"2008/01/01" );

    string temp(ws.length(), '\0');
    copy(ws.begin(), ws.end(), temp.begin());
    date d1( from_simple_string( temp ) );

    cout << d1;

事实证明,更好的方法是使用标准 C++ 库 locale,它是 facets 的集合。 facet 是一项服务,它允许流操作员处理日期或时间表示或其他任何内容的特定选择。所有关于不同事物的选择,每一个都由它自己的方面处理,都聚集在一个语言环境中。

litb 向我指出了这个解决方案,他为我提供了足够的帮助来在我的生产代码中使用构面,使其更简洁、更快。谢谢。

设计构面的 Nathan Myers 在语言环境和构面上有一个 excellent tutorial。他有一种轻松的风格,使他的教程易于阅读,尽管这是高级的东西,第一次通读后你的大脑可能会受伤——我的就是这样。我建议你现在去那里。对于只想将宽字符串转换为提升日期的实用性的任何人,本文的其余部分描述了使其工作所需的最低限度。


litb 首先提供了以下消除编译器警告的简单解决方案。 (该解决方案在我开始接受之前已被编辑。)这看起来像它做同样的事情,一个接一个地转换宽字符,但它避免了使用临时字符串,因此更清晰,我认为。我真的很喜欢编译器警告消失了。

#include "boost/date_time/gregorian/gregorian.hpp"
using namespace boost::gregorian;

#include <string>
using namespace std;


    wstring ws( L"2008/01/01" );

    date d1( from_simple_string( string( ws.begin(), ws.end() ) );

    cout << d1;

litb 继续建议使用我以前从未听说过的“构面”。他们似乎完成了这项工作,在循环中生成了极其简洁的代码,但代价是设置语言环境的序言。

wstring ws( L"2008/01/01" );

// construct a locale to collect all the particulars of the 'greek' style
locale greek_locale;
// construct a facet to handle greek dates - wide characters in 2008/Dec/31 format
wdate_input_facet greek_date_facet(L"%Y/%m/%d");
// add facet to locale
greek_locale = locale( greek_locale, &greek_date_facet );
// construct stringstream to use greek locale
std::wstringstream greek_ss; 
greek_ss.imbue( greek_locale );

date d2;

greek_ss << ws;
greek_ss >> d2;

cout << d2;

事实证明,这也更有效:

clock_t start, finish;
double  duration;

start = clock();
for( int k = 0; k < 100000; k++ ) {
    string temp(ws.length(), '\0');
    copy(ws.begin(), ws.end(), temp.begin());
    date d1( from_simple_string( temp ) );
}
finish = clock();
duration = (double)(finish - start) / CLOCKS_PER_SEC;
cout << "1st method: " << duration << endl;

start = clock();
for( int k = 0; k < 100000; k++ ) {
    date d1( from_simple_string( string( ws.begin(), ws.end() ) ) );
}
finish = clock();
duration = (double)(finish - start) / CLOCKS_PER_SEC;
cout << "2nd method: " << duration << endl;

start = clock();
for( int k = 0; k < 100000; k++ ) {
    greek_ss << ws;
    greek_ss >> d2;
    ss.clear();
}
finish = clock();
duration = (double)(finish - start) / CLOCKS_PER_SEC;
cout << "3rd method: " << duration << endl;

产生以下输出:

第一种方法:2.453 第二种方法:2.422 第三种方法:1.968

好的,现在在生产代码中并通过了回归测试。它看起来像这样:

  //  .. construct greek locale and stringstream 

  // ... loop over input extracting date strings

        // convert range to boost dates
        date d1;
        greek_ss<< sd1; greek_ss >> d1;
        if( greek_ss.fail() ) {
                       // input is garbled
            wcout << L"do not understand " << sl << endl;
            exit(1);
        }
         greek_ss.clear();

// finish processing and end loop

我对此有最后一个问题。将方面添加到语言环境似乎需要两次调用语言环境复制构造函数

    // add facet to locale
greek_locale = locale( greek_locale, &greek_date_facet );

为什么没有 add(facet*) 方法? ( _Addfac() 很复杂,未记录且已弃用)

【问题讨论】:

  • 对于您的测试,每次提取后,您应该在流上调用 .clear()。因为它可能设置了错误标志,这将导致进一步的读/写被取消。
  • 所以,最好为(int k = 0; k > d2; ss.clear(); /* 清除可能的 eof 标志 */ }

标签: c++ datetime boost localization facet


【解决方案1】:

efotinis 找到了一个使用 from_stream 的好方法。


我查看了date_time 的手册,发现它支持构面:

#include <boost/date_time/gregorian/gregorian.hpp>
#include <iostream>
#include <sstream>
#include <locale>

int main() {
    using namespace boost::gregorian;

    std::wstringstream ss;
    wdate_input_facet * fac = new wdate_input_facet(L"%Y-%m-%d");
    ss.imbue(std::locale(std::locale::classic(), fac));

    date d;
    ss << L"2004-01-01 2005-01-01 2006-06-06";
    while(ss >> d) {
        std::cout << d << std::endl;
    }
}

你也可以这样做。


我查看了日期方面的工作原理:

  • boost::date_time::date_input_facet 模板实现了一个方面。
  • 构面派生自std::locale::facet,每个构面都有唯一的ID。
  • 您可以将新的语言环境灌输到流中,替换其旧的语言环境。流的语言环境将用于各种解析和转换。
  • 当您使用我显示的表单创建一个新的std::locale 时,您给它一个现有的语言环境和一个指向 facet 的指针。给定的方面将替换给定语言环境中相同类型的任何现有方面。 (因此,它将替换使用的任何其他 date_input_facet)。
  • 所有方面都以某种方式与语言环境相关联,因此您可以使用std::has_facet&lt;Facet&gt;(some_locale) 检查给定语言环境是否具有某些给定方面类型。
  • 您可以通过 std::use_facet&lt;Facet&gt;(some_locale).some_member... 使用来自一个区域设置的构面。
  • date_input_facet有一个get函数,可以这样使用:

下面的内容基本上是由operator&gt;&gt; by boost::date_type 完成的:

// assume src is a stream having the wdate_input_facet in its locale. 
// wdate_input_facet is a boost::date_time::date_input_facet<date,wchar_t> typedef.

date d;

// iterate over characters of src
std::istreambuf_iterator<wchar_t> b(src), e;

// use the facet to parse the date
std::use_facet<wdate_input_facet>(src.getloc()).get(b, e, src, d);

【讨论】:

  • 谢谢。 “如果你确定……” 是的。您的代码肯定更清晰、更简洁!效率更高吗?
  • 它摆脱了编译器警告! (你骗了它:-)
  • 编译器拒绝 string( wstring(L"test") ) 但毫无怨言地接受 wstring ws(L"test"); 对我来说似乎很奇怪字符串(ws.begin(),ws.end()))。效果是一样的。
  • string( wstring(L"test") ) vs. wstring ws(L"test"); string(ws.begin(), ws.end()) ) 非常不同。第二种的效果是ws的每一个字符都转换成string::char_type再相加。第一个只是复制构造
  • 我不确定您是否会注意到我的第一种方法和您的方法之间的性能差异。
【解决方案2】:

您可以使用 from_stream 解析器函数:

using boost::gregorian::date;
using boost::gregorian::from_stream;

std::wstring ws( L"2008/01/01" );
date d1(from_stream(ws.begin(), ws.end()));
std::cout << d1;  // prints "2008-Jan-01"

【讨论】:

猜你喜欢
  • 2015-04-27
  • 2011-11-28
  • 2016-05-03
相关资源
最近更新 更多