草庐IT

c++ - 使用 Boost.Date_Time 解析带时区的日期时间?

coder 2024-02-19 原文

我想使用 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

最佳答案

我已经仔细研究了它。看来你几乎完全倒霉了:


所以你可以尝试让它与 %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/

有关c++ - 使用 Boost.Date_Time 解析带时区的日期时间?的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. Ruby 解析字符串 - 2

    我有一个字符串input="maybe(thisis|thatwas)some((nice|ugly)(day|night)|(strange(weather|time)))"Ruby中解析该字符串的最佳方法是什么?我的意思是脚本应该能够像这样构建句子:maybethisissomeuglynightmaybethatwassomenicenightmaybethiswassomestrangetime等等,你明白了......我应该一个字符一个字符地读取字符串并构建一个带有堆栈的状态机来存储括号值以供以后计算,还是有更好的方法?也许为此目的准备了一个开箱即用的库?

  3. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  4. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  5. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  6. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  7. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  8. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  9. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  10. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

随机推荐