我想使用 Boost Date Time IO 库解析带时区的日期时间。

#include <boost/date_time.hpp>
#include <ctime>
#include <sstream>

using namespace boost::gregorian;
using namespace boost::posix_time;

std::chrono::system_clock::time_point ParseDate(const std::wstring& dateText, const wchar_t* const format) {
    ptime time;
    std::wstringstream buffer(dateText);
    buffer.imbue(std::locale(std::locale::classic(), new wtime_input_facet(format)));
    buffer >> time;
    auto timeInfo = to_tm(time);
    auto result = std::chrono::system_clock::from_time_t(std::mktime(&timeInfo));
    return result;
}

TEST_CLASS(DateUtilsTest) {
public:
    TEST_METHOD(ShouldParseUtcDate) {
        auto timePoint = ParseDate(L"2016-12-03T07:09:01-05:00", L"%Y-%m-%dT%H:%M:%S%Q");
        auto time = std::chrono::system_clock::to_time_t(timePoint);
        auto timePoint2 = ParseDate(L"2016-12-03T07:09:01", L"%Y-%m-%dT%H:%M:%S");
        auto time2 = std::chrono::system_clock::to_time_t(timePoint2);
        Assert::IsTrue(time != time2);
    }
}

这是 在线示例 : https://wandbox.org/permlink/9GEhah5l4uzhgDta

上面的测试失败了,因为 time == time2。

似乎时区部分对解析结果没有任何影响。

您知道如何使用 Boost 解析带时区的日期时间字符串吗?

实际情况是像“ 2017-12-21T10:47:58.299Z ”这样的字符串(ISO 8601 格式,由 JavaScript 生成: (new Date()).toISOString() ),但我没有找到这种格式的任何文档,有什么想法吗?

环境:
  • Boost-1.65.1
  • 系统是 Windows 10
  • 系统时区为 GMT+8
  • Visual Studio 2015U3
  • 最佳答案

    我已经仔细地看了很久。看来你几乎完全不走运:

    c&#43;&#43; - 使用 Boost.Date_Time 解析带时区的日期时间?-LMLPHP

    所以你可以尝试让它与 %ZP 一起工作。

    做英雄事

    我做了英雄,才发现对wtime_zone和 friend 的支持是......库中的不完整。

    这就是它的全部 g(l)ory:

    Live On Coliru

    #include <boost/date_time.hpp>
    #include <boost/date_time/local_time/local_time_io.hpp>
    #include <boost/date_time/local_time/local_time.hpp>
    #include <boost/date_time/time_zone_base.hpp>
    #include <ctime>
    #include <chrono>
    #include <sstream>
    
    namespace DT = boost::date_time;
    namespace LT = boost::local_time;
    namespace PT = boost::posix_time;
    
    template <typename CharT = wchar_t> struct TypeDefs {
        using ptime   = PT::ptime;
        using tz_base = DT::time_zone_base<ptime, CharT>;
        using tz_ptr  = boost::shared_ptr<DT::time_zone_base<PT::ptime, CharT> >;
        using ptz_t   = LT::posix_time_zone_base<CharT>;
        using ldt_t   = LT::local_date_time_base<ptime, tz_base>;
    };
    
    namespace boost { namespace local_time {
      //! input operator for local_date_time
      template <class CharT, class Traits, typename Defs = TypeDefs<CharT>, typename local_date_time = typename Defs::ldt_t>
      inline
      std::basic_istream<CharT, Traits>&
      operator>>(std::basic_istream<CharT, Traits>& is, local_date_time& ldt)
      {
        using time_zone_ptr = typename Defs::tz_ptr;
        using posix_time_zone = typename Defs::ptz_t;
        boost::io::ios_flags_saver iflags(is);
        typename std::basic_istream<CharT, Traits>::sentry strm_sentry(is, false);
        if (strm_sentry) {
          try {
            typedef typename local_date_time::utc_time_type utc_time_type;
            typedef typename date_time::time_input_facet<utc_time_type, CharT> time_input_facet;
    
            // intermediate objects
            std::basic_string<CharT> tz_str;
            utc_time_type pt(DT::not_a_date_time);
    
            std::istreambuf_iterator<CharT,Traits> sit(is), str_end;
            if(std::has_facet<time_input_facet>(is.getloc())) {
              std::use_facet<time_input_facet>(is.getloc()).get_local_time(sit, str_end, is, pt, tz_str);
            }
            else {
              time_input_facet* f = new time_input_facet();
              std::locale l = std::locale(is.getloc(), f);
              is.imbue(l);
              f->get_local_time(sit, str_end, is, pt, tz_str);
            }
            if(tz_str.empty()) {
              time_zone_ptr null_ptr;
              // a null time_zone_ptr creates a local_date_time that is UTC
              ldt = local_date_time(pt, null_ptr);
            }
            else {
              time_zone_ptr tz_ptr(new posix_time_zone(tz_str));
              // the "date & time" constructor expects the time label to *not* be utc.
              // a posix_tz_string also expects the time label to *not* be utc.
              ldt = local_date_time(pt.date(), pt.time_of_day(), tz_ptr, local_date_time::EXCEPTION_ON_ERROR);
            }
          }
          catch(...) {
            // mask tells us what exceptions are turned on
            std::ios_base::iostate exception_mask = is.exceptions();
            // if the user wants exceptions on failbit, we'll rethrow our
            // date_time exception & set the failbit
            if(std::ios_base::failbit & exception_mask) {
              try { is.setstate(std::ios_base::failbit); }
              catch(std::ios_base::failure&) {} // ignore this one
              throw; // rethrow original exception
            }
            else {
              // if the user want's to fail quietly, we simply set the failbit
              is.setstate(std::ios_base::failbit);
            }
    
          }
        }
        return is;
      }
    } }
    
    template <typename CharT = wchar_t> struct DateUtilsBase : TypeDefs<CharT> {
    
        using base = TypeDefs<CharT>;
        using typename base::ldt_t;
        using typename base::tz_ptr;
        using typename base::ptime;
    
        static std::tm to_tm(ldt_t const& lt) {
            std::tm v = PT::to_tm(lt.local_time());
            v.tm_isdst = lt.is_dst()? 1:0;
            return v;
        }
    
        static tz_ptr s_GMT;
    
        static std::chrono::system_clock::time_point Parse(const std::basic_string<CharT>& dateText, const CharT* const format) {
    
            ldt_t value(LT::special_values::not_a_date_time, s_GMT);
    
            std::basic_istringstream<CharT> buffer(dateText);
            buffer.imbue(std::locale(std::locale::classic(), new DT::time_input_facet<ptime, CharT>(format)));
    
            std::basic_string<CharT> dummy;
            if (buffer >> value && (buffer >> dummy).eof()) {
                std::cout << "DEBUG: " << value.utc_time() << " EOF:" << buffer.eof() << "\n";
                auto timeInfo = PT::to_tm(value.utc_time());
                return std::chrono::system_clock::from_time_t(std::mktime(&timeInfo));
            } else {
                return std::chrono::system_clock::time_point::min();
            }
        }
    };
    
    template <> typename DateUtilsBase<wchar_t>::tz_ptr DateUtilsBase<wchar_t>::s_GMT { new ptz_t(L"GMT") } ;
    template <> typename DateUtilsBase<char>::tz_ptr    DateUtilsBase<char>::s_GMT    { new ptz_t("GMT")  } ;
    
    #if 1
        using DateUtils = DateUtilsBase<wchar_t>;
        #define T(lit) L##lit
    #else
        using DateUtils = DateUtilsBase<char>;
        #define T(lit) lit
    #endif
    
    int main() {
        using namespace std::chrono_literals;
        using C = std::chrono::system_clock;
        std::cout << std::boolalpha << std::unitbuf;
    
        C::time_point with_zone, without_zone;
    
        // all three equivalent:
        with_zone = DateUtils::Parse(T("2016-12-03T07:09:01 PST-05:00"), T("%Y-%m-%dT%H:%M:%S%ZP"));
        with_zone = DateUtils::Parse(T("2016-12-03T07:09:01 -05:00"), T("%Y-%m-%dT%H:%M:%S%ZP"));
        with_zone = DateUtils::Parse(T("2016-12-03T07:09:01-05:00"), T("%Y-%m-%dT%H:%M:%S%ZP"));
    
        without_zone = DateUtils::Parse(T("2016-12-03T07:09:01"), T("%Y-%m-%dT%H:%M:%S"));
        std::cout << "time_point equal? " << (with_zone == without_zone) << "\n";
    
        {
            std::time_t t_with_zone    = C::to_time_t(with_zone);
            std::time_t t_without_zone = C::to_time_t(without_zone);
    
            std::cout << "time_t equal? " << (t_with_zone == t_without_zone) << "\n";
        }
    
        std::cout << (without_zone - with_zone) / 1h << " hours difference\n";
    }
    

    是的。这有点怪物。它打印:
    DEBUG: 2016-Dec-03 12:09:01 EOF:true
    DEBUG: 2016-Dec-03 12:09:01 EOF:true
    DEBUG: 2016-Dec-03 12:09:01 EOF:true
    DEBUG: 2016-Dec-03 07:09:01 EOF:true
    time_point equal? false
    time_t equal? false
    -5 hours difference
    

    回到理智

    事实上,库作者(明智地)决定,即使流是宽的或窄的,local_date_time(或者实际上,只是它们的时区表示中的字符串)也不需要。这就是为什么提供的库 operator>> 只支持 local_date_timeemploys the internal helper function convert_string_type to coerce to narrow-char timezone info :
    time_zone_ptr tz_ptr(new posix_time_zone(date_time::convert_string_type<CharT,char>(tz_str)));
    

    考虑到这一点,让我们删除很多“通用”的东西。剩下的是添加错误处理:
    if (buffer >> value && (buffer >> dummy).eof()) {
        //std::cout << "DEBUG: " << value.utc_time() << " EOF:" << buffer.eof() << "\n";
        auto timeInfo = boost::posix_time::to_tm(value.utc_time());
        return std::chrono::system_clock::from_time_t(std::mktime(&timeInfo));
    } else {
        return std::chrono::system_clock::time_point::min();
    }
    

    Live On Coliru
    #include <boost/date_time.hpp>
    #include <boost/date_time/local_time/local_time_io.hpp>
    #include <ctime>
    #include <chrono>
    #include <sstream>
    
    struct DateUtils {
        using ptime           = boost::posix_time::ptime;
        using time_zone_ptr   = boost::local_time::time_zone_ptr;
        using local_date_time = boost::local_time::local_date_time;
    
        template <typename CharT>
        static std::chrono::system_clock::time_point Parse(const std::basic_string<CharT>& dateText, const CharT* const format) {
            static time_zone_ptr s_GMT(new boost::local_time::posix_time_zone("GMT"));
    
            local_date_time value(boost::local_time::special_values::not_a_date_time, s_GMT);
    
            std::basic_istringstream<CharT> buffer(dateText);
            buffer.imbue(std::locale(std::locale::classic(), new boost::date_time::time_input_facet<ptime, CharT>(format)));
    
            std::basic_string<CharT> dummy;
            if (buffer >> value && (buffer >> dummy).eof()) {
                //std::cout << "DEBUG: " << value.utc_time() << " EOF:" << buffer.eof() << "\n";
                auto timeInfo = boost::posix_time::to_tm(value.utc_time());
                return std::chrono::system_clock::from_time_t(std::mktime(&timeInfo));
            } else {
                return std::chrono::system_clock::time_point::min();
            }
        }
    };
    
    #if 1
        using CharT = wchar_t;
        #define T(lit) L##lit
    #else
        using CharT = char;
        #define T(lit) lit
    #endif
    
    int main() {
        using namespace std::chrono_literals;
        using C = std::chrono::system_clock;
        std::cout << std::boolalpha << std::unitbuf;
    
        C::time_point with_zone, without_zone;
    
        // all three equivalent:
        with_zone = DateUtils::Parse<CharT>(T("2016-12-03T07:09:01 PST-05:00"), T("%Y-%m-%dT%H:%M:%S%ZP"));
        with_zone = DateUtils::Parse<CharT>(T("2016-12-03T07:09:01 -05:00"), T("%Y-%m-%dT%H:%M:%S%ZP"));
        with_zone = DateUtils::Parse<CharT>(T("2016-12-03T07:09:01-05:00"), T("%Y-%m-%dT%H:%M:%S%ZP"));
    
        without_zone = DateUtils::Parse<CharT>(T("2016-12-03T07:09:01"), T("%Y-%m-%dT%H:%M:%S"));
        std::cout << "time_point equal? " << (with_zone == without_zone) << "\n";
    
        {
            std::time_t t_with_zone    = C::to_time_t(with_zone);
            std::time_t t_without_zone = C::to_time_t(without_zone);
    
            std::cout << "time_t equal? " << (t_with_zone == t_without_zone) << "\n";
        }
    
        std::cout << (without_zone - with_zone) / 1h << " hours difference\n";
    }
    

    哇。从 151 LoC 降至 64 LoC。更好的

    打印:
    time_point equal? false
    time_t equal? false
    -5 hours difference
    

    概括:
  • 阅读(所有)文档中的注释
  • 使用 %ZP 作为唯一支持的输入格式
  • 使用 local_date_time 因为该格式字符串被 ptime 忽略(在注释中是这样说的)
  • 使用错误处理来确保输入
  • 中没有留下“未解析”的东西

    关于c++ - 使用 Boost.Date_Time 解析带时区的日期时间?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/47926927/

    10-12 07:35
    查看更多