Конвертируйте строки широких символов для повышения даты - PullRequest
4 голосов
/ 29 ноября 2008

Мне нужно конвертировать несколько миллионов дат, сохраненных в виде широких строк, в даты повышения

Следующий код работает. Однако он генерирует ужасное предупреждение компилятора и не выглядит эффективным.

Есть ли лучший способ?

#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 . Фасет - это сервис, который позволяет операторам потока обрабатывать определенный выбор для представления даты или времени или для чего-либо еще. Все варианты различных вещей, каждый из которых обрабатывается своим собственным аспектом, собраны в одной локали.

Это решение было указано мне litb , который оказал мне достаточно помощи, чтобы использовать фасеты в моем производственном коде, делая его более быстрым и быстрым. Спасибо.

Имеется превосходное учебное пособие по языкам и фасетам Натана Майерса, который разработал фасеты. У него легкий стиль, который облегчает чтение его учебника, хотя это сложный материал, и ваш мозг может пострадать после первого прочтения - мой сделал. Я предлагаю вам пойти туда сейчас. Для тех, кто просто хочет практиковать преобразование строк широких символов для повышения даты, оставшаяся часть этого поста описывает минимум, необходимый для его работы.


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;

Литб продолжал предлагать использовать «грани», о которых я никогда раньше не слышал. Похоже, они выполняют свою работу, производя невероятно сжатый код внутри цикла, за счет пролога, в котором настроен языковой стандарт.

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;

Создает следующий вывод:

1st method: 2.453
2nd method: 2.422
3rd method: 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 () сложный, недокументированный и не рекомендуется)

Ответы [ 2 ]

2 голосов
/ 29 ноября 2008

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, и у каждого есть уникальный идентификатор.
  • Вы можете вставить новую локаль в поток, заменив ее прежнюю локаль. Локаль потока будет использоваться для всех видов анализа и преобразования.
  • Когда вы создаете новый std::locale, используя форму, которую я показал, вы даете ему существующую локаль и указатель на фасет. Данный фасет заменит любой существующий фасет того же типа в данной локали. (поэтому он заменит любой другой используемый date_input_facet).
  • Все фасеты как-то связаны с локалью, так что вы можете использовать std::has_facet<Facet>(some_locale), чтобы проверить, имеет ли данная локаль заданный тип фасета.
  • Вы можете использовать фасет из одной локали, выполнив std::use_facet<Facet>(some_locale).some_member....
  • date_input_facet имеет функцию get, которую можно использовать так:

Нижеследующее по сути сделано operator>> при помощи 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);
1 голос
/ 29 ноября 2008

Вы можете использовать функцию парсера 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"
...