/*
 * Copyright (c) 2003-2009, John Wiegley.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * - Neither the name of New Artisans LLC nor the names of its
 *   contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <system.hh>

#include "times.h"

namespace ledger {

optional<datetime_t> epoch;

date_time::weekdays start_of_week = gregorian::Sunday;

//#define USE_BOOST_FACETS 1

namespace {
  template <typename T, typename InputFacetType, typename OutputFacetType>
  class temporal_io_t : public noncopyable
  {
    const char *       fmt_str;
#if defined(USE_BOOST_FACETS)
    std::istringstream input_stream;
    std::ostringstream output_stream;
    InputFacetType *   input_facet;
    OutputFacetType *  output_facet;
    std::string        temp_string;
#endif // USE_BOOST_FACETS

  public:
    bool	       has_year;
    bool	       input;

    temporal_io_t(const char * _fmt_str, bool _input)
      : fmt_str(_fmt_str), has_year(icontains(fmt_str, "%y")),
	input(_input) {
#if defined(USE_BOOST_FACETS)
      if (input) {
	input_facet  = new InputFacetType(fmt_str);
	input_stream.imbue(std::locale(std::locale::classic(), input_facet));
      } else {
	output_facet = new OutputFacetType(fmt_str);
	output_stream.imbue(std::locale(std::locale::classic(), output_facet));
      }
#endif // USE_BOOST_FACETS
    }

    void set_format(const char * fmt) {
      fmt_str  = fmt;
      has_year = icontains(fmt_str, "%y");

#if defined(USE_BOOST_FACETS)
      if (input)
	input_facet->format(fmt_str);
      else
	output_facet->format(fmt_str);
#endif // USE_BOOST_FACETS
    }

    T parse(const char * str) {
    }

    std::string format(const T& when) {
#if defined(USE_BOOST_FACETS)
      output_stream.str(temp_string);
      output_stream.seekp(std::ios_base::beg);
      output_stream.clear();
      output_stream << when;
      return output_stream.str();
#else // USE_BOOST_FACETS
      std::tm data(to_tm(when));
      char buf[128];
      std::strftime(buf, 127, fmt_str, &data);
      return buf;
#endif // USE_BOOST_FACETS
    }
  };

  template <>
  datetime_t temporal_io_t<datetime_t, posix_time::time_input_facet,
			   posix_time::time_facet>
    ::parse(const char * str)
  {
#if defined(USE_BOOST_FACETS)
      input_stream.seekg(std::ios_base::beg);
      input_stream.clear();
      input_stream.str(str);

      datetime_t when;
      input_stream >> when;
#if defined(DEBUG_ON)
      if (when.is_not_a_date_time())
	DEBUG("times.parse", "Failed to parse date/time '" << str
	      << "' using pattern '" << fmt_str << "'");
#endif

      if (! when.is_not_a_date_time() &&
	  input_stream.good() && ! input_stream.eof() &&
	  input_stream.peek() != EOF) {
	DEBUG("times.parse", "This string has leftovers: '" << str << "'");
	return datetime_t();
      }
      return when;
#else // USE_BOOST_FACETS
    std::tm data;
    std::memset(&data, 0, sizeof(std::tm));
    if (strptime(str, fmt_str, &data))
      return posix_time::ptime_from_tm(data);
    else
      return datetime_t();
#endif // USE_BOOST_FACETS
  }

  template <>
  date_t temporal_io_t<date_t, gregorian::date_input_facet,
		       gregorian::date_facet>
    ::parse(const char * str)
  {
#if defined(USE_BOOST_FACETS)
      input_stream.seekg(std::ios_base::beg);
      input_stream.clear();
      input_stream.str(str);

      date_t when;
      input_stream >> when;
#if defined(DEBUG_ON)
      if (when.is_not_a_date())
	DEBUG("times.parse", "Failed to parse date '" << str
	      << "' using pattern '" << fmt_str << "'");
#endif

      if (! when.is_not_a_date() &&
	  input_stream.good() && ! input_stream.eof() &&
	  input_stream.peek() != EOF) {
	DEBUG("times.parse", "This string has leftovers: '" << str << "'");
	return date_t();
      }
      return when;
#else // USE_BOOST_FACETS
    std::tm data;
    std::memset(&data, 0, sizeof(std::tm));
    data.tm_mday = 1;		// some formats have no day
    if (strptime(str, fmt_str, &data))
      return gregorian::date_from_tm(data);
    else
      return date_t();
#endif // USE_BOOST_FACETS
  }

  typedef temporal_io_t<datetime_t, posix_time::time_input_facet,
			posix_time::time_facet> datetime_io_t;
  typedef temporal_io_t<date_t, gregorian::date_input_facet,
			gregorian::date_facet> date_io_t;

  shared_ptr<datetime_io_t> input_datetime_io;
  shared_ptr<date_io_t>     input_date_io;
  shared_ptr<datetime_io_t> written_datetime_io;
  shared_ptr<date_io_t>     written_date_io;
  shared_ptr<datetime_io_t> printed_datetime_io;
  shared_ptr<date_io_t>     printed_date_io;

  std::vector<shared_ptr<date_io_t> > readers;

  date_t parse_date_mask_routine(const char * date_str, date_io_t& io,
				 optional<date_t::year_type> year,
				 bool& saw_year)
  {
    date_t when;

    if (std::strchr(date_str, '/')) {
      when = io.parse(date_str);
    } else {
      char buf[128];
      VERIFY(std::strlen(date_str) < 127);
      std::strcpy(buf, date_str);

      for (char * p = buf; *p; p++)
	if (*p == '.' || *p == '-')
	  *p = '/';

      when = io.parse(buf);
    }

    if (! when.is_not_a_date()) {
      DEBUG("times.parse", "Parsed date string: " << date_str);
      DEBUG("times.parse", "Parsed result is:   " << when);

      if (! io.has_year) {
	saw_year = false;

	when = date_t(year ? *year : CURRENT_DATE().year(),
		      when.month(), when.day());

	if (when.month() > CURRENT_DATE().month())
	  when -= gregorian::years(1);
      } else {
	saw_year = true;
      }
    }
    return when;
  }

  date_t parse_date_mask(const char * date_str,
			 optional<date_t::year_type> year, bool& saw_year)
  {
    if (input_date_io.get()) {
      date_t when = parse_date_mask_routine(date_str, *input_date_io.get(),
					    year, saw_year);
      if (! when.is_not_a_date())
	return when;
    }

    foreach (shared_ptr<date_io_t>& reader, readers) {
      date_t when = parse_date_mask_routine(date_str, *reader.get(),
					    year, saw_year);
      if (! when.is_not_a_date())
	return when;
    }

    throw_(date_error, _("Invalid date: %1") << date_str);
    return date_t();
  }
}

optional<date_time::weekdays> string_to_day_of_week(const std::string& str)
{
  if (str == _("sun") || str == _("sunday") || str == "0")
    return gregorian::Sunday;
  else if (str == _("mon") || str == _("monday") || str == "1")
    return gregorian::Monday;
  else if (str == _("tue") || str == _("tuesday") || str == "2")
    return gregorian::Tuesday;
  else if (str == _("wed") || str == _("wednesday") || str == "3")
    return gregorian::Wednesday;
  else if (str == _("thu") || str == _("thursday") || str == "4")
    return gregorian::Thursday;
  else if (str == _("fri") || str == _("friday") || str == "5")
    return gregorian::Friday;
  else if (str == _("sat") || str == _("saturday") || str == "6")
    return gregorian::Saturday;
  else
    return none;
}
  
optional<date_time::months_of_year>
string_to_month_of_year(const std::string& str)
{
  if (str == _("jan") || str == _("january") || str == "0")
    return gregorian::Jan;
  else if (str == _("feb") || str == _("february") || str == "1")
    return gregorian::Feb;
  else if (str == _("mar") || str == _("march") || str == "2")
    return gregorian::Mar;
  else if (str == _("apr") || str == _("april") || str == "3")
    return gregorian::Apr;
  else if (str == _("may") || str == _("may") || str == "4")
    return gregorian::May;
  else if (str == _("jun") || str == _("june") || str == "5")
    return gregorian::Jun;
  else if (str == _("jul") || str == _("july") || str == "6")
    return gregorian::Jul;
  else if (str == _("aug") || str == _("august") || str == "7")
    return gregorian::Aug;
  else if (str == _("sep") || str == _("september") || str == "8")
    return gregorian::Sep;
  else if (str == _("oct") || str == _("october") || str == "9")
    return gregorian::Oct;
  else if (str == _("nov") || str == _("november") || str == "10")
    return gregorian::Nov;
  else if (str == _("dec") || str == _("december") || str == "11")
    return gregorian::Dec;
  else
    return none;
}

datetime_t parse_datetime(const char * str, optional<date_t::year_type>)
{
  datetime_t when = input_datetime_io->parse(str);
  if (when.is_not_a_date_time())
    throw_(date_error, _("Invalid date/time: %1") << str);
  return when;
}

date_t parse_date(const char * str, optional<date_t::year_type> current_year)
{
  bool saw_year;
  return parse_date_mask(str, current_year, saw_year);
}

std::ostream& operator<<(std::ostream& out,
			 const date_interval_t::duration_t& duration)
{
  if (duration.quantum == date_interval_t::duration_t::DAYS)
    out << duration.length << " day(s)";
  else if (duration.quantum == date_interval_t::duration_t::WEEKS)
    out << duration.length << " week(s)";
  else if (duration.quantum == date_interval_t::duration_t::MONTHS)
    out << duration.length << " month(s)";
  else {
    assert(duration.quantum == date_interval_t::duration_t::YEARS);
    out << duration.length << " year(s)";
  }
  return out;
}

void date_interval_t::resolve_end()
{
  if (start && ! end_of_duration) {
    end_of_duration = duration->add(*start);
    DEBUG("times.interval",
	  "stabilize: end_of_duration = " << *end_of_duration);
  }

  if (end && *end_of_duration > *end) {
    end_of_duration = end;
    DEBUG("times.interval",
	  "stabilize: end_of_duration reset to end: " << *end_of_duration);
  }

  if (! skip_duration) {
    skip_duration = duration;
    DEBUG("times.interval",
	  "stabilize: skip_duration set to: " << *skip_duration);
  }

  if (start && ! next) {
    next = skip_duration->add(*start);
    DEBUG("times.interval",
	  "stabilize: next set to: " << *next);
  }
}

void date_interval_t::stabilize(const optional<date_t>& date)
{
#if defined(DEBUG_ON)
  if (date)
    DEBUG("times.interval", "stabilize: with date = " << *date);
#endif

  if (date && ! aligned) {
    DEBUG("times.interval", "stabilize: date passed, but not aligned");
    if (duration) {
      DEBUG("times.interval",
	    "stabilize: aligning with a duration: " << *duration);

      // The interval object has not been seeded with a start date yet, so
      // find the nearest period before on on date which fits, if possible.
      //
      // Find an efficient starting point for the upcoming while loop.  We
      // want a date early enough that the range will be correct, but late
      // enough that we don't spend hundreds of thousands of loops skipping
      // through time.
      optional<date_t> initial_start = start;
      optional<date_t> initial_end   = end;

#if defined(DEBUG_ON)
      if (initial_start)
	DEBUG("times.interval",
	      "stabilize: initial_start = " << *initial_start);
      if (initial_end)
	DEBUG("times.interval",
	      "stabilize: initial_end   = " << *initial_end);
#endif

      date_t when = start ? *start : *date;

      if (duration->quantum == duration_t::MONTHS ||
	  duration->quantum == duration_t::YEARS) {
	DEBUG("times.interval", "stabilize: monthly or yearly duration");

	start = date_t(when.year(), gregorian::Jan, 1);
      } else {
	DEBUG("times.interval", "stabilize: daily or weekly duration");

	start = date_t(when - gregorian::days(400));

	if (duration->quantum == duration_t::WEEKS) {
	  // Move it to a Sunday
	  while (start->day_of_week() != start_of_week)
	    *start += gregorian::days(1);
	}
      }

      DEBUG("times.interval",
	    "stabilize: beginning start date = " << *start);

      while (*start < *date) {
	date_interval_t next_interval(*this);
	++next_interval;

	if (next_interval.start && *next_interval.start < *date) {
	  *this = next_interval;
	} else {
	  end_of_duration = none;
	  next		  = none;
	  break;
	}
      }

      DEBUG("times.interval", "stabilize: final start date = " << *start);

      if (initial_start && (! start || *start < *initial_start)) {
	resolve_end();

	start = initial_start;
	DEBUG("times.interval", "stabilize: start reset to initial start");
      }
      if (initial_end && (! end || *end > *initial_end)) {
	end = initial_end;
	DEBUG("times.interval", "stabilize: end reset to initial end");
      }
    }
    aligned = true;
  }

  // If there is no duration, then if we've reached here the date falls
  // between begin and end.
  if (! duration) {
    DEBUG("times.interval", "stabilize: there was no duration given");

    if (! start && ! end)
      throw_(date_error,
	     _("Invalid date interval: neither start, nor end, nor duration"));
  } else {
    resolve_end();
  }
}

bool date_interval_t::find_period(const date_t& date)
{
  stabilize(date);

  if (end && date > *end) {
    DEBUG("times.interval",
	  "false: date [" << date << "] > end [" << *end << "]");
    return false;
  }

  if (! start) {
    throw_(std::runtime_error, _("Date interval is improperly initialized"));
  }
  else if (date < *start) {
    DEBUG("times.interval",
	  "false: date [" << date << "] < start [" << *start << "]");
    return false;
  }

  if (end_of_duration) {
    if (date < *end_of_duration) {
      DEBUG("times.interval",
	    "true: date [" << date << "] < end_of_duration ["
	    << *end_of_duration << "]");
      return true;
    }
  } else {
    DEBUG("times.interval", "false: there is no end_of_duration");
    return false;
  }

  // If we've reached here, it means the date does not fall into the current
  // interval, so we must seek another interval that does match -- unless we
  // pass by date in so doing, which means we shouldn't alter the current
  // period of the interval at all.

  date_t scan        = *start;
  date_t end_of_scan = *end_of_duration;

  DEBUG("times.interval", "date        = " << date);
  DEBUG("times.interval", "scan        = " << scan);
  DEBUG("times.interval", "end_of_scan = " << end_of_scan);

  while (date >= scan && (! end || scan < *end)) {
    if (date < end_of_scan) {
      start	      = scan;
      end_of_duration = end_of_scan;
      next	      = none;

      DEBUG("times.interval", "true: start	     = " << *start);
      DEBUG("times.interval", "true: end_of_duration = " << *end_of_duration);

      return true;
    }

    scan	= skip_duration->add(scan);
    end_of_scan = duration->add(scan);
  }

  return false;
}

date_interval_t& date_interval_t::operator++()
{
  if (! start)
    throw_(date_error, _("Cannot increment an unstarted date interval"));

  stabilize();

  if (! skip_duration || ! duration)
    throw_(date_error,
	   _("Cannot increment a date interval without a duration"));

  assert(next);

  if (end && *next >= *end) {
    start = none;
  } else {
    start = *next;

    end_of_duration = duration->add(*start);
  }

  next = none;

  resolve_end();

  return *this;
}

namespace {
  void parse_inclusion_specifier(const string& word,
				 date_t *      begin,
				 date_t *      end)
  {
    bool   saw_year = true;
    date_t when	    = parse_date_mask(word.c_str(), none, saw_year);

    if (when.is_not_a_date())
      throw_(date_error, _("Could not parse date mask: %1") << word);

    if (begin) {
      *begin = when;

      if (end) {
	if (saw_year)
	  *end = *begin + gregorian::years(1);
	else
	  *end = *begin + gregorian::months(1);
      }
    }
    else if (end) {
      *end = when;
    }
  }

  inline void read_lower_word(std::istream& in, string& word) {
    in >> word;
    for (string::size_type i = 0, l = word.length(); i < l; i++)
      word[i] = static_cast<char>(std::tolower(word[i]));
  }

  void parse_date_words(std::istream&	 in,
			string&		 word,
			date_interval_t& interval,
			bool             look_for_start = true,
			bool             look_for_end   = true)
  {
    string type;

    if (word == _("this") || word == _("last") || word == _("next")) {
      type = word;
      if (! in.eof())
	read_lower_word(in, word);
      else
	word = _("month");
    } else {
      type = _("this");
    }

    date_t start = CURRENT_DATE();
    date_t end;
    bool   parse_specifier = false;

    optional<date_interval_t::duration_t> duration;

    assert(look_for_start || look_for_end);

    if (word == _("year")) {
      duration = date_interval_t::duration_t(date_interval_t::duration_t::YEARS, 1);
      start    = gregorian::date(start.year(), 1, 1);
    }
    else if (word == _("month")) {
      duration = date_interval_t::duration_t(date_interval_t::duration_t::MONTHS, 1);
      start    = gregorian::date(start.year(), start.month(), 1);
    }
    else if (word == _("today") || word == _("day")) {
      duration = date_interval_t::duration_t(date_interval_t::duration_t::DAYS, 1);
    }
    else {
      parse_specifier = true;
    }

    if (parse_specifier)
      parse_inclusion_specifier(word, &start, &end);
    else
      end = duration->add(start);

    if (type == _("last") && duration) {
      start = duration->subtract(start);
      end   = duration->subtract(end);
    }
    else if (type == _("next") && duration) {
      start = duration->add(start);
      end   = duration->add(end);
    }

    if (look_for_start && is_valid(start)) interval.start = start;
    if (look_for_end   && is_valid(end))   interval.end   = end;
  }
}

void date_interval_t::parse(std::istream& in)
{
  string word;

  optional<date_time::months_of_year> mon;
  optional<date_time::weekdays>       wday;
  optional<date_t::year_type>         year;

  while (! in.eof()) {
    read_lower_word(in, word);
    if (word == _("every")) {
      read_lower_word(in, word);
      if (std::isdigit(word[0])) {
	int quantity = lexical_cast<int>(word);
	read_lower_word(in, word);
	if (word == _("days"))
	  duration = duration_t(duration_t::DAYS, quantity);
	else if (word == _("weeks"))
	  duration = duration_t(duration_t::WEEKS, quantity);
	else if (word == _("months"))
	  duration = duration_t(duration_t::MONTHS, quantity);
	else if (word == _("quarters"))
	  duration = duration_t(duration_t::MONTHS, 3 * quantity);
	else if (word == _("years"))
	  duration = duration_t(duration_t::YEARS, quantity);
      }
      else if (word == _("day"))
	duration = duration_t(duration_t::DAYS, 1);
      else if (word == _("week"))
	duration = duration_t(duration_t::WEEKS, 1);
      else if (word == _("month"))
	duration = duration_t(duration_t::MONTHS, 1);
      else if (word == _("quarter"))
	duration = duration_t(duration_t::MONTHS, 3);
      else if (word == _("year"))
	duration = duration_t(duration_t::YEARS, 1);
    }
    else if (word == _("daily"))
      duration = duration_t(duration_t::DAYS, 1);
    else if (word == _("weekly"))
      duration = duration_t(duration_t::WEEKS, 1);
    else if (word == _("biweekly"))
      duration = duration_t(duration_t::WEEKS, 2);
    else if (word == _("monthly"))
      duration = duration_t(duration_t::MONTHS, 1);
    else if (word == _("bimonthly"))
      duration = duration_t(duration_t::MONTHS, 2);
    else if (word == _("quarterly"))
      duration = duration_t(duration_t::MONTHS, 3);
    else if (word == _("yearly"))
      duration = duration_t(duration_t::YEARS, 1);
    else if (word == _("this") || word == _("last") || word == _("next") ||
	     word == _("today")) {
      parse_date_words(in, word, *this);
    }
    else if (word == _("in")) {
      read_lower_word(in, word);
      parse_date_words(in, word, *this);
    }
    else if (word == _("from") || word == _("since")) {
      read_lower_word(in, word);
      parse_date_words(in, word, *this, true, false);
    }
    else if (word == _("to") || word == _("until")) {
      read_lower_word(in, word);
      parse_date_words(in, word, *this, false, true);
    }
    else if (optional<date_time::months_of_year>
	     m = string_to_month_of_year(word)) {
      mon = m;
    }
    else if (optional<date_time::weekdays>
	     d = string_to_day_of_week(word)) {
      wday = d;
    }
    else if (all(word, is_digit())) {
      year = lexical_cast<unsigned short>(word);
    }
    else {
      // otherwise, it should be an explicit date
      date_t b, e;
      parse_inclusion_specifier(word, &b, &e);
      start = b;
      end   = e;
    }
  }

  if (year || mon || wday) {
    if (! start)
      start = CURRENT_DATE();

    if (wday) {
      while (start->day_of_week() != *wday)
	*start = duration_t(duration_t::DAYS, 1).subtract(*start);

      if (! end)
	end = duration_t(duration_t::DAYS, 1).add(*start);
    } else {
      bool overwrite_end = false;

      if (year) {
	start = date_t(*year, 1, 1);
	if (! end) {
	  end = duration_t(duration_t::YEARS, 1).add(*start);
	  overwrite_end = true;
	}
      }

      if (mon) {
	start = date_t(start->year(), *mon, 1);
	if (! end || overwrite_end)
	  end = duration_t(duration_t::MONTHS, 1).add(*start);
      }
    }
  }
}

namespace {
  typedef std::map<std::string, datetime_io_t *> datetime_io_map;
  typedef std::map<std::string, date_io_t *>     date_io_map;

  datetime_io_map temp_datetime_io;
  date_io_map	  temp_date_io;
}

std::string format_datetime(const datetime_t&		  when,
			    const format_type_t		  format_type,
			    const optional<const char *>& format)
{
  if (format_type == FMT_WRITTEN) {
    return written_datetime_io->format(when);
  }
  else if (format_type == FMT_CUSTOM || format) {
    datetime_io_map::iterator i = temp_datetime_io.find(*format);
    if (i != temp_datetime_io.end()) {
      return (*i).second->format(when);
    } else {
      datetime_io_t * formatter = new datetime_io_t(*format, false);
      temp_datetime_io.insert(datetime_io_map::value_type(*format, formatter));
      return formatter->format(when);
    }
  }
  else if (format_type == FMT_PRINTED) {
    return printed_datetime_io->format(when);
  }
  else {
    assert(0);
    return "";
  }
}

std::string format_date(const date_t&		      when,
			const format_type_t	      format_type,
			const optional<const char *>& format)
{
  if (format_type == FMT_WRITTEN) {
    return written_date_io->format(when);
  }
  else if (format_type == FMT_CUSTOM || format) {
    date_io_map::iterator i = temp_date_io.find(*format);
    if (i != temp_date_io.end()) {
      return (*i).second->format(when);
    } else {
      date_io_t * formatter = new date_io_t(*format, false);
      temp_date_io.insert(date_io_map::value_type(*format, formatter));
      return formatter->format(when);
    }
  }
  else if (format_type == FMT_PRINTED) {
    return printed_date_io->format(when);
  }
  else {
    assert(0);
    return "";
  }
}

namespace {
  bool is_initialized = false;
}

void set_datetime_format(const char * format)
{
  printed_datetime_io->set_format(format);
}

void set_date_format(const char * format)
{
  printed_date_io->set_format(format);
}

void set_input_date_format(const char * format)
{
  input_date_io.reset(new date_io_t(format, true));
}

void times_initialize()
{
  if (! is_initialized) {
    input_datetime_io.reset(new datetime_io_t("%Y/%m/%d %H:%M:%S", true));

    written_datetime_io.reset(new datetime_io_t("%Y/%m/%d %H:%M:%S", false));
    written_date_io.reset(new date_io_t("%Y/%m/%d", false));

    printed_datetime_io.reset(new datetime_io_t("%y-%b-%d %H:%M:%S", false));
    printed_date_io.reset(new date_io_t("%y-%b-%d", false));

    readers.push_back(shared_ptr<date_io_t>(new date_io_t("%m/%d", true)));
    readers.push_back(shared_ptr<date_io_t>(new date_io_t("%Y/%m/%d", true)));
    readers.push_back(shared_ptr<date_io_t>(new date_io_t("%Y/%m", true)));
    readers.push_back(shared_ptr<date_io_t>(new date_io_t("%y/%m/%d", true)));

    is_initialized = true;
  }
}

void times_shutdown()
{
  if (is_initialized) {
    input_datetime_io.reset();
    input_date_io.reset();
    written_datetime_io.reset();
    written_date_io.reset();
    printed_datetime_io.reset();
    printed_date_io.reset();

    readers.clear();

    foreach (datetime_io_map::value_type& pair, temp_datetime_io)
      checked_delete(pair.second);
    temp_datetime_io.clear();

    foreach (date_io_map::value_type& pair, temp_date_io)
      checked_delete(pair.second);
    temp_date_io.clear();

    is_initialized = false;
  }
}
} // namespace ledger
