2015-02-24 4 views
1

Как выполнить синтаксический анализ эквивалента строки RFC 3339 в любой тип регулярной структуры DateTime? Формат даты RFC 3339 используется в ряде спецификаций, таких как Atom Syndication Format.Как обработать и преобразовать DateTime в RFC 3339 с помощью с ++

Вот пример времени даты в ATOM (RFC 3339) Формат:

2005-08-15T15:52:01+04:00 

ответ

3

Вот полный, но, к сожалению неудовлетворительными, и в то же переносимая программа среди самых последних версий LIBC++, libstdC++, VS реализациях, который анализирует строку в формате, который вы показываете, в std::chrono::system_clock::time_point.

Не удалось найти DateTime, на который вы ссылаетесь. Однако std::chrono::system_clock::time_pointявляется структурой «DateTime». std::chrono::system_clock::time_point - это подсчет продолжительности времени (секунды, микросекунды, наносекунды, что угодно) с какой-то неуточненной эпохи. И вы можете запросить std::chrono::system_clock::time_point, чтобы узнать, какова его продолжительность. И, как оказалось, каждая реализация измеряет время, начиная с Нового года 1970, пренебрегая прыжковыми секундами.

#include <chrono> 
#include <iostream> 
#include <limits> 
#include <locale> 
#include <sstream> 

template <class Int> 
// constexpr 
Int 
days_from_civil(Int y, unsigned m, unsigned d) noexcept 
{ 
    static_assert(std::numeric_limits<unsigned>::digits >= 18, 
      "This algorithm has not been ported to a 16 bit unsigned integer"); 
    static_assert(std::numeric_limits<Int>::digits >= 20, 
      "This algorithm has not been ported to a 16 bit signed integer"); 
    y -= m <= 2; 
    const Int era = (y >= 0 ? y : y-399)/400; 
    const unsigned yoe = static_cast<unsigned>(y - era * 400);  // [0, 399] 
    const unsigned doy = (153*(m + (m > 2 ? -3 : 9)) + 2)/5 + d-1; // [0, 365] 
    const unsigned doe = yoe * 365 + yoe/4 - yoe/100 + doy;   // [0, 146096] 
    return era * 146097 + static_cast<Int>(doe) - 719468; 
} 

using days = std::chrono::duration 
    <int, std::ratio_multiply<std::ratio<24>, std::chrono::hours::period>>; 

namespace std 
{ 

namespace chrono 
{ 

template<class charT, class traits> 
std::basic_istream<charT,traits>& 
operator >>(std::basic_istream<charT,traits>& is, system_clock::time_point& item) 
{ 
    typename std::basic_istream<charT,traits>::sentry ok(is); 
    if (ok) 
    { 
     std::ios_base::iostate err = std::ios_base::goodbit; 
     try 
     { 
      const std::time_get<charT>& tg = std::use_facet<std::time_get<charT> > 
                  (is.getloc()); 
      std::tm t = {}; 
      const charT pattern[] = "%Y-%m-%dT%H:%M:%S"; 
      tg.get(is, 0, is, err, &t, begin(pattern), end(pattern)-1); 
      if (err == std::ios_base::goodbit) 
      { 
       charT sign = {}; 
       is.get(sign); 
       err = is.rdstate(); 
       if (err == std::ios_base::goodbit) 
       { 
        if (sign == charT('+') || sign == charT('-')) 
        { 
         std::tm t2 = {}; 
         const charT pattern2[] = "%H:%M"; 
         tg.get(is, 0, is, err, &t2, begin(pattern2), end(pattern2)-1); 
         if (!(err & std::ios_base::failbit)) 
         { 
          auto offset = (sign == charT('+') ? 1 : -1) * 
              (hours{t2.tm_hour} + minutes{t2.tm_min}); 
          item = system_clock::time_point{ 
           days{days_from_civil(t.tm_year+1900, t.tm_mon+1, 
                t.tm_mday)} + 
           hours{t.tm_hour} + minutes{t.tm_min} + seconds{t.tm_sec} - 
           offset}; 
         } 
         else 
         { 
          err |= ios_base::failbit; 
         } 
        } 
        else 
        { 
         err |= ios_base::failbit; 
        } 
       } 
       else 
       { 
        err |= ios_base::failbit; 
       } 
      } 
      else 
      { 
       err |= ios_base::failbit; 
      } 
     } 
     catch (...) 
     { 
      err |= std::ios_base::badbit | std::ios_base::failbit; 
     } 
     is.setstate(err); 
    } 
    return is; 
} 

} // namespace chrono 
} // namespace std 

int 
main() 
{ 
    std::istringstream infile("2005-08-15T15:52:01+04:00"); 
    std::chrono::system_clock::time_point tp; 
    infile >> tp; 
    std::cout << tp.time_since_epoch().count() << '\n'; 
} 

Это было протестировано на LIBC++, libstdC++ - 5.0 и VS-2015 и производит соответственно:

1124106721000000 
1124106721000000000 
11241067210000000 

На LIBC++ это подсчет микросекунд с New Years 1970, пренебрегая високосные секунды. На libstdC++ - 5.0 это число наносекунд, а на VS-2015 оно составляет 100 наносекунд.

Проблема с этим решением заключается в том, что он включает в себя вставку функции в пространство имен std. В будущем комитет C++ может решить включить эту же функцию в одно и то же пространство имен, которое может привести к аннулированию кода.

Другая проблема с этим кодом заключается в том, что он ужасно сложный. Жаль, что стандарт не обеспечивает более простого решения.

Другая проблема с этим кодом заключается в том, что он не использует более простые шаблоны синтаксического анализа «% F», «% T» и «% z», задокументированные в стандарте C (хотя и документированные как шаблоны форматирования). Я экспериментально обнаружил, что их использование не переносилось.

Другая проблема с этим кодом заключается в том, что для него потребуется gcc-5.0. Если вы используете gcc-4.9, вам не повезло. Вам придется самостоятельно разбирать вещи. Я не смог протестировать VS-реализации до VS-2015. libC++ должен быть в порядке (хотя даже libC++ не поддерживает «% z»).

Вы можете конвертировать std::chrono::system_clock::time_point обратно в структуру с разбивкой по мере необходимости через formulas here. Однако, если это ваша конечная цель, было бы более эффективно модифицировать приведенный выше код, чтобы анализировать непосредственно в вашей «разбитой» структуре, а не в std::chrono::system_clock::time_point.

Отказ от ответственности: Только очень легко проверено. Я рад обновить этот ответ с помощью любых отчетов об ошибках.

Update

В последующие годы я впервые дал этот ответ я написал библиотеку, которая делает все вычисления выше с гораздо более кратким синтаксисом.

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

int 
main() 
{ 
    using namespace date; 
    std::istringstream infile{"2005-08-15T15:52:01+04:00"}; 
    sys_seconds tp; // This is a system_clock time_point with seconds precision 
    infile >> parse("%FT%T%Ez", tp); 
    std::cout << tp.time_since_epoch() << " is " << tp << '\n'; 
} 

Вы можете найти "chrono_io.h" и "date.h"here. Это бесплатные библиотеки с открытым исходным кодом, только заголовки. На this link есть также ссылки на full documentation, а для "date.h" - даже video tutorial. Хотя video tutorial был создан до реализации функции parse.

Выход выше программы:

1124106721s is 2005-08-15 11:52:01 

, который дает как секунды с начала эпохи (1970-01-01 00:00:00 UTC) и дата/время в UTC (беря учитывается).

С учетом того, что вам нужно засчитывать прыжки секунд с эпохи, another library в этой же ссылке GitHub доступен, но не только для заголовка и требует небольшого количества installation. Но использование его является простой модификацией вышеуказанной программы:

#include "chrono_io.h" 
#include "tz.h" 
#include <iostream> 
#include <sstream> 

int 
main() 
{ 
    using namespace date; 
    std::istringstream infile{"2005-08-15T15:52:01+04:00"}; 
    utc_seconds tp; // This is a utc_clock time_point with seconds precision 
    infile >> parse("%FT%T%Ez", tp); 
    std::cout << tp.time_since_epoch() << " is " << tp << '\n'; 
} 

И теперь выход:

1124106743s is 2005-08-15 11:52:01 

Разница в коде, что "tz.h" теперь включен вместо "date.h" и utc_seconds анализируется вместо sys_seconds. utc_seconds по-прежнему std::chrono::time_point, но теперь он основан на секундомерах, поддерживающих прыжок. Программа выводит одну и ту же дату/время, но количество секунд с тех пор, как эпоха теперь больше на 22 с, так как это число секунд прыжка, вставленных между 1970-01-01 и 2005-08-15.