【问题标题】:How to read an ISO timezone with boost::date_time?如何使用 boost::date_time 读取 ISO 时区?
【发布时间】:2014-09-09 18:25:14
【问题描述】:

我很惊讶 boost::date_time 似乎可以写入它无法读取的日期/时间字符串。

考虑以下示例代码:

#include <boost/date_time/local_time/local_time.hpp>
#include <iostream>
#include <locale>

class PointTime : public boost::local_time::local_date_time {
    typedef boost::local_time::local_time_input_facet   input_facet_t;
    typedef boost::local_time::local_time_facet         output_face_t;

  public:
    static input_facet_t const s_input_facet;
    static output_face_t const s_output_facet;
    static std::locale const s_input_locale;
    static std::locale const s_output_locale;

  public:
    PointTime(std::string const& str);
};

PointTime::input_facet_t const PointTime::s_input_facet("%Y-%m-%dT%H:%M:%S%q", 1);
PointTime::output_face_t const PointTime::s_output_facet("%Y-%m-%dT%H:%M:%S%q",
    boost::local_time::local_time_facet::period_formatter_type(),
    boost::local_time::local_time_facet::special_values_formatter_type(),
    boost::local_time::local_time_facet::date_gen_formatter_type(),
    1);
std::locale const PointTime::s_input_locale(std::locale::classic(), &PointTime::s_input_facet);
std::locale const PointTime::s_output_locale(std::locale::classic(), &PointTime::s_output_facet);

PointTime::PointTime(std::string const& str) : boost::local_time::local_date_time(boost::local_time::not_a_date_time)
{
  std::istringstream is(str);
  is.imbue(s_input_locale);
  is >> *this;
  std::string s;
  is >> s;
  std::cout << "Left after parsing: \"" << s << "\"." << std::endl;
}

int main()
{
  std::string ts("2005-10-15T13:12:11-0700");
  std::cout << "ts = " << ts << std::endl;

  try
  {
    PointTime t(ts);
    std::cout.imbue(PointTime::s_output_locale);
    std::cout << "t =  " << t << std::endl;
  }
  catch (boost::bad_lexical_cast const& error)
  {
    std::cout << error.what() << std::endl;
  }

  return 0;
}

这个输出:

ts = 2005-10-15T13:12:11-0700
解析后留下:“-0700”。
t = 2005-10-15T13:12:11Z

我明白为什么会这样:%q 格式被记录为“仅输出”, 但我找不到真正读回这个字符串的方法?!我该怎么做?

【问题讨论】:

  • Parse ISO 8601 durations 的可能重复项
  • @SebastianK:呵呵——这和我问的完全无关。
  • @Aleric:你问的是题外话。如果您问“如何阅读...”,那么它将被视为 SO 的主题。
  • 这个问题似乎是题外话,因为它是关于答案是基于意见的。
  • @SebastianK:问题是“如何使用 boost::date_time 读取 ISO 时区?” - 你在自相矛盾。

标签: c++ datetime boost


【解决方案1】:

最后我使用了一个自定义的方法来读回这些字符串。

我使用派生自 boost::posix_time::ptime 的类来节省存储空间(超过派生自 boost::local_time::local_date_time),因为我不再使用 boost-date-time 的时区支持。

但是,当将日期打印为字符串时,我希望它以“Z”结尾,以明确它是祖鲁时间(UTC、GMT)而不是本地时间。我通过使用带有以“Z”结尾的格式字符串的输出构面来实现这一点。

PointTime 类定义了一些(输入和输出)方面(即ISOInputFacetExtendedISOInputFacet 和称为LLInputFacet 的混合方面,因为 Linden Lab 认为这是 ISO 标准并将其用于SecondLife(tm),呃。同样ISOOutputFacetExtendedISOOutputFacetLLOutputFacet

该类实例化六个方面中每一个方面的静态版本,因为我不想在每次创建、读取或写入 PointTime 时为一个方面调用 new/delete。

主要技巧在这个函数中:

std::istream& operator>>(std::istream& is, PointTime& point_time)
{
  is >> static_cast<boost::posix_time::ptime&>(point_time);
  std::locale loc(is.getloc());
  if (std::has_facet<PointTime::InputFacet>(loc))
  {
    std::use_facet<PointTime::InputFacet>(loc).parse_timezone(is, point_time);
  }
  return is;
}

查看正在使用的输入方面,当它是从PointTime::InputFacet 派生的方面时,它会调用成员函数来解析可能的剩余时区。

我写的测试代码如下。也许它可以作为其他人的示例代码。

#include <boost/date_time/posix_time/posix_time_io.hpp>
#include <iostream>
#include <locale>
#include <cassert>

class TZSignOrZ;

class PointTime : public boost::posix_time::ptime {
  public:
    struct InputFacet : public boost::posix_time::time_input_facet {
        InputFacet(char const* format, size_t ref_arg) : boost::posix_time::time_input_facet(format, ref_arg) { }
        static void parse_iso_timezone(std::istream& is, PointTime& point_time, bool colon);
        static void parse_hours_and_minutes(std::istream& is, PointTime& point_time, TZSignOrZ const& sign, bool colon);
        virtual void parse_timezone(std::istream& is, PointTime& point_time) const = 0;
    };

    struct ISOInputFacet : public InputFacet {
        ISOInputFacet(size_t ref_arg = 0) : InputFacet("%Y%m%dT%H%M%S%F", ref_arg) { }
        /*virtual*/ void parse_timezone(std::istream& is, PointTime& point_time) const;
    };

    struct ExtendedISOInputFacet : public InputFacet {
        ExtendedISOInputFacet(size_t ref_arg = 0) : InputFacet("%Y-%m-%d %H:%M:%S%F", ref_arg) { }
        /*virtual*/ void parse_timezone(std::istream& is, PointTime& point_time) const;
    };

    struct LLInputFacet : public InputFacet {
        LLInputFacet(size_t ref_arg = 0) : InputFacet("%Y-%m-%dT%H:%M:%S%F", ref_arg) { }
        /*virtual*/ void parse_timezone(std::istream& is, PointTime& point_time) const;
    };

    struct OutputFacet : public boost::posix_time::time_facet {
        OutputFacet(char const* format, size_t ref_arg) :
          boost::posix_time::time_facet(
              format, boost::posix_time::time_facet::period_formatter_type(),
              boost::posix_time::time_facet::special_values_formatter_type(),
              boost::posix_time::time_facet::date_gen_formatter_type(), ref_arg) { }
    };

    struct ISOOutputFacet : public OutputFacet {
        ISOOutputFacet(size_t ref_arg = 0) : OutputFacet("%Y%m%dT%H%M%S%FZ", ref_arg) { }
    };

    struct ExtendedISOOutputFacet : public OutputFacet {
        ExtendedISOOutputFacet(size_t ref_arg = 0) : OutputFacet("%Y-%m-%d %H:%M:%S%FZ", ref_arg) { }
    };

    struct LLOutputFacet : public OutputFacet {
        LLOutputFacet(size_t ref_arg = 0) : OutputFacet("%Y-%m-%dT%H:%M:%S%FZ", ref_arg) { }
    };

    static ISOInputFacet s_iso_input_facet;
    static ExtendedISOInputFacet s_extended_iso_input_facet;
    static LLInputFacet s_ll_input_facet;
    static ISOOutputFacet s_iso_output_facet;
    static ExtendedISOOutputFacet s_extended_iso_output_facet;
    static LLOutputFacet s_ll_output_facet;

  public:
    PointTime(void) { }
    PointTime(std::string const& str, InputFacet const& input_facet = s_extended_iso_input_facet);

    friend std::istream& operator>>(std::istream& os, PointTime& point_time);
};

PointTime::ISOInputFacet PointTime::s_iso_input_facet(1);
PointTime::ExtendedISOInputFacet PointTime::s_extended_iso_input_facet(1);
PointTime::LLInputFacet PointTime::s_ll_input_facet(1);
PointTime::ISOOutputFacet PointTime::s_iso_output_facet(1);
PointTime::ExtendedISOOutputFacet PointTime::s_extended_iso_output_facet(1);
PointTime::LLOutputFacet PointTime::s_ll_output_facet(1);

PointTime::PointTime(std::string const& str, InputFacet const& input_facet) : boost::posix_time::ptime(boost::posix_time::not_a_date_time)
{
  std::istringstream is(str);
  is.imbue(std::locale(std::locale::classic(), &input_facet));
  is >> *this;
}

std::istream& operator>>(std::istream& is, PointTime& point_time)
{
  is >> static_cast<boost::posix_time::ptime&>(point_time);
  std::locale loc(is.getloc());
  if (std::has_facet<PointTime::InputFacet>(loc))
  {
    std::use_facet<PointTime::InputFacet>(loc).parse_timezone(is, point_time);
  }
  return is;
}

struct TZSignOrZ {
  char c;
  friend std::istream& operator>>(std::istream& is, TZSignOrZ& sign)
  {
    sign.c = is.get();
    if (!is.good() || (sign.c != 'Z' && sign.c != '+' && sign.c != '-'))
    {
      if (!is.fail())
      {
        is.putback(sign.c);
      }
      throw std::runtime_error("Failed to read TZSignOrZ: first character after date-time is not a 'Z' or a sign.");
    }
    return is;
  }
  bool is_utc(void) const { return c == 'Z'; }
  bool is_negative(void) const { return c == '-'; }
};

struct TZColon {
  friend std::istream& operator>>(std::istream& is, TZColon& colon)
  {
    char c = is.get();
    if (!is.good() || c != ':')
      throw std::runtime_error("Failed to read TZColon: expected a colon between hours and minutes in time zone of date-time.");
    return is;
  }
};

struct TZTwoDigits {
  unsigned char val;
  friend std::istream& operator>>(std::istream& is, TZTwoDigits& digits)
  {
    char c1, c2;
    is >> std::noskipws >> c1 >> c2;
    if (!is.good() || !std::isdigit(c1) || !std::isdigit(c2))
      throw std::runtime_error("Failed to read TZTwoDigits: expected two digits for hours or minutes part in time zone of data-time.");
    digits.val = c2 - '0' + 10 * (c1 - '0');
    return is;
  }
};

void PointTime::InputFacet::parse_hours_and_minutes(std::istream& is, PointTime& point_time, TZSignOrZ const& sign, bool colon)
{
  TZTwoDigits hours, minutes;
  is >> hours;
  if (colon)
  {
    TZColon colon;
    is >> colon;
  }
  is >> minutes;
  boost::posix_time::time_duration duration(hours.val, minutes.val, 0, 0);
  if (sign.is_negative())
    point_time += duration;
  else
    point_time -= duration;
}

void PointTime::InputFacet::parse_iso_timezone(std::istream& is, PointTime& point_time, bool colon)
{
  if (!is.good())
    return; // Don't throw when the input stream is already bad.
  TZSignOrZ sign; 
  try
  {
    is >> sign;
  }
  catch (std::runtime_error const&)
  {
    // It is actually allowed to have no time zone.
    return;
  }
  parse_hours_and_minutes(is, point_time, sign, colon);
}

void PointTime::ISOInputFacet::parse_timezone(std::istream& is, PointTime& point_time) const
{
  parse_iso_timezone(is, point_time, false);
}

void PointTime::ExtendedISOInputFacet::parse_timezone(std::istream& is, PointTime& point_time) const
{
  parse_iso_timezone(is, point_time, true);
}

void PointTime::LLInputFacet::parse_timezone(std::istream& is, PointTime& point_time) const
{
  if (!is.good())
    return; // Don't throw when the input stream is already bad.
  TZSignOrZ sign;
  is >> sign;
  if (sign.is_utc())
    return;
  parse_hours_and_minutes(is, point_time, sign, false);
}

int main() 
{
  std::string test_strings[] = {
    "2014-09-10T00:12:34+0230",
    "2014-09-10T00:12:34.000567+0230",
    "2014-09-10T00:12:34.567+0230",
    "2014-09-10T21:32:34-0230",
    "2014-09-10T21:32:34.000567-0230",
    "2014-09-10T21:32:34.567-0230",
    "2014-09-10 00:12:34+02:30",
    "2014-09-10 00:12:34.000567+02:30",
    "2014-09-10 00:12:34.567+02:30",
    "2014-09-10 21:32:34-02:30",
    "2014-09-10 21:32:34.000567-02:30",
    "2014-09-10 21:32:34.567-02:30",
    "2014-09-10 00:12:34 hello!",
    "2014-09-10 00:12:34.000567***",
    "2014-09-10 00:12:34.567 hello!",
    "2014-09-10T00:12:34 +0230",
    "2014-09-10T00:12:34+ 0230",
    "2014-09-10T00:12:34+02 30",
    "2014-09-10T00:12:34 +02:30",
    "2014-09-10T00:12:34+ 02:30",
    "2014-09-10T00:12:34+02: 30",
    "2014-09-10T00:12:34+02 :30"
  };
  int const n = sizeof(test_strings) / sizeof(test_strings[0]);

  PointTime::InputFacet const* facets[] = {
    &PointTime::s_ll_input_facet,
    &PointTime::s_extended_iso_input_facet
  };

  std::cout << std::left << std::setw(32) << "INPUT" << "    " <<
  std::setw(40) << "LL input format" << "    " <<
  std::setw(40) << "Extended ISO input format" << std::endl;
  for (int i = 0 ; i < n ; ++i)
  {
    std::cout << std::left << std::setw(32) << test_strings[i];

    for (int j = 0; j < 2; ++j)
    {
      try
      {
        PointTime t;
        std::istringstream is(test_strings[i]);
        is.imbue(std::locale(std::locale::classic(), facets[j]));
        is >> t;
        std::string leftover;
        is >> std::noskipws;
        std::getline(is, leftover);
        std::ostringstream oss;
        oss.imbue(std::locale(std::locale::classic(), &PointTime::s_extended_iso_output_facet));
        oss << t;
        if (!leftover.empty())
        {
          oss << " [\"" << leftover << "\"]";
        }
        std::cout << " -- " << std::setw(40) << oss.str();
      }
      catch (boost::bad_lexical_cast const& error)
      {
        std::cout << " -- " << std::setw(40) << "invalid date-time";
      }
      catch (std::runtime_error const& error)
      {
        std::cout << " -- " << std::setw(40) << "invalid TZ";
      }
    }
    std::cout << std::endl;
  }

  return 0;
}

这个程序的输出是:

INPUT                               LL input format                             Extended ISO input format               
2014-09-10T00:12:34+0230         -- 2014-09-09 21:42:34Z                     -- invalid TZ                              
2014-09-10T00:12:34.000567+0230  -- 2014-09-09 21:42:34.000567Z              -- invalid TZ                              
2014-09-10T00:12:34.567+0230     -- 2014-09-09 21:42:34.567000Z              -- invalid TZ                              
2014-09-10T21:32:34-0230         -- 2014-09-11 00:02:34Z                     -- invalid TZ                              
2014-09-10T21:32:34.000567-0230  -- 2014-09-11 00:02:34.000567Z              -- invalid TZ                              
2014-09-10T21:32:34.567-0230     -- 2014-09-11 00:02:34.567000Z              -- invalid TZ                              
2014-09-10 00:12:34+02:30        -- invalid TZ                               -- 2014-09-09 21:42:34Z                    
2014-09-10 00:12:34.000567+02:30 -- invalid TZ                               -- 2014-09-09 21:42:34.000567Z             
2014-09-10 00:12:34.567+02:30    -- invalid TZ                               -- 2014-09-09 21:42:34.567000Z             
2014-09-10 21:32:34-02:30        -- invalid TZ                               -- 2014-09-11 00:02:34Z                    
2014-09-10 21:32:34.000567-02:30 -- invalid TZ                               -- 2014-09-11 00:02:34.000567Z             
2014-09-10 21:32:34.567-02:30    -- invalid TZ                               -- 2014-09-11 00:02:34.567000Z             
2014-09-10 00:12:34 hello!       -- invalid TZ                               -- 2014-09-10 00:12:34Z [" hello!"]        
2014-09-10 00:12:34.000567***    -- invalid TZ                               -- 2014-09-10 00:12:34.000567Z ["***"]     
2014-09-10 00:12:34.567 hello!   -- invalid TZ                               -- 2014-09-10 00:12:34.567000Z [" hello!"] 
2014-09-10T00:12:34 +0230        -- invalid TZ                               -- 2014-09-10 00:12:34Z [" +0230"]         
2014-09-10T00:12:34+ 0230        -- invalid TZ                               -- invalid TZ                              
2014-09-10T00:12:34+02 30        -- invalid TZ                               -- invalid TZ                              
2014-09-10T00:12:34 +02:30       -- invalid TZ                               -- 2014-09-10 00:12:34Z [" +02:30"]        
2014-09-10T00:12:34+ 02:30       -- invalid TZ                               -- invalid TZ                              
2014-09-10T00:12:34+02: 30       -- invalid TZ                               -- invalid TZ                              
2014-09-10T00:12:34+02 :30       -- invalid TZ                               -- invalid TZ

【讨论】:

  • 乍一看,这看起来很棒。我可能会为我自己的代码库查看此内容。谢谢
  • 我在实际代码中做的最后一步:将 PointTime 重命名为 TimePoint。使用 PointTime 没有任何意义(我对缩写 ptime 感到困惑,但这可能代表 POSIX 时间,即使它位于命名空间 posix_time 中。可能更好的名称应该是 boost::date_time::posix_time::point)。
【解决方案2】:

可悲的是,是的,目前这是一个限制

添加了一些新标志,并覆盖了其他标志。输入系统仅支持特定标志,因此,并非所有适用于输出的标志都适用于输入 (我们目前正在努力纠正这种情况)。

同时,您也许可以利用 Boost Locale 的输入/输出工具:http://www.boost.org/doc/libs/1_55_0/libs/locale/doc/html/group__manipulators.html

【讨论】:

    猜你喜欢
    • 2017-12-02
    • 2011-09-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-07
    • 2022-10-16
    • 2018-07-19
    相关资源
    最近更新 更多