Rewrote the way date and time I/O is managed

This commit is contained in:
John Wiegley 2009-10-25 04:37:11 -04:00
parent 588f2ef2f5
commit fc84eeb358
17 changed files with 361 additions and 133 deletions

View file

@ -127,7 +127,7 @@ void annotation_t::print(std::ostream& out, bool keep_base) const
<< '}';
if (date)
out << " [" << format_date(*date, string("%Y/%m/%d")) << ']';
out << " [" << format_date(*date, FMT_WRITTEN) << ']';
if (tag)
out << " (" << *tag << ')';

View file

@ -531,15 +531,14 @@ void subtotal_posts::report_subtotal(const char * spec_fmt,
std::ostringstream out_date;
if (spec_fmt) {
out_date << format_date(*range_finish, string(spec_fmt));
out_date << format_date(*range_finish, FMT_CUSTOM, spec_fmt);
}
else if (date_format) {
string fmt = "- ";
fmt += *date_format;
out_date << format_date(*range_finish, string(fmt));
out_date << "- " << format_date(*range_finish, FMT_CUSTOM,
date_format->c_str());
}
else {
out_date << format_date(*range_finish, std::string("- ") + output_date_format);
out_date << "- " << format_date(*range_finish);
}
xact_temps.push_back(xact_t());

View file

@ -321,11 +321,11 @@ void generate_posts_iterator::generate_note(std::ostream& out)
void generate_posts_iterator::generate_xact(std::ostream& out)
{
out << format_date(next_date, string("%Y/%m/%d"));
out << format_date(next_date, FMT_WRITTEN);
next_date += gregorian::days(six_gen());
if (truth_gen()) {
out << '=';
out << format_date(next_eff_date, string("%Y/%m/%d"));
out << format_date(next_eff_date, FMT_WRITTEN);
next_eff_date += gregorian::days(six_gen());
}
out << ' ';

View file

@ -433,8 +433,10 @@ void global_scope_t::normalize_report_options(const string& verb)
rep.session.commodity_pool->price_db = none;
if (rep.HANDLED(date_format_)) {
output_datetime_format = rep.HANDLER(date_format_).str() + " %H:%M:%S";
output_date_format = rep.HANDLER(date_format_).str();
set_date_format(rep.HANDLER(date_format_).str().c_str());
}
if (rep.HANDLED(datetime_format_)) {
set_datetime_format(rep.HANDLER(datetime_format_).str().c_str());
}
if (rep.HANDLED(start_of_week_)) {
if (optional<date_time::weekdays> weekday =

View file

@ -86,7 +86,7 @@ commodity_quote_from_script(commodity_t& commodity,
std::ios_base::out | std::ios_base::app);
#endif
database << "P "
<< format_datetime(point->when, string("%Y/%m/%d %H:%M:%S"))
<< format_datetime(point->when, FMT_WRITTEN)
<< " " << commodity.symbol()
<< " " << point->price
<< std::endl;

View file

@ -263,9 +263,12 @@ value_t report_t::fn_join(call_scope_t& scope)
value_t report_t::fn_format_date(call_scope_t& scope)
{
interactive_t args(scope, "ds");
return string_value(format_date(args.get<date_t>(0),
args.get<string>(1)));
interactive_t args(scope, "d&s");
if (args.has(1))
return string_value(format_date(args.get<date_t>(0), FMT_CUSTOM,
args.get<string>(1).c_str()));
else
return string_value(format_date(args.get<date_t>(0), FMT_PRINTED));
}
value_t report_t::fn_ansify_if(call_scope_t& scope)
@ -521,6 +524,7 @@ option_t<report_t> * report_t::lookup_option(const char * p)
case 'd':
OPT(daily);
else OPT(date_format_);
else OPT(datetime_format_);
else OPT(depth_);
else OPT(deviation);
else OPT_(display_);

View file

@ -218,6 +218,7 @@ public:
HANDLER(current).report(out);
HANDLER(daily).report(out);
HANDLER(date_format_).report(out);
HANDLER(datetime_format_).report(out);
HANDLER(depth_).report(out);
HANDLER(deviation).report(out);
HANDLER(display_).report(out);
@ -412,10 +413,8 @@ public:
parent->HANDLER(period_).on(string("--daily"), "daily");
});
OPTION__(report_t, date_format_, // -y
CTOR(report_t, date_format_) {
on(none, "%y-%b-%d");
});
OPTION(report_t, date_format_);
OPTION(report_t, datetime_format_);
OPTION_(report_t, depth_, DO_(scope) {
interactive_t args(scope, "sl");
@ -633,9 +632,9 @@ public:
OPTION__(report_t, print_format_, CTOR(report_t, print_format_) {
on(none,
"%(format_date(xact.date, \"%Y/%m/%d\"))"
"%(xact.date)"
"%(!effective & xact.effective_date ?"
" \"=\" + format_date(xact.effective_date, \"%Y/%m/%d\") : \"\")"
" \"=\" + xact.effective_date : \"\")"
"%(xact.cleared ? \" *\" : (xact.pending ? \" !\" : \"\"))"
"%(code ? \" (\" + code + \")\" :"
" \"\") %(payee)%(xact.comment)\n"
@ -673,7 +672,8 @@ public:
OPTION__(report_t, register_format_, CTOR(report_t, register_format_) {
on(none,
"%(ansify_if(justify(date, date_width), green if color & date > today))"
"%(ansify_if(justify(format_date(date), date_width), green "
" if color & date > today))"
" %(ansify_if(justify(truncated(payee, payee_width), payee_width), "
" bold if color & !cleared))"
" %(ansify_if(justify(truncated(account, account_width, abbrev_len), "

View file

@ -45,6 +45,7 @@ namespace ledger {
void set_session_context(session_t * session)
{
if (session) {
times_initialize();
amount_t::initialize(session->commodity_pool);
// jww (2009-02-04): Is amount_t the right place for parse_conversion to
@ -57,6 +58,7 @@ void set_session_context(session_t * session)
else if (! session) {
value_t::shutdown();
amount_t::shutdown();
times_shutdown();
}
}

View file

@ -144,9 +144,9 @@ public:
});
OPTION_(session_t, input_date_format_, DO_(args) {
// This changes the global variable inside times.h, which affects the
// basic date parser
input_date_format = args[1].as_string();
// This changes static variables inside times.h, which affects the basic
// date parser.
set_input_date_format(args[1].as_string().c_str());
});
OPTION(session_t, price_db_);

View file

@ -35,55 +35,177 @@
namespace ledger {
date_time::weekdays start_of_week = gregorian::Sunday;
optional<std::string> input_date_format;
std::string output_datetime_format = "%Y-%m-%d %H:%M:%S";
std::string output_date_format = "%Y-%m-%d";
date_time::weekdays start_of_week = gregorian::Sunday;
//#define USE_BOOST_FACETS 1
namespace {
struct date_format_t {
const char * format;
bool has_year;
date_format_t(const char * _format, bool _has_year)
: format(_format), has_year(_has_year) {}
};
const date_format_t formats[] = {
date_format_t("%m/%d", false),
date_format_t("%Y/%m/%d", true),
date_format_t("%Y/%m", true),
date_format_t("%y/%m/%d", true),
date_format_t("%m.%d", false),
date_format_t("%Y.%m.%d", true),
date_format_t("%Y.%m", true),
date_format_t("%y.%m.%d", true),
date_format_t("%m-%d", false),
date_format_t("%Y-%m-%d", true),
date_format_t("%Y-%m", true),
date_format_t("%y-%m-%d", true)
};
date_t parse_date_mask_routine(const char * date_str, const date_format_t& df,
optional<date_t::year_type> year, bool& saw_year)
template <typename T, typename InputFacetType, typename OutputFacetType>
class temporal_io_t : public noncopyable
{
std::string str(date_str);
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
gregorian::date_input_facet * facet(new gregorian::date_input_facet(df.format));
std::istringstream sstr(str);
sstr.imbue(std::locale(sstr.getloc(), facet));
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
}
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
}
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
std::tm data(to_tm(when));
char buf[128];
std::strftime(buf, 127, fmt_str, &data);
return buf;
#endif
}
};
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 '" << str
<< "' using pattern '" << fmt_str << "'");
#endif
if (! when.is_not_a_date_time() &&
input_stream.good() && ! input_stream.eof() &&
input_stream.peek() != EOF)
return datetime_t();
return when;
#else
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
}
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 '" << str
<< "' using pattern '" << fmt_str << "'");
#endif
if (! when.is_not_a_date() &&
input_stream.good() && ! input_stream.eof() &&
input_stream.peek() != EOF)
return date_t();
return when;
#else
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
}
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;
std::auto_ptr<datetime_io_t> written_datetime_io;
std::auto_ptr<datetime_io_t> printed_datetime_io;
std::auto_ptr<date_io_t> input_date_io;
std::auto_ptr<date_io_t> written_date_io;
std::auto_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;
sstr >> 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()) {
if (sstr.good() && ! sstr.eof() && sstr.peek() != EOF)
return date_t();
DEBUG("times.parse", "Parsed date string: " << date_str);
DEBUG("times.parse", "Parsed result is: " << when);
DEBUG("times.parse", "Format used was: " << df.format);
DEBUG("times.parse", "Parsed result is: " << when);
if (! df.has_year) {
if (! io.has_year) {
saw_year = false;
when = date_t(year ? *year : CURRENT_DATE().year(),
@ -98,25 +220,24 @@ namespace {
return when;
}
date_t parse_date_mask(const char * date_str, optional<date_t::year_type> year,
bool& saw_year)
date_t parse_date_mask(const char * date_str,
optional<date_t::year_type> year, bool& saw_year)
{
if (input_date_format) {
date_format_t df(input_date_format->c_str(), true);
if (! icontains(*input_date_format, "%y"))
df.has_year = false;
date_t when = parse_date_mask_routine(date_str, df, year, 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;
}
for (uint8_t i = 0; i < (sizeof(formats) / sizeof(date_format_t)); i++) {
date_t when = parse_date_mask_routine(date_str, formats[i], year,
saw_year);
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();
}
}
@ -174,16 +295,7 @@ string_to_month_of_year(const std::string& str)
datetime_t parse_datetime(const char * str, optional<date_t::year_type>)
{
posix_time::time_input_facet * facet
(new posix_time::time_input_facet("%Y/%m/%d %H:%M:%S"));
std::string temp(str);
std::istringstream sstr(temp);
sstr.imbue(std::locale(sstr.getloc(), facet));
datetime_t when;
sstr >> when;
return when;
return written_datetime_io->parse(str);
}
date_t parse_date(const char * str, optional<date_t::year_type> current_year)
@ -481,7 +593,7 @@ namespace {
inline void read_lower_word(std::istream& in, string& word) {
in >> word;
for (int i = 0, l = word.length(); i < l; i++)
for (string::size_type i = 0, l = word.length(); i < l; i++)
word[i] = static_cast<char>(std::tolower(word[i]));
}
@ -662,4 +774,123 @@ void date_interval_t::parse(std::istream& in)
}
}
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) {
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) {
printed_datetime_io.reset();
written_datetime_io.reset();
input_date_io.reset();
printed_date_io.reset();
written_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

View file

@ -74,7 +74,6 @@ inline bool is_valid(const date_t& moment) {
#define CURRENT_DATE() boost::gregorian::day_clock::universal_day()
extern date_time::weekdays start_of_week;
extern optional<std::string> input_date_format;
optional<date_time::weekdays>
string_to_day_of_week(const std::string& str);
@ -97,33 +96,20 @@ inline date_t parse_date(const std::string& str,
return parse_date(str.c_str(), current_year);
}
extern std::string output_datetime_format;
enum format_type_t {
FMT_WRITTEN, FMT_PRINTED, FMT_CUSTOM
};
inline std::string format_datetime(const datetime_t& when,
const optional<std::string>& format = none)
{
posix_time::time_facet * facet
(new posix_time::time_facet(format ? format->c_str() :
output_datetime_format.c_str()));
std::ostringstream buf;
buf.imbue(std::locale(std::locale::classic(), facet));
buf << when;
return buf.str();
}
std::string format_datetime(const datetime_t& when,
const format_type_t format_type = FMT_PRINTED,
const optional<const char *>& format = none);
void set_datetime_format(const char * format);
extern std::string output_date_format;
inline std::string format_date(const date_t& when,
const optional<std::string>& format = none)
{
gregorian::date_facet * facet
(new gregorian::date_facet(format ? format->c_str() :
output_date_format.c_str()));
std::ostringstream buf;
buf.imbue(std::locale(std::locale::classic(), facet));
buf << when;
return buf.str();
}
std::string format_date(const date_t& when,
const format_type_t format_type = FMT_PRINTED,
const optional<const char *>& format = none);
void set_date_format(const char * format);
void set_input_date_format(const char * format);
class date_interval_t : public equality_comparable<date_interval_t>
{
@ -207,6 +193,9 @@ public:
date_interval_t& operator++();
};
void times_initialize();
void times_shutdown();
std::ostream& operator<<(std::ostream& out,
const date_interval_t::duration_t& duration);

View file

@ -984,7 +984,7 @@ void value_t::in_place_cast(type_t cast_type)
set_datetime(datetime_t(as_date(), time_duration(0, 0, 0, 0)));
return;
case STRING:
set_string(format_date(as_date(), string("%Y-%m-%d")));
set_string(format_date(as_date(), FMT_WRITTEN));
return;
default:
break;
@ -996,7 +996,7 @@ void value_t::in_place_cast(type_t cast_type)
set_date(as_datetime().date());
return;
case STRING:
set_string(format_datetime(as_datetime(), string("%Y-%m-%d %H:%M:%S")));
set_string(format_datetime(as_datetime(), FMT_WRITTEN));
return;
default:
break;
@ -1495,16 +1495,17 @@ void value_t::print(std::ostream& out,
case DATETIME:
if (date_format)
out << format_datetime(as_datetime(), *date_format);
out << format_datetime(as_datetime(), FMT_CUSTOM,
date_format->c_str());
else
out << format_datetime(as_datetime());
out << format_datetime(as_datetime(), FMT_WRITTEN);
break;
case DATE:
if (date_format)
out << format_date(as_date(), *date_format);
out << format_date(as_date(), FMT_CUSTOM, date_format->c_str());
else
out << format_date(as_date());
out << format_date(as_date(), FMT_WRITTEN);
break;
case INTEGER:
@ -1579,10 +1580,10 @@ void value_t::dump(std::ostream& out, const bool relaxed) const
break;
case DATETIME:
out << '[' << format_datetime(as_datetime()) << ']';
out << '[' << format_datetime(as_datetime(), FMT_WRITTEN) << ']';
break;
case DATE:
out << '[' << format_date(as_date()) << ']';
out << '[' << format_date(as_date(), FMT_WRITTEN) << ']';
break;
case INTEGER:

View file

@ -934,7 +934,7 @@ public:
#define NULL_VALUE (value_t())
inline value_t string_value(const string& str) {
inline value_t string_value(const string& str = "") {
return value_t(str, true);
}

View file

@ -4,7 +4,7 @@ csv --csv-format='"%(date)"\n'
Assets:Investments:Vanguard:VMMXX 0.350 VMMXX @ $1.00
Income:Dividends:Vanguard:VMMXX $-0.35
>>>1
"07-Feb-02"
"07-Feb-02"
"2007/02/02"
"2007/02/02"
>>>2
=== 0

View file

@ -10,10 +10,10 @@ P 2009/02/01 17:30:00 AAPL $50.00
Assets:Brokerage 100 AAPL
Income
>>>1
09-Jan-01 13:30:00 $10.00
09-Jan-01 14:30:00 $20.00
09-Jan-01 15:30:00 $30.00
09-Jan-01 16:30:00 $40.00
09-Feb-01 17:30:00 $50.00
2009/01/01 13:30:00 $10.00
2009/01/01 14:30:00 $20.00
2009/01/01 15:30:00 $30.00
2009/01/01 16:30:00 $40.00
2009/02/01 17:30:00 $50.00
>>>2
=== 0

View file

@ -10,10 +10,10 @@ P 2009/02/01 17:30:00 AAPL $50.00
Assets:Brokerage 100 AAPL
Income
>>>1
P 09-Jan-01 $10.00
P 09-Jan-01 $20.00
P 09-Jan-01 $30.00
P 09-Jan-01 $40.00
P 09-Feb-01 $50.00
P 2009/01/01 $10.00
P 2009/01/01 $20.00
P 2009/01/01 $30.00
P 2009/01/01 $40.00
P 2009/02/01 $50.00
>>>2
=== 0

View file

@ -3,8 +3,8 @@ period june 2008
>>>1
global details =>
start: 2008-06-01
end: 2008-07-01
start: 08-Jun-01
end: 08-Jul-01
factor: 1
>>>2
=== 0