
#ifndef _DATE_TIME_FACET__HPP__
#define _DATE_TIME_FACET__HPP__

/* Copyright (c) 2004-2005 CrystalClear Software, Inc.
 * Use, modification and distribution is subject to the 
 * Boost Software License, Version 1.0. (See accompanying
 * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
 * Author:  Martin Andrian, Jeff Garland, Bart Garst
 * $Date: 2008-11-23 06:13:35 -0500 (Sun, 23 Nov 2008) $
 */

#include <cctype>
#include <locale>
#include <limits>
#include <string>
#include <sstream>
#include <iomanip>
#include <iterator> // i/ostreambuf_iterator
#include <exception>
#include <boost/assert.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/throw_exception.hpp>
#include <boost/algorithm/string/erase.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/date_time/compiler_config.hpp>
#include <boost/date_time/date_facet.hpp>
#include <boost/date_time/string_convert.hpp>
#include <boost/date_time/special_defs.hpp>

namespace boost {
namespace date_time {

  template <class CharT>
  struct time_formats {
    public:
      typedef CharT char_type;
      static const char_type fractional_seconds_format[3];               // f
      static const char_type fractional_seconds_or_none_format[3];       // F
      static const char_type seconds_with_fractional_seconds_format[3];  // s
      static const char_type seconds_format[3];                          // S
      static const char_type hours_format[3];                            // H
      static const char_type unrestricted_hours_format[3];               // O
      static const char_type standard_format[9];                         // x X
      static const char_type zone_abbrev_format[3];                      // z
      static const char_type zone_name_format[3];                        // Z
      static const char_type zone_iso_format[3];                         // q
      static const char_type zone_iso_extended_format[3];                // Q
      static const char_type posix_zone_string_format[4];                // ZP
      static const char_type duration_sign_negative_only[3];             // -
      static const char_type duration_sign_always[3];                    // +
      static const char_type duration_seperator[2];
      static const char_type negative_sign[2];                           //-
      static const char_type positive_sign[2];                           //+
      static const char_type iso_time_format_specifier[18];
      static const char_type iso_time_format_extended_specifier[22];
      //default ptime format is YYYY-Mon-DD HH:MM:SS[.fff...][ zzz]
      static const char_type default_time_format[23]; 
      // default_time_input_format uses a posix_time_zone_string instead of a time zone abbrev
      static const char_type default_time_input_format[24]; 
      //default time_duration format is HH:MM:SS[.fff...]
      static const char_type default_time_duration_format[11];
  };

  template <class CharT>  
  const typename time_formats<CharT>::char_type 
  time_formats<CharT>::fractional_seconds_format[3] = {'%','f'};

  template <class CharT>  
  const typename time_formats<CharT>::char_type 
  time_formats<CharT>::fractional_seconds_or_none_format[3] = {'%','F'};

  template <class CharT>  
  const typename time_formats<CharT>::char_type 
  time_formats<CharT>::seconds_with_fractional_seconds_format[3] = 
    {'%','s'};

  template <class CharT>
  const typename time_formats<CharT>::char_type 
  time_formats<CharT>::seconds_format[3] =  {'%','S'};

  template <class CharT>
  const typename time_formats<CharT>::char_type 
  time_formats<CharT>::hours_format[3] =  {'%','H'};

  template <class CharT>
  const typename time_formats<CharT>::char_type 
  time_formats<CharT>::unrestricted_hours_format[3] =  {'%','O'};

  template <class CharT>  
  const typename time_formats<CharT>::char_type 
  //time_formats<CharT>::standard_format[5] =  {'%','c',' ','%','z'};
  time_formats<CharT>::standard_format[9] =  {'%','x',' ','%','X',' ','%','z'};

  template <class CharT>  
  const typename time_formats<CharT>::char_type 
  time_formats<CharT>::zone_abbrev_format[3] =  {'%','z'};

  template <class CharT>  
  const typename time_formats<CharT>::char_type 
  time_formats<CharT>::zone_name_format[3] =  {'%','Z'};

  template <class CharT>  
  const typename time_formats<CharT>::char_type 
  time_formats<CharT>::zone_iso_format[3] =  {'%','q'};

  template <class CharT>  
  const typename time_formats<CharT>::char_type 
  time_formats<CharT>::zone_iso_extended_format[3] ={'%','Q'};

  template <class CharT>  
  const typename time_formats<CharT>::char_type 
  time_formats<CharT>::posix_zone_string_format[4] ={'%','Z','P'};

  template <class CharT>  
  const typename time_formats<CharT>::char_type 
  time_formats<CharT>::duration_seperator[2] =  {':'};

  template <class CharT>  
  const typename time_formats<CharT>::char_type 
  time_formats<CharT>::negative_sign[2] =  {'-'};

  template <class CharT>  
  const typename time_formats<CharT>::char_type 
  time_formats<CharT>::positive_sign[2] =  {'+'};

  template <class CharT>  
  const typename time_formats<CharT>::char_type 
  time_formats<CharT>::duration_sign_negative_only[3] ={'%','-'};

  template <class CharT>  
  const typename time_formats<CharT>::char_type 
  time_formats<CharT>::duration_sign_always[3] ={'%','+'};

  template <class CharT>  
  const typename time_formats<CharT>::char_type 
  time_formats<CharT>::iso_time_format_specifier[18] =  
    {'%', 'Y', '%', 'm', '%', 'd', 'T', 
     '%', 'H', '%', 'M', '%', 'S', '%', 'F', '%','q' };

  template <class CharT>  
  const typename time_formats<CharT>::char_type 
  time_formats<CharT>::iso_time_format_extended_specifier[22] =  
    {'%', 'Y', '-', '%', 'm', '-', '%', 'd', ' ', 
     '%', 'H', ':', '%', 'M', ':', '%', 'S', '%', 'F','%','Q'};

  template <class CharT>  
  const typename time_formats<CharT>::char_type 
  time_formats<CharT>::default_time_format[23] = 
    {'%','Y','-','%','b','-','%','d',' ',
      '%','H',':','%','M',':','%','S','%','F',' ','%','z'};

  template <class CharT>  
  const typename time_formats<CharT>::char_type 
  time_formats<CharT>::default_time_input_format[24] = 
    {'%','Y','-','%','b','-','%','d',' ',
      '%','H',':','%','M',':','%','S','%','F',' ','%','Z','P'};

  template <class CharT>  
  const typename time_formats<CharT>::char_type 
  time_formats<CharT>::default_time_duration_format[11] = 
    {'%','H',':','%','M',':','%','S','%','F'};



  /*! Facet used for format-based output of time types 
   * This class provides for the use of format strings to output times.  In addition
   * to the flags for formatting date elements, the following are the allowed format flags:
   *  - %x %X => default format - enables addition of more flags to default (ie. "%x %X %z")
   *  - %f => fractional seconds ".123456"
   *  - %F => fractional seconds or none: like frac sec but empty if frac sec == 0
   *  - %s => seconds w/ fractional sec "02.123" (this is the same as "%S%f) 
   *  - %S => seconds "02"
   *  - %z => abbreviated time zone "EDT"
   *  - %Z => full time zone name "Eastern Daylight Time"
   */
  template <class time_type,
            class CharT, 
            class OutItrT = std::ostreambuf_iterator<CharT, std::char_traits<CharT> > >
  class time_facet : 
    public boost::date_time::date_facet<typename time_type::date_type , CharT, OutItrT> {
   public:
    typedef typename time_type::date_type date_type;
    typedef typename time_type::time_duration_type time_duration_type;
    typedef boost::date_time::period<time_type,time_duration_type> period_type;
    typedef boost::date_time::date_facet<typename time_type::date_type, CharT, OutItrT> base_type;
    typedef typename base_type::string_type string_type;
    typedef typename base_type::char_type   char_type;
    typedef typename base_type::period_formatter_type period_formatter_type;
    typedef typename base_type::special_values_formatter_type special_values_formatter_type;
    typedef typename base_type::date_gen_formatter_type date_gen_formatter_type;
    static const char_type* fractional_seconds_format;                // %f
    static const char_type* fractional_seconds_or_none_format;        // %F
    static const char_type* seconds_with_fractional_seconds_format;   // %s
    static const char_type* seconds_format;                           // %S
    static const char_type* hours_format;                             // %H
    static const char_type* unrestricted_hours_format;                // %O
    static const char_type* standard_format;                          // %x X
    static const char_type* zone_abbrev_format;                       // %z
    static const char_type* zone_name_format;                         // %Z
    static const char_type* zone_iso_format;                          // %q
    static const char_type* zone_iso_extended_format;                 // %Q
    static const char_type* posix_zone_string_format;                 // %ZP
    static const char_type* duration_seperator;
    static const char_type* duration_sign_always;                     // %+
    static const char_type* duration_sign_negative_only;              // %-
    static const char_type* negative_sign;                            //-
    static const char_type* positive_sign;                            //+
    static const char_type* iso_time_format_specifier;
    static const char_type* iso_time_format_extended_specifier;

    //default ptime format is YYYY-Mon-DD HH:MM:SS[.fff...][ zzz]
    static const char_type* default_time_format; 
    //default time_duration format is HH:MM:SS[.fff...]
    static const char_type* default_time_duration_format;
    static std::locale::id id;

#if defined (__SUNPRO_CC) && defined (_RWSTD_VER)
      std::locale::id& __get_id (void) const { return id; }
#endif

    //! sets default formats for ptime, local_date_time, and time_duration
    explicit time_facet(::size_t a_ref = 0)
      : base_type(default_time_format, period_formatter_type(), special_values_formatter_type(), date_gen_formatter_type(), a_ref),
        m_time_duration_format(string_type(duration_sign_negative_only) + default_time_duration_format)
    {}

    //! Construct the facet with an explicitly specified format
    explicit time_facet(const char_type* a_format,
                        period_formatter_type period_formatter = period_formatter_type(), 
                        const special_values_formatter_type& special_value_formatter = special_values_formatter_type(), 
                        date_gen_formatter_type dg_formatter = date_gen_formatter_type(),
                         ::size_t a_ref = 0) 
      : base_type(a_format, 
                  period_formatter,
                  special_value_formatter, 
                  dg_formatter, 
                  a_ref),
        m_time_duration_format(string_type(duration_sign_negative_only) + default_time_duration_format)
    {}

    //! Changes format for time_duration
    void time_duration_format(const char_type* const format) 
    {
      m_time_duration_format = format;
    }

    virtual void set_iso_format()
    {
      this->m_format = iso_time_format_specifier;
    }
    virtual void set_iso_extended_format()
    {
      this->m_format = iso_time_format_extended_specifier;
    }

    OutItrT put(OutItrT a_next, 
                std::ios_base& a_ios, 
                char_type a_fill, 
                const time_type& a_time) const 
    {
      if (a_time.is_special()) { 
        return this->do_put_special(a_next, a_ios, a_fill, 
                              a_time.date().as_special());
      }
      string_type format(this->m_format);

      string_type frac_str;
      if (format.find(seconds_with_fractional_seconds_format) != string_type::npos) {
        // replace %s with %S.nnn 
        frac_str = 
          fractional_seconds_as_string(a_time.time_of_day(), false);
        char_type sep = std::use_facet<std::numpunct<char_type> >(a_ios.getloc()).decimal_point();
        
        string_type replace_string(seconds_format);
        replace_string += sep;
        replace_string += frac_str;
        boost::algorithm::replace_all(format, 
                                      seconds_with_fractional_seconds_format, 
                                      replace_string);
      }
      /* NOTE: replacing posix_zone_string_format must be done BEFORE
       * zone_name_format: "%ZP" & "%Z", if Z is checked first it will 
       * incorrectly replace a zone_name where a posix_string should go */
      if (format.find(posix_zone_string_format) != string_type::npos) {
        if(a_time.zone_abbrev().empty()) {
          // if zone_abbrev() returns an empty string, we want to
          // erase posix_zone_string_format from format
          boost::algorithm::erase_all(format, posix_zone_string_format);
        }
        else{
          boost::algorithm::replace_all(format,
                                        posix_zone_string_format,
                                        a_time.zone_as_posix_string());
        }
      }
      if (format.find(zone_name_format) != string_type::npos) {
        if(a_time.zone_name().empty()) {
          /* TODO: this'll probably create problems if a user places 
           * the zone_*_format flag in the format with a ptime. This 
           * code removes the flag from the default formats */

          // if zone_name() returns an empty string, we want to
          // erase zone_name_format & one preceeding space
          std::basic_ostringstream<char_type> ss;
          ss << ' ' << zone_name_format;
          boost::algorithm::erase_all(format, ss.str());
        }
        else{
          boost::algorithm::replace_all(format,
                                        zone_name_format,
                                        a_time.zone_name());
        }
      }
      if (format.find(zone_abbrev_format) != string_type::npos) {
        if(a_time.zone_abbrev(false).empty()) {
          /* TODO: this'll probably create problems if a user places 
           * the zone_*_format flag in the format with a ptime. This 
           * code removes the flag from the default formats */

          // if zone_abbrev() returns an empty string, we want to
          // erase zone_abbrev_format & one preceeding space
          std::basic_ostringstream<char_type> ss;
          ss << ' ' << zone_abbrev_format;
          boost::algorithm::erase_all(format, ss.str());
        }
        else{
          boost::algorithm::replace_all(format,
                                        zone_abbrev_format,
                                        a_time.zone_abbrev(false));
        }
      }
      if (format.find(zone_iso_extended_format) != string_type::npos) {
        if(a_time.zone_name(true).empty()) {
          /* TODO: this'll probably create problems if a user places 
           * the zone_*_format flag in the format with a ptime. This 
           * code removes the flag from the default formats */

          // if zone_name() returns an empty string, we want to
          // erase zone_iso_extended_format from format
          boost::algorithm::erase_all(format, zone_iso_extended_format);
        }
        else{
          boost::algorithm::replace_all(format,
                                        zone_iso_extended_format,
                                        a_time.zone_name(true));
        }
      }

      if (format.find(zone_iso_format) != string_type::npos) {
        if(a_time.zone_abbrev(true).empty()) {
          /* TODO: this'll probably create problems if a user places 
           * the zone_*_format flag in the format with a ptime. This 
           * code removes the flag from the default formats */

          // if zone_abbrev() returns an empty string, we want to
          // erase zone_iso_format from format
          boost::algorithm::erase_all(format, zone_iso_format);
        }
        else{
          boost::algorithm::replace_all(format,
                                        zone_iso_format,
                                        a_time.zone_abbrev(true));
        }
      }
      if (format.find(fractional_seconds_format) != string_type::npos) {
        // replace %f with nnnnnnn
        if (!frac_str.size()) {
          frac_str = fractional_seconds_as_string(a_time.time_of_day(), false);
        }
        boost::algorithm::replace_all(format,
                                      fractional_seconds_format, 
                                      frac_str);
      }

      if (format.find(fractional_seconds_or_none_format) != string_type::npos) {
        // replace %F with nnnnnnn or nothing if fs == 0
        frac_str = 
          fractional_seconds_as_string(a_time.time_of_day(), true);
        if (frac_str.size()) {
          char_type sep = std::use_facet<std::numpunct<char_type> >(a_ios.getloc()).decimal_point();
          string_type replace_string;
          replace_string += sep;
          replace_string += frac_str;
          boost::algorithm::replace_all(format,
                                        fractional_seconds_or_none_format, 
                                        replace_string);
        }
        else {
          boost::algorithm::erase_all(format,
                                      fractional_seconds_or_none_format);
        }
      }

      return this->do_put_tm(a_next, a_ios, a_fill, 
                       to_tm(a_time), format);
    }

    //! put function for time_duration
    OutItrT put(OutItrT a_next, 
                std::ios_base& a_ios, 
                char_type a_fill, 
                const time_duration_type& a_time_dur) const 
    {
      if (a_time_dur.is_special()) { 
        return this->do_put_special(a_next, a_ios, a_fill, 
                              a_time_dur.get_rep().as_special());
      }

      string_type format(m_time_duration_format);
      if (a_time_dur.is_negative()) {
        // replace %- with minus sign.  Should we use the numpunct facet?
        boost::algorithm::replace_all(format, 
                                      duration_sign_negative_only, 
                                      negative_sign);
          // remove all the %+ in the string with '-'
        boost::algorithm::replace_all(format, 
                                      duration_sign_always, 
                                      negative_sign);
      }
      else { //duration is positive
        // remove all the %- combos from the string
        boost::algorithm::erase_all(format, duration_sign_negative_only);
        // remove all the %+ in the string with '+'
        boost::algorithm::replace_all(format, 
                                      duration_sign_always, 
                                      positive_sign);
      }

      /*
       * It is possible for a time duration to span more then 24 hours.  
       * Standard time_put::put is obliged to behave the same as strftime 
       * (See ISO 14882-2003 22.2.5.3.1 par. 1) and strftime's behavior is 
       * unspecified for the case when tm_hour field is outside 0-23 range 
       * (See ISO 9899-1999 7.23.3.5 par. 3). So we must output %H and %O
       * here ourself.
       */
      string_type hours_str;
      if (format.find(unrestricted_hours_format) != string_type::npos) {
        hours_str = hours_as_string(a_time_dur);
        boost::algorithm::replace_all(format, unrestricted_hours_format, hours_str);
      }
      // We still have to process restricted hours format specifier. In order to
      // support parseability of durations in ISO format (%H%M%S), we'll have to
      // restrict the stringified hours length to 2 characters.
      if (format.find(hours_format) != string_type::npos) {
        if (hours_str.empty())
          hours_str = hours_as_string(a_time_dur);
        BOOST_ASSERT(hours_str.length() <= 2);
        boost::algorithm::replace_all(format, hours_format, hours_str);
      }

      string_type frac_str;
      if (format.find(seconds_with_fractional_seconds_format) != string_type::npos) {
        // replace %s with %S.nnn 
        frac_str = 
          fractional_seconds_as_string(a_time_dur, false);
        char_type sep = std::use_facet<std::numpunct<char_type> >(a_ios.getloc()).decimal_point();
        
        string_type replace_string(seconds_format);
        replace_string += sep;
        replace_string += frac_str;
        boost::algorithm::replace_all(format, 
                                      seconds_with_fractional_seconds_format, 
                                      replace_string);
      }
      if (format.find(fractional_seconds_format) != string_type::npos) {
        // replace %f with nnnnnnn
        if (!frac_str.size()) {
          frac_str = fractional_seconds_as_string(a_time_dur, false);
        }
        boost::algorithm::replace_all(format,
                                      fractional_seconds_format, 
                                      frac_str);
      }

      if (format.find(fractional_seconds_or_none_format) != string_type::npos) {
        // replace %F with nnnnnnn or nothing if fs == 0
        frac_str = 
          fractional_seconds_as_string(a_time_dur, true);
        if (frac_str.size()) {
          char_type sep = std::use_facet<std::numpunct<char_type> >(a_ios.getloc()).decimal_point();
          string_type replace_string;
          replace_string += sep;
          replace_string += frac_str;
          boost::algorithm::replace_all(format,
                                        fractional_seconds_or_none_format, 
                                        replace_string);
        }
        else {
          boost::algorithm::erase_all(format,
                                      fractional_seconds_or_none_format);
        }
      }

      return this->do_put_tm(a_next, a_ios, a_fill, 
                       to_tm(a_time_dur), format);
    }
    
    OutItrT put(OutItrT next, std::ios_base& a_ios, 
                char_type fill, const period_type& p) const 
    {
      return this->m_period_formatter.put_period(next, a_ios, fill,p,*this);
    }


  protected:

    static 
    string_type 
    fractional_seconds_as_string(const time_duration_type& a_time,
                                 bool null_when_zero) 
    {
      typename time_duration_type::fractional_seconds_type frac_sec = 
        a_time.fractional_seconds();

      if (null_when_zero && (frac_sec == 0)) {
        return string_type();
      }

      //make sure there is no sign
      return integral_as_string(
        date_time::absolute_value(frac_sec),
        time_duration_type::num_fractional_digits());
    }

    static
    string_type
    hours_as_string(const time_duration_type& a_time, int width = 2)
    {
      return integral_as_string(date_time::absolute_value(a_time.hours()), width);
    }

    template< typename IntT >
    static
    string_type
    integral_as_string(IntT val, int width = 2)
    {
      std::basic_ostringstream<char_type> ss;
      ss.imbue(std::locale::classic()); // don't want any formatting
      ss << std::setw(width) 
        << std::setfill(static_cast<char_type>('0'));
#if (defined(BOOST_MSVC) && (_MSC_VER < 1300))
      // JDG [7/6/02 VC++ compatibility]
      char_type buff[34];
      ss << _i64toa(static_cast<boost::int64_t>(val), buff, 10);
#else
      ss << val;
#endif
      return ss.str();
    }

  private:
    string_type m_time_duration_format;

  };

  template <class time_type, class CharT, class OutItrT>  
  std::locale::id time_facet<time_type, CharT, OutItrT>::id;

  template <class time_type, class CharT, class OutItrT>  
  const typename time_facet<time_type, CharT, OutItrT>::char_type* 
  time_facet<time_type, CharT, OutItrT>::fractional_seconds_format = time_formats<CharT>::fractional_seconds_format;

  template <class time_type, class CharT, class OutItrT>  
  const typename time_facet<time_type, CharT, OutItrT>::char_type* 
  time_facet<time_type, CharT, OutItrT>::fractional_seconds_or_none_format = time_formats<CharT>::fractional_seconds_or_none_format;

  template <class time_type, class CharT, class OutItrT>  
  const typename time_facet<time_type, CharT, OutItrT>::char_type* 
  time_facet<time_type, CharT, OutItrT>::seconds_with_fractional_seconds_format = 
    time_formats<CharT>::seconds_with_fractional_seconds_format;


  template <class time_type, class CharT, class OutItrT>  
  const typename time_facet<time_type, CharT, OutItrT>::char_type*
  time_facet<time_type, CharT, OutItrT>::zone_name_format =  time_formats<CharT>::zone_name_format;

  template <class time_type, class CharT, class OutItrT>  
  const typename time_facet<time_type, CharT, OutItrT>::char_type*
  time_facet<time_type, CharT, OutItrT>::zone_abbrev_format =  time_formats<CharT>::zone_abbrev_format;

  template <class time_type, class CharT, class OutItrT>  
  const typename time_facet<time_type, CharT, OutItrT>::char_type*
  time_facet<time_type, CharT, OutItrT>::zone_iso_extended_format =time_formats<CharT>::zone_iso_extended_format;

  template <class time_type, class CharT, class OutItrT>  
  const typename time_facet<time_type, CharT, OutItrT>::char_type*
  time_facet<time_type, CharT, OutItrT>::posix_zone_string_format =time_formats<CharT>::posix_zone_string_format;

  template <class time_type, class CharT, class OutItrT>  
  const typename time_facet<time_type, CharT, OutItrT>::char_type*
  time_facet<time_type, CharT, OutItrT>::zone_iso_format =  time_formats<CharT>::zone_iso_format;

  template <class time_type, class CharT, class OutItrT>  
  const typename time_facet<time_type, CharT, OutItrT>::char_type*
  time_facet<time_type, CharT, OutItrT>::seconds_format =  time_formats<CharT>::seconds_format;

  template <class time_type, class CharT, class OutItrT>  
  const typename time_facet<time_type, CharT, OutItrT>::char_type*
  time_facet<time_type, CharT, OutItrT>::hours_format =  time_formats<CharT>::hours_format;

  template <class time_type, class CharT, class OutItrT>  
  const typename time_facet<time_type, CharT, OutItrT>::char_type*
  time_facet<time_type, CharT, OutItrT>::unrestricted_hours_format =  time_formats<CharT>::unrestricted_hours_format;

  template <class time_type, class CharT, class OutItrT>  
  const typename time_facet<time_type, CharT, OutItrT>::char_type*
  time_facet<time_type, CharT, OutItrT>::standard_format =  time_formats<CharT>::standard_format;

  template <class time_type, class CharT, class OutItrT>  
  const typename time_facet<time_type, CharT, OutItrT>::char_type*
  time_facet<time_type, CharT, OutItrT>::duration_seperator =  time_formats<CharT>::duration_seperator;

  template <class time_type, class CharT, class OutItrT>  
  const typename time_facet<time_type, CharT, OutItrT>::char_type*
  time_facet<time_type, CharT, OutItrT>::negative_sign =  time_formats<CharT>::negative_sign;

  template <class time_type, class CharT, class OutItrT>  
  const typename time_facet<time_type, CharT, OutItrT>::char_type*
  time_facet<time_type, CharT, OutItrT>::positive_sign =  time_formats<CharT>::positive_sign;

  template <class time_type, class CharT, class OutItrT>  
  const typename time_facet<time_type, CharT, OutItrT>::char_type*
  time_facet<time_type, CharT, OutItrT>::duration_sign_negative_only =  time_formats<CharT>::duration_sign_negative_only;

  template <class time_type, class CharT, class OutItrT>  
  const typename time_facet<time_type, CharT, OutItrT>::char_type*
  time_facet<time_type, CharT, OutItrT>::duration_sign_always =  time_formats<CharT>::duration_sign_always;

  template <class time_type, class CharT, class OutItrT>  
  const typename time_facet<time_type,CharT, OutItrT>::char_type*
  time_facet<time_type,CharT, OutItrT>::iso_time_format_specifier = time_formats<CharT>::iso_time_format_specifier;

  template <class time_type, class CharT, class OutItrT>  
  const typename time_facet<time_type, CharT, OutItrT>::char_type*
  time_facet<time_type, CharT, OutItrT>::iso_time_format_extended_specifier = time_formats<CharT>::iso_time_format_extended_specifier;

  template <class time_type, class CharT, class OutItrT>  
  const typename time_facet<time_type, CharT, OutItrT>::char_type*
  time_facet<time_type, CharT, OutItrT>::default_time_format = 
    time_formats<CharT>::default_time_format;

  template <class time_type, class CharT, class OutItrT>  
  const typename time_facet<time_type, CharT, OutItrT>::char_type* 
  time_facet<time_type, CharT, OutItrT>::default_time_duration_format = 
    time_formats<CharT>::default_time_duration_format;


  //! Facet for format-based input.  
  /*!
   */
  template <class time_type,
            class CharT, 
            class InItrT = std::istreambuf_iterator<CharT, std::char_traits<CharT> > >
  class time_input_facet : 
    public boost::date_time::date_input_facet<typename time_type::date_type , CharT, InItrT> {
    public:
      typedef typename time_type::date_type date_type;
      typedef typename time_type::time_duration_type time_duration_type;
      typedef typename time_duration_type::fractional_seconds_type fracional_seconds_type;
      typedef boost::date_time::period<time_type,time_duration_type> period_type;
      typedef boost::date_time::date_input_facet<typename time_type::date_type, CharT, InItrT> base_type;
      typedef typename base_type::duration_type date_duration_type;
      typedef typename base_type::year_type year_type;
      typedef typename base_type::month_type month_type;
      typedef typename base_type::day_type day_type;
      typedef typename base_type::string_type string_type;
      typedef typename string_type::const_iterator const_itr;
      typedef typename base_type::char_type   char_type;
      typedef typename base_type::format_date_parser_type format_date_parser_type;
      typedef typename base_type::period_parser_type period_parser_type;
      typedef typename base_type::special_values_parser_type special_values_parser_type;
      typedef typename base_type::date_gen_parser_type date_gen_parser_type;
      typedef typename base_type::special_values_parser_type::match_results match_results;

      static const char_type* fractional_seconds_format;                // f
      static const char_type* fractional_seconds_or_none_format;        // F
      static const char_type* seconds_with_fractional_seconds_format;   // s
      static const char_type* seconds_format;                           // S
      static const char_type* standard_format;                          // x X
      static const char_type* zone_abbrev_format;                       // z
      static const char_type* zone_name_format;                         // Z
      static const char_type* zone_iso_format;                          // q
      static const char_type* zone_iso_extended_format;                 // Q
      static const char_type* duration_seperator;
      static const char_type* iso_time_format_specifier;
      static const char_type* iso_time_format_extended_specifier;
      static const char_type* default_time_input_format; 
      static const char_type* default_time_duration_format;
      static std::locale::id id;

      //! Constructor that takes a format string for a ptime
      explicit time_input_facet(const string_type& format, ::size_t a_ref = 0) 
        : base_type(format, a_ref), 
          m_time_duration_format(default_time_duration_format)
      { }

      explicit time_input_facet(const string_type& format,
                                const format_date_parser_type& date_parser,
                                const special_values_parser_type& sv_parser,
                                const period_parser_type& per_parser,
                                const date_gen_parser_type& date_gen_parser,
                                ::size_t a_ref = 0)
        : base_type(format,
                    date_parser,
                    sv_parser,
                    per_parser,
                    date_gen_parser,
                    a_ref), 
          m_time_duration_format(default_time_duration_format)
      {}

      //! sets default formats for ptime, local_date_time, and time_duration
      explicit time_input_facet(::size_t a_ref = 0) 
        : base_type(default_time_input_format, a_ref), 
          m_time_duration_format(default_time_duration_format)
      { }
      
      //! Set the format for time_duration
      void time_duration_format(const char_type* const format) {
        m_time_duration_format = format;
      }
      virtual void set_iso_format()
      {
        this->m_format = iso_time_format_specifier;
      }
      virtual void set_iso_extended_format()
      {
        this->m_format = iso_time_format_extended_specifier;
      }
      
      InItrT get(InItrT& sitr,
                 InItrT& stream_end,
                 std::ios_base& a_ios,
                 period_type& p) const
      {
        p = this->m_period_parser.get_period(sitr, 
                                             stream_end, 
                                             a_ios, 
                                             p, 
                                             time_duration_type::unit(), 
                                             *this);
        return sitr;
      }
      
      //default ptime format is YYYY-Mon-DD HH:MM:SS[.fff...][ zzz]
      //default time_duration format is %H:%M:%S%F HH:MM:SS[.fff...]

      InItrT get(InItrT& sitr, 
                 InItrT& stream_end, 
                 std::ios_base& a_ios, 
                 time_duration_type& td) const
      {
        // skip leading whitespace
        while((sitr != stream_end) && std::isspace(*sitr)) { ++sitr; }
        
        bool use_current_char = false;
        
        // num_get will consume the +/-, we may need a copy if special_value
        char_type c = '\0';
        if((sitr != stream_end) && (*sitr == '-' || *sitr == '+')) {
          c = *sitr;
        }
        
        typedef typename time_duration_type::hour_type hour_type;
        typedef typename time_duration_type::min_type min_type;
        typedef typename time_duration_type::sec_type sec_type;

        hour_type hour = 0; 
        min_type min = 0; 
        sec_type sec = 0; 
        typename time_duration_type::fractional_seconds_type frac(0);

        typedef std::num_get<CharT, InItrT> num_get;
        if(!std::has_facet<num_get>(a_ios.getloc())) {
          num_get* ng = new num_get();
          std::locale loc = std::locale(a_ios.getloc(), ng);
          a_ios.imbue(loc);
        }
       
        const_itr itr(m_time_duration_format.begin());
        while (itr != m_time_duration_format.end() && (sitr != stream_end)) {
          if (*itr == '%') {
            ++itr;
            if (*itr != '%') {
              switch(*itr) {
              case 'O': 
                {
                  // A period may span more than 24 hours. In that case the format
                  // string should be composed with the unrestricted hours specifier.
                  hour = var_string_to_int<hour_type, CharT>(sitr, stream_end, 
                                      std::numeric_limits<hour_type>::digits10 + 1);
                  if(hour == -1){
                     return check_special_value(sitr, stream_end, td, c);
                  }
                  break;
                }
              case 'H': 
                {
                  match_results mr;
                  hour = fixed_string_to_int<hour_type, CharT>(sitr, stream_end, mr, 2);
                  if(hour == -1){
                     return check_special_value(sitr, stream_end, td, c);
                  }
                  break;
                }
              case 'M': 
                {
                  match_results mr;
                  min = fixed_string_to_int<min_type, CharT>(sitr, stream_end, mr, 2);
                  if(min == -1){
                     return check_special_value(sitr, stream_end, td, c);
                  }
                  break;
                }
              case 's':
              case 'S': 
                {
                  match_results mr;
                  sec = fixed_string_to_int<sec_type, CharT>(sitr, stream_end, mr, 2);
                  if(sec == -1){
                     return check_special_value(sitr, stream_end, td, c);
                  }
                  if (*itr == 'S')
                    break;
                  // %s is the same as %S%f so we drop through into %f
                }
              case 'f':
                {
                  // check for decimal, check special_values if missing
                  if(*sitr == '.') {
                    ++sitr;
                    parse_frac_type(sitr, stream_end, frac);
                    // sitr will point to next expected char after this parsing 
                    // is complete so no need to advance it
                    use_current_char = true;
                  }
                  else {
                    return check_special_value(sitr, stream_end, td, c);
                  }
                  break;
                }
              case 'F': 
                {
                  // check for decimal, skip if missing
                  if(*sitr == '.') {
                    ++sitr;
                    parse_frac_type(sitr, stream_end, frac);
                    // sitr will point to next expected char after this parsing 
                    // is complete so no need to advance it
                    use_current_char = true;
                  }
                  else {
                    // nothing was parsed so we don't want to advance sitr
                    use_current_char = true;
                  }
                  break;
                }
              default:
                {} // ignore what we don't understand?
              }// switch
            }
            else { // itr == '%', second consecutive
              ++sitr;
            }
        
            ++itr; //advance past format specifier
          }
          else {  //skip past chars in format and in buffer
            ++itr;
            // set use_current_char when sitr is already 
            // pointing at the next character to process
            if (use_current_char) {
              use_current_char = false;
            }
            else {
              ++sitr;
            }
          }
        }

        td = time_duration_type(hour, min, sec, frac);
        return sitr;
      }
    

      //! Parses a time object from the input stream
      InItrT get(InItrT& sitr, 
                 InItrT& stream_end, 
                 std::ios_base& a_ios, 
                 time_type& t) const
      {
        string_type tz_str;
        return get(sitr, stream_end, a_ios, t, tz_str, false);
      }
      //! Expects a time_zone in the input stream
      InItrT get_local_time(InItrT& sitr, 
                            InItrT& stream_end, 
                            std::ios_base& a_ios, 
                            time_type& t,
                            string_type& tz_str) const
      {
        return get(sitr, stream_end, a_ios, t, tz_str, true);
      }

    protected:

      InItrT get(InItrT& sitr, 
                 InItrT& stream_end, 
                 std::ios_base& a_ios, 
                 time_type& t,
                 string_type& tz_str,
                 bool time_is_local) const
      {
        // skip leading whitespace
        while((sitr != stream_end) && std::isspace(*sitr)) { ++sitr; }
        
        bool use_current_char = false;
        bool use_current_format_char = false; // used whith two character flags
        
        // num_get will consume the +/-, we may need a copy if special_value
        char_type c = '\0';
        if((sitr != stream_end) && (*sitr == '-' || *sitr == '+')) {
          c = *sitr;
        }

        typedef typename time_duration_type::hour_type hour_type;
        typedef typename time_duration_type::min_type min_type;
        typedef typename time_duration_type::sec_type sec_type;

        // time elements
        hour_type hour = 0; 
        min_type min = 0; 
        sec_type sec = 0; 
        typename time_duration_type::fractional_seconds_type frac(0);
        // date elements
        short day_of_year(0);
        /* Initialized the following to their minimum values. These intermediate 
         * objects are used so we get specific exceptions when part of the input 
         * is unparsable. 
         * Ex: "205-Jan-15" will throw a bad_year, "2005-Jsn-15"- bad_month, etc.*/
        year_type t_year(1400);
        month_type t_month(1);
        day_type t_day(1);
        
        typedef std::num_get<CharT, InItrT> num_get;
        if(!std::has_facet<num_get>(a_ios.getloc())) {
          num_get* ng = new num_get();
          std::locale loc = std::locale(a_ios.getloc(), ng);
          a_ios.imbue(loc);
        }
       
        const_itr itr(this->m_format.begin());
        while (itr != this->m_format.end() && (sitr != stream_end)) {
          if (*itr == '%') {
            ++itr;
            if (*itr != '%') {
              // the cases are grouped by date & time flags - not alphabetical order
              switch(*itr) {
                // date flags
                case 'Y':
                case 'y':
                  {
                    char_type cs[3] = { '%', *itr };
                    string_type s(cs);
                    match_results mr;
                    try {
                      t_year = this->m_parser.parse_year(sitr, stream_end, s, mr);
                    }
                    catch(std::out_of_range&) { // base class for bad_year exception
                      if(this->m_sv_parser.match(sitr, stream_end, mr)) {
                        t = time_type(static_cast<special_values>(mr.current_match));
                        return sitr;
                      }
                      else {
                        throw; // rethrow bad_year
                      }
                    }
                    break;
                  }
                case 'B':
                case 'b':
                case 'm':
                  {
                    char_type cs[3] = { '%', *itr };
                    string_type s(cs);
                    match_results mr;
                    try {
                      t_month = this->m_parser.parse_month(sitr, stream_end, s, mr);
                    }
                    catch(std::out_of_range&) { // base class for bad_month exception
                      if(this->m_sv_parser.match(sitr, stream_end, mr)) {
                        t = time_type(static_cast<special_values>(mr.current_match));
                        return sitr;
                      }
                      else {
                        throw; // rethrow bad_month
                      }
                    }
                    // did m_parser already advance sitr to next char?
                    if(mr.has_remaining()) {
                      use_current_char = true;
                    }
                    break;
                  }
                case 'a':
                case 'A':
                case 'w':
                  {
                    // weekday is not used in construction but we need to get it out of the stream
                    char_type cs[3] = { '%', *itr };
                    string_type s(cs);
                    match_results mr;
                    typename date_type::day_of_week_type wd(0);
                    try {
                      wd = this->m_parser.parse_weekday(sitr, stream_end, s, mr);
                    }
                    catch(std::out_of_range&) { // base class for bad_weekday exception
                      if(this->m_sv_parser.match(sitr, stream_end, mr)) {
                        t = time_type(static_cast<special_values>(mr.current_match));
                        return sitr;
                      }
                      else {
                        throw; // rethrow bad_weekday
                      }
                    }
                    // did m_parser already advance sitr to next char?
                    if(mr.has_remaining()) {
                      use_current_char = true;
                    }
                    break;
                  }
                case 'j':
                  {
                    // code that gets julian day (from format_date_parser)
                    match_results mr;
                    day_of_year = fixed_string_to_int<unsigned short, CharT>(sitr, stream_end, mr, 3);
                    if(day_of_year == -1) {
                      if(this->m_sv_parser.match(sitr, stream_end, mr)) {
                        t = time_type(static_cast<special_values>(mr.current_match));
                        return sitr;
                      }
                    }
                    // these next two lines are so we get an exception with bad input
                    typedef typename time_type::date_type::day_of_year_type day_of_year_type;
                    day_of_year_type t_day_of_year(day_of_year);
                    break;
                  }
                case 'd':
                  {
                    try {
                      t_day = this->m_parser.parse_day_of_month(sitr, stream_end);
                    }
                    catch(std::out_of_range&) { // base class for exception bad_day_of_month
                      match_results mr;
                      if(this->m_sv_parser.match(sitr, stream_end, mr)) {
                        t = time_type(static_cast<special_values>(mr.current_match));
                        return sitr;
                      }
                      else {
                        throw; // rethrow bad_day_of_month
                      }
                    }
                    break;
                  }
                // time flags
                case 'H': 
                  {
                    match_results mr;
                    hour = fixed_string_to_int<hour_type, CharT>(sitr, stream_end, mr, 2);
                    if(hour == -1){
                       return check_special_value(sitr, stream_end, t, c);
                    }
                    break;
                  }
                case 'M': 
                  {
                    match_results mr;
                    min = fixed_string_to_int<min_type, CharT>(sitr, stream_end, mr, 2);
                    if(min == -1){
                       return check_special_value(sitr, stream_end, t, c);
                    }
                    break;
                  }
                case 's':
                case 'S': 
                  {
                    match_results mr;
                    sec = fixed_string_to_int<sec_type, CharT>(sitr, stream_end, mr, 2);
                    if(sec == -1){
                       return check_special_value(sitr, stream_end, t, c);
                    }
                    if (*itr == 'S')
                      break;
                    // %s is the same as %S%f so we drop through into %f
                  }
                case 'f':
                  {
                    // check for decimal, check SV if missing
                    if(*sitr == '.') {
                      ++sitr;
                      parse_frac_type(sitr, stream_end, frac);
                      // sitr will point to next expected char after this parsing 
                      // is complete so no need to advance it
                      use_current_char = true;
                    }
                    else {
                      return check_special_value(sitr, stream_end, t, c);
                    }
                    break;
                  }
                case 'F': 
                  {
                    // check for decimal, skip if missing
                    if(*sitr == '.') {
                      ++sitr;
                      parse_frac_type(sitr, stream_end, frac);
                      // sitr will point to next expected char after this parsing 
                      // is complete so no need to advance it
                      use_current_char = true;
                    }
                    else {
                      // nothing was parsed so we don't want to advance sitr
                      use_current_char = true;
                    }
                    break;
                  }
                  // time_zone flags
                //case 'q':
                //case 'Q':
                //case 'z':
                case 'Z':
                  {
                    if(time_is_local) { // skip if 't' is a ptime
                      ++itr;
                      if(*itr == 'P') {
                        // skip leading whitespace
                        while((sitr != stream_end) && std::isspace(*sitr)) { ++sitr; }
                        // parse zone
                        while((sitr != stream_end) && (!std::isspace(*sitr))) {
                          tz_str += *sitr;
                          ++sitr;
                        }
                      }
                      else {
                        use_current_format_char = true;
                      }
                    
                    }
                    else {
                      // nothing was parsed so we don't want to advance sitr
                      use_current_char = true;
                    }
                   
                    break;
                  }
                default:
                {} // ignore what we don't understand?
              }// switch
            } 
            else { // itr == '%', second consecutive
              ++sitr;
            }
       
            if(use_current_format_char) {
              use_current_format_char = false;
            }
            else {
              ++itr; //advance past format specifier
            }
             
          }
          else {  //skip past chars in format and in buffer
            ++itr;
            // set use_current_char when sitr is already 
            // pointing at the next character to process
            if (use_current_char) {
              use_current_char = false;
            }
            else {
              ++sitr;
            }
          }
        }
        
        date_type d(not_a_date_time);
        if (day_of_year > 0) {
          d = date_type(static_cast<unsigned short>(t_year-1),12,31) + date_duration_type(day_of_year);
        }
        else {
          d = date_type(t_year, t_month, t_day);
        }

        time_duration_type td(hour, min, sec, frac);
        t = time_type(d, td);
        return sitr;
      }

      //! Helper function to check for special_value
      /*! First character may have been consumed during original parse 
       * attempt. Parameter 'c' should be a copy of that character. 
       * Throws ios_base::failure if parse fails. */
      template<class temporal_type>
      inline
      InItrT check_special_value(InItrT& sitr,InItrT& stream_end, temporal_type& tt, char_type c='\0') const
      {
        match_results mr;
        if((c == '-' || c == '+') && (*sitr != c)) { // was the first character consumed?
          mr.cache += c;
        }
        this->m_sv_parser.match(sitr, stream_end, mr);
        if(mr.current_match == match_results::PARSE_ERROR) {
          std::string tmp = convert_string_type<char_type, char>(mr.cache);
          boost::throw_exception(std::ios_base::failure("Parse failed. No match found for '" + tmp + "'"));
          BOOST_DATE_TIME_UNREACHABLE_EXPRESSION(return sitr); // should never reach
        }
        tt = temporal_type(static_cast<special_values>(mr.current_match)); 
        return sitr;
      }

      //! Helper function for parsing a fractional second type from the stream
      void parse_frac_type(InItrT& sitr, 
                           InItrT& stream_end, 
                           fracional_seconds_type& frac) const
      {
        string_type cache;
        while((sitr != stream_end) && std::isdigit(*sitr)) {
          cache += *sitr;
          ++sitr;
        }
        if(cache.size() > 0) {
          unsigned short precision = time_duration_type::num_fractional_digits();
          // input may be only the first few decimal places
          if(cache.size() < precision) {
            frac = lexical_cast<fracional_seconds_type>(cache);
            frac = decimal_adjust(frac, static_cast<unsigned short>(precision - cache.size()));
          }
          else {
            // if input has too many decimal places, drop excess digits
            frac = lexical_cast<fracional_seconds_type>(cache.substr(0, precision));
          }
        }
      }
      
    private:
      string_type m_time_duration_format;

      //! Helper function to adjust trailing zeros when parsing fractional digits
      template<class int_type>
      inline
      int_type decimal_adjust(int_type val, const unsigned short places) const
      {
        unsigned long factor = 1;
        for(int i = 0; i < places; ++i){
          factor *= 10; // shift decimal to the right
        }
        return val * factor;
      }

  };

template <class time_type, class CharT, class InItrT>
  std::locale::id time_input_facet<time_type, CharT, InItrT>::id;

template <class time_type, class CharT, class InItrT>  
  const typename time_input_facet<time_type, CharT, InItrT>::char_type* 
  time_input_facet<time_type, CharT, InItrT>::fractional_seconds_format = time_formats<CharT>::fractional_seconds_format;

  template <class time_type, class CharT, class InItrT>  
  const typename time_input_facet<time_type, CharT, InItrT>::char_type* 
  time_input_facet<time_type, CharT, InItrT>::fractional_seconds_or_none_format = time_formats<CharT>::fractional_seconds_or_none_format;

  template <class time_type, class CharT, class InItrT>  
  const typename time_input_facet<time_type, CharT, InItrT>::char_type* 
  time_input_facet<time_type, CharT, InItrT>::seconds_with_fractional_seconds_format = time_formats<CharT>::seconds_with_fractional_seconds_format;

  template <class time_type, class CharT, class InItrT>  
  const typename time_input_facet<time_type, CharT, InItrT>::char_type* 
  time_input_facet<time_type, CharT, InItrT>::seconds_format = time_formats<CharT>::seconds_format;

  template <class time_type, class CharT, class InItrT>  
  const typename time_input_facet<time_type, CharT, InItrT>::char_type* 
  time_input_facet<time_type, CharT, InItrT>::standard_format = time_formats<CharT>::standard_format;

  template <class time_type, class CharT, class InItrT>  
  const typename time_input_facet<time_type, CharT, InItrT>::char_type* 
  time_input_facet<time_type, CharT, InItrT>::zone_abbrev_format = time_formats<CharT>::zone_abbrev_format;

  template <class time_type, class CharT, class InItrT>  
  const typename time_input_facet<time_type, CharT, InItrT>::char_type* 
  time_input_facet<time_type, CharT, InItrT>::zone_name_format = time_formats<CharT>::zone_name_format;

  template <class time_type, class CharT, class InItrT>  
  const typename time_input_facet<time_type, CharT, InItrT>::char_type* 
  time_input_facet<time_type, CharT, InItrT>::zone_iso_format = time_formats<CharT>::zone_iso_format;

  template <class time_type, class CharT, class InItrT>  
  const typename time_input_facet<time_type, CharT, InItrT>::char_type* 
  time_input_facet<time_type, CharT, InItrT>::zone_iso_extended_format = time_formats<CharT>::zone_iso_extended_format;

  template <class time_type, class CharT, class InItrT>  
  const typename time_input_facet<time_type, CharT, InItrT>::char_type* 
  time_input_facet<time_type, CharT, InItrT>::duration_seperator = time_formats<CharT>::duration_seperator;

  template <class time_type, class CharT, class InItrT>  
  const typename time_input_facet<time_type, CharT, InItrT>::char_type* 
  time_input_facet<time_type, CharT, InItrT>::iso_time_format_specifier = time_formats<CharT>::iso_time_format_specifier;

  template <class time_type, class CharT, class InItrT>  
  const typename time_input_facet<time_type, CharT, InItrT>::char_type* 
  time_input_facet<time_type, CharT, InItrT>::iso_time_format_extended_specifier = time_formats<CharT>::iso_time_format_extended_specifier;

  template <class time_type, class CharT, class InItrT>  
  const typename time_input_facet<time_type, CharT, InItrT>::char_type* 
  time_input_facet<time_type, CharT, InItrT>::default_time_input_format = time_formats<CharT>::default_time_input_format;

  template <class time_type, class CharT, class InItrT>  
  const typename time_input_facet<time_type, CharT, InItrT>::char_type* 
  time_input_facet<time_type, CharT, InItrT>::default_time_duration_format = time_formats<CharT>::default_time_duration_format;


} } // namespaces


#endif

