std :: chrono :: time_point из std :: string

Я пытаюсь преобразовать дату (в виде std::string) в std::chrono::time_point. Для этого я использую Boost Date Time. Ниже вы можете найти минимальный рабочий пример, который это делает. Однако я не понимаю, почему некоторые входные строки, которые, на мой взгляд, недействительны, каким-то образом не вызывают исключения. Я не могу понять, что здесь происходит.

#include <iostream>
#include <chrono>
#include <sstream>
#include <boost/date_time.hpp>

using Clock = std::chrono::system_clock;
using TimePoint = std::chrono::time_point<Clock>;

TimePoint timePointFromString(const std::string& date, const std::string& format) {
  // local takes care of destructing time_input_facet
  auto loc = std::locale(std::locale::classic(), new boost::posix_time::time_input_facet(format));

  std::stringstream ss{date};
  ss.imbue(loc);

  boost::posix_time::ptime pt;
  ss >> pt;

  if (!ss.good()) {
    throw std::runtime_error("Cannot parse string");
  }

  boost::posix_time::ptime time_t_epoch{boost::gregorian::date(1970, 1, 1)};
  boost::posix_time::time_duration diff = pt - time_t_epoch;

  Clock::duration duration{diff.total_nanoseconds()};

  return TimePoint{duration};
}

int main() {
  std::string format{"%Y-%m-%d"};

  std::vector<std::string> strings {"2018", "2018-", "19700101", "19700103", "19700301"};
  for (const auto& s: strings) {
    auto tp = timePointFromString(s, format);
    std::cout << s << ": " << TimePoint::clock::to_time_t(tp) << std::endl;
  }
}

Выход:

2018: 1514764800
2018-: 1514764800
19700101: 23587200
19700103: 23587200
terminate called after throwing an instance of 'std::runtime_error'
  what():  Cannot parse string

Обновление. Я неправильно понял этот фрагмент кода, полагая, что он выполняет своего рода сопоставление с образцом. Это не так (см. Ответ Öö Tiib и комментарии под его ответом)! По-видимому, лучше всего использовать библиотеку даты / времени Говарда Хиннанта.


person gncs    schedule 04.09.2018    source источник


Ответы (2)


Вот как выглядел бы ваш код, если бы вы использовали бесплатную библиотеку даты и времени Говарда Хиннанта с открытым исходным кодом:

#include "date/date.h"
#include <chrono>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

std::chrono::system_clock::time_point
timePointFromString(const std::string& date, const std::string& format)
{
    std::stringstream ss{date};
    std::chrono::system_clock::time_point pt;
    ss >> date::parse(format, pt);
    if (ss.fail())
        throw std::runtime_error("Cannot parse date");
    return pt;
}

int
main()
{
    std::string format{"%Y-%m-%d"};
    std::vector<std::string> strings{"2018", "2018-", "19700101", "19700103", "19700301",
                                     "1970-03-01"};
    for (const auto& s: strings)
    {
        try
        {
            auto tp = timePointFromString(s, format);
            using date::operator<<;
            std::cout << s << ": " << tp << '\n';
        }
        catch (std::exception const& e)
        {
            std::cout << s << ": " << e.what() << '\n';
        }
    }
}

И результат будет:

2018: Cannot parse date
2018-: Cannot parse date
19700101: Cannot parse date
19700103: Cannot parse date
19700301: Cannot parse date
1970-03-01: 1970-03-01 00:00:00.000000

Я добавил действительную строку / дату в конце вектора, чтобы показать, что он принимает, используя этот format. И количество конечных нулей в 1970-03-01 00:00:00.0... будет варьироваться в зависимости от точности std::chrono::system_clock::time_point вашей платформы.

Также этот код должен легко переноситься на C ++ 20:

  • Бросьте #include "date/date.h".
  • Отбросить using date::operator<<;
  • Измените date::parse на std::chrono::parse.

Обновить

Чтобы помочь вам интерпретировать свои результаты:

  • 1514764800 секунд после 1970-01-01 00:00:00 - 2018-01-01 00:00:00
  • 23587200s после 1970-01-01 00:00:00 - это 1970-10-01 00:00:00

(все без учета дополнительных секунд, что является нормой)

person Howard Hinnant    schedule 05.09.2018
comment
Спасибо за это предложение! Думаю, я воспользуюсь этой библиотекой. - person gncs; 05.09.2018

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

С помощью «19700101» и «19700103» он анализирует «1970» пропускает символ, анализирует «10» пропускает символ и находит конец строки, поэтому делает вывод, что оба они относятся к первому октября 1970 года.

С "19700301" он анализирует "1970", пропускает символ, разбирает "30" и выкидывает, поскольку месяц 30 - это ерунда.

Также в вашем описании вывода есть опечатка, вы бросаете «Невозможно проанализировать дату», а не «Невозможно проанализировать строку».

person Öö Tiib    schedule 04.09.2018
comment
Хорошо, я понимаю, но: 1) почему не возникает исключения, когда строка слишком короткая (как насчет ss.good ())? 2) почему пропускаются символы? И, наконец, как мне нужно изменить код, чтобы гарантировать, что эти случаи выдаются? Может проверить размер строки? - person gncs; 05.09.2018
comment
Потому что это не подтверждает, что ваша строка соответствует определенному шаблону. Вместо этого он проверяет то, что на самом деле читает из него. Когда он находит только год и заканчивается строка, тогда предполагается первый январь, а когда он находит только год и месяц, тогда считается первый день месяца. Я не знаю, почему он так устроен. Если вы хотите выполнить проверку шаблона, используйте регулярные выражения или библиотеку Говарда Хиннанта, это лучше, чем увеличение даты и времени, и его код также более приятен для чтения. - person Öö Tiib; 05.09.2018