Как разобрать и преобразовать DateTime в RFC 3339 с помощью C ++

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

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

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

person misterion    schedule 24.02.2015    source источник


Ответы (1)


Вот полная, но, к сожалению, неудовлетворительная, но переносимая программа среди самых последних версий реализаций 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 ++ это количество микросекунд с Нового 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 обратно в «разбитую» структуру с помощью формул здесь. Однако, если это ваша конечная цель, было бы более эффективно изменить приведенный выше код для анализа непосредственно в вашу «разбитую» структуру, а не в std::chrono::system_clock::time_point.

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

Обновить

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

#include "date/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';
}

Вы можете найти "date.h" здесь. Это бесплатная библиотека с открытым исходным кодом только для заголовков. По этой ссылке также есть ссылки на полная документация, а для "date.h" даже видеоурок. Хотя видеоурок был создан до реализации функции parse.

Результат вышеупомянутой программы:

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

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

Если вам нужно считать дополнительные секунды с начала эпохи, другую библиотеку по адресу эта же ссылка на GitHub доступна, но не только для заголовка и требует небольшого количества установки < / а>. Но его использование - это простая модификация указанной выше программы:

#include "date/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.

person Howard Hinnant    schedule 28.02.2015