From b2ba07b90c11b4645a7ae4310e8422c8d5f62f35 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Tue, 17 Nov 2009 21:28:43 -0500 Subject: Fixed several time and date duration type uses --- src/commodity.cc | 3 ++- src/value.cc | 22 ++++++++++++++-------- 2 files changed, 16 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/commodity.cc b/src/commodity.cc index 49e82b53..b76c7896 100644 --- a/src/commodity.cc +++ b/src/commodity.cc @@ -637,7 +637,8 @@ bool compare_amount_commodities::operator()(const amount_t * left, return false; if (aleftcomm.details.date && arightcomm.details.date) { - date_duration_t diff = *aleftcomm.details.date - *arightcomm.details.date; + gregorian::date_duration diff = + *aleftcomm.details.date - *arightcomm.details.date; return diff.is_negative(); } diff --git a/src/value.cc b/src/value.cc index 3f70ab3d..797c144c 100644 --- a/src/value.cc +++ b/src/value.cc @@ -338,10 +338,13 @@ value_t& value_t::operator+=(const value_t& val) case DATETIME: switch (val.type()) { case INTEGER: - as_datetime_lval() += date_duration(val.as_long()); + as_datetime_lval() += + time_duration_t(0, 0, static_cast(val.as_long())); return *this; case AMOUNT: - as_datetime_lval() += date_duration(val.as_amount().to_long()); + as_datetime_lval() += + time_duration_t(0, 0, static_cast + (val.as_amount().to_long())); return *this; default: break; @@ -351,10 +354,10 @@ value_t& value_t::operator+=(const value_t& val) case DATE: switch (val.type()) { case INTEGER: - as_date_lval() += date_duration_t(val.as_long()); + as_date_lval() += gregorian::date_duration(val.as_long()); return *this; case AMOUNT: - as_date_lval() += date_duration_t(val.as_amount().to_long()); + as_date_lval() += gregorian::date_duration(val.as_amount().to_long()); return *this; default: break; @@ -466,10 +469,13 @@ value_t& value_t::operator-=(const value_t& val) case DATETIME: switch (val.type()) { case INTEGER: - as_datetime_lval() -= date_duration(val.as_long()); + as_datetime_lval() -= + time_duration_t(0, 0, static_cast(val.as_long())); return *this; case AMOUNT: - as_datetime_lval() -= date_duration(val.as_amount().to_long()); + as_datetime_lval() -= + time_duration_t(0, 0, static_cast + (val.as_amount().to_long())); return *this; default: break; @@ -479,10 +485,10 @@ value_t& value_t::operator-=(const value_t& val) case DATE: switch (val.type()) { case INTEGER: - as_date_lval() -= date_duration_t(val.as_long()); + as_date_lval() -= gregorian::date_duration(val.as_long()); return *this; case AMOUNT: - as_date_lval() -= date_duration_t(val.as_amount().to_long()); + as_date_lval() -= gregorian::date_duration(val.as_amount().to_long()); return *this; default: break; -- cgit v1.2.3 From b06df8533ba3216e59bc135a29e3a7cd3606c1d1 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Tue, 17 Nov 2009 21:28:56 -0500 Subject: Added ledger::string(Iter, Iter) constructor --- src/utils.h | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src') diff --git a/src/utils.h b/src/utils.h index 1ea78620..ab8fb495 100644 --- a/src/utils.h +++ b/src/utils.h @@ -194,6 +194,11 @@ public: string(const string& str); string(const std::string& str); string(size_type len, char x); + template + string(_InputIterator __beg, _InputIterator __end) + : std::string(__beg, __end) { + TRACE_CTOR(string, "InputIterator, InputIterator"); + } string(const char * str); string(const char * str, const char * end); string(const string& str, size_type x); -- cgit v1.2.3 From 4bc830a2c526cad108695104503f377997e14e7e Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Tue, 17 Nov 2009 21:29:33 -0500 Subject: Some reformatting in query.h --- src/query.h | 72 +++++++++++++++++++++++++++++++------------------------------ 1 file changed, 37 insertions(+), 35 deletions(-) (limited to 'src') diff --git a/src/query.h b/src/query.h index 43db60a6..fb73ef2a 100644 --- a/src/query.h +++ b/src/query.h @@ -96,14 +96,14 @@ public: explicit token_t(kind_t _kind = UNKNOWN, const optional& _value = none) : kind(_kind), value(_value) { - TRACE_CTOR(lexer_t::token_t, ""); + TRACE_CTOR(query_t::lexer_t::token_t, ""); } token_t(const token_t& tok) : kind(tok.kind), value(tok.value) { - TRACE_CTOR(lexer_t::token_t, "copy"); + TRACE_CTOR(query_t::lexer_t::token_t, "copy"); } ~token_t() throw() { - TRACE_DTOR(lexer_t::token_t); + TRACE_DTOR(query_t::lexer_t::token_t); } token_t& operator=(const token_t& tok) { @@ -120,40 +120,42 @@ public: string to_string() const { switch (kind) { - case UNKNOWN: return "UNKNOWN"; - case LPAREN: return "LPAREN"; - case RPAREN: return "RPAREN"; - case TOK_NOT: return "TOK_NOT"; - case TOK_AND: return "TOK_AND"; - case TOK_OR: return "TOK_OR"; - case TOK_EQ: return "TOK_EQ"; - case TOK_DATE: return "TOK_DATE"; - case TOK_CODE: return "TOK_CODE"; - case TOK_PAYEE: return "TOK_PAYEE"; - case TOK_NOTE: return "TOK_NOTE"; + case UNKNOWN: return "UNKNOWN"; + case LPAREN: return "LPAREN"; + case RPAREN: return "RPAREN"; + case TOK_NOT: return "TOK_NOT"; + case TOK_AND: return "TOK_AND"; + case TOK_OR: return "TOK_OR"; + case TOK_EQ: return "TOK_EQ"; + case TOK_DATE: return "TOK_DATE"; + case TOK_CODE: return "TOK_CODE"; + case TOK_PAYEE: return "TOK_PAYEE"; + case TOK_NOTE: return "TOK_NOTE"; case TOK_ACCOUNT: return "TOK_ACCOUNT"; - case TOK_META: return "TOK_META"; - case TOK_EXPR: return "TOK_EXPR"; - case TERM: return string("TERM(") + *value + ")"; + case TOK_META: return "TOK_META"; + case TOK_EXPR: return "TOK_EXPR"; + case TERM: return string("TERM(") + *value + ")"; case END_REACHED: return "END_REACHED"; } + assert(false); + return empty_string; } string symbol() const { switch (kind) { - case LPAREN: return "("; - case RPAREN: return ")"; - case TOK_NOT: return "not"; - case TOK_AND: return "and"; - case TOK_OR: return "or"; - case TOK_EQ: return "="; - case TOK_DATE: return "date"; - case TOK_CODE: return "code"; - case TOK_PAYEE: return "payee"; - case TOK_NOTE: return "note"; + case LPAREN: return "("; + case RPAREN: return ")"; + case TOK_NOT: return "not"; + case TOK_AND: return "and"; + case TOK_OR: return "or"; + case TOK_EQ: return "="; + case TOK_DATE: return "date"; + case TOK_CODE: return "code"; + case TOK_PAYEE: return "payee"; + case TOK_NOTE: return "note"; case TOK_ACCOUNT: return "account"; - case TOK_META: return "meta"; - case TOK_EXPR: return "expr"; + case TOK_META: return "meta"; + case TOK_EXPR: return "expr"; case END_REACHED: return ""; @@ -180,7 +182,7 @@ public: consume_whitespace(false), consume_next_arg(false) { - TRACE_CTOR(lexer_t, ""); + TRACE_CTOR(query_t::lexer_t, ""); assert(begin != end); arg_i = (*begin).as_string().begin(); arg_end = (*begin).as_string().end(); @@ -192,10 +194,10 @@ public: consume_next_arg(lexer.consume_next_arg), token_cache(lexer.token_cache) { - TRACE_CTOR(lexer_t, "copy"); + TRACE_CTOR(query_t::lexer_t, "copy"); } ~lexer_t() throw() { - TRACE_DTOR(lexer_t); + TRACE_DTOR(query_t::lexer_t); } token_t next_token(); @@ -227,14 +229,14 @@ protected: public: parser_t(const value_t& _args) : args(_args), lexer(args.begin(), args.end()) { - TRACE_CTOR(parser_t, ""); + TRACE_CTOR(query_t::parser_t, ""); } parser_t(const parser_t& parser) : args(parser.args), lexer(parser.lexer) { - TRACE_CTOR(parser_t, "copy"); + TRACE_CTOR(query_t::parser_t, "copy"); } ~parser_t() throw() { - TRACE_DTOR(parser_t); + TRACE_DTOR(query_t::parser_t); } expr_t::ptr_op_t parse() { -- cgit v1.2.3 From c28d828d8e24fe637a74674bedc9bc0cbdabca1c Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Tue, 17 Nov 2009 21:30:07 -0500 Subject: Renamed date_interval_t::end to finish --- src/filters.cc | 6 ++-- src/precmd.cc | 8 ++--- src/query.cc | 4 +-- src/report.cc | 4 +-- src/times.cc | 76 +++++++++++++++++++++++----------------------- src/times.h | 6 ++-- test/regress/7F3650FD.test | 10 +++--- test/regress/BBFA1759.test | 2 +- 8 files changed, 58 insertions(+), 58 deletions(-) (limited to 'src') diff --git a/src/filters.cc b/src/filters.cc index bef4dc24..5a92421b 100644 --- a/src/filters.cc +++ b/src/filters.cc @@ -784,7 +784,7 @@ void budget_posts::report_budget_items(const date_t& date) assert(begin); if (*begin <= date && - (! pair.first.end || *begin < *pair.first.end)) { + (! pair.first.finish || *begin < *pair.first.finish)) { post_t& post = *pair.second; DEBUG("budget.generate", "Reporting budget for " @@ -905,8 +905,8 @@ void forecast_posts::flush() } date_t& begin = *(*least).first.start; - if ((*least).first.end) - assert(begin < *(*least).first.end); + if ((*least).first.finish) + assert(begin < *(*least).first.finish); // If the next date in the series for this periodic posting is more than 5 // years beyond the last valid post we generated, drop it from further diff --git a/src/precmd.cc b/src/precmd.cc index 92483dc8..c4d7cbaa 100644 --- a/src/precmd.cc +++ b/src/precmd.cc @@ -173,8 +173,8 @@ value_t period_command(call_scope_t& args) out << _(" start: ") << format_date(*interval.start) << std::endl; else out << _(" start: TODAY: ") << format_date(CURRENT_DATE()) << std::endl; - if (interval.end) - out << _(" end: ") << format_date(*interval.end) << std::endl; + if (interval.finish) + out << _(" finish: ") << format_date(*interval.finish) << std::endl; if (interval.skip_duration) out << _(" skip: ") << *interval.skip_duration << std::endl; @@ -191,8 +191,8 @@ value_t period_command(call_scope_t& args) if (interval.start) out << _(" start: ") << format_date(*interval.start) << std::endl; - if (interval.end) - out << _(" end: ") << format_date(*interval.end) << std::endl; + if (interval.finish) + out << _(" finish: ") << format_date(*interval.finish) << std::endl; if (interval.skip_duration) out << _(" skip: ") << *interval.skip_duration << std::endl; diff --git a/src/query.cc b/src/query.cc index bf4eb62a..2d6085fa 100644 --- a/src/query.cc +++ b/src/query.cc @@ -263,12 +263,12 @@ query_t::parser_t::parse_query_term(query_t::lexer_t::token_t::kind_t tok_contex node->set_right(arg1); } - if (interval.end) { + if (interval.finish) { expr_t::ptr_op_t lt = new expr_t::op_t(expr_t::op_t::O_LT); lt->set_left(ident); expr_t::ptr_op_t arg1 = new expr_t::op_t(expr_t::op_t::VALUE); - arg1->set_value(*interval.end); + arg1->set_value(*interval.finish); lt->set_right(arg1); if (node) { diff --git a/src/report.cc b/src/report.cc index 267a4a3d..efe162e2 100644 --- a/src/report.cc +++ b/src/report.cc @@ -125,9 +125,9 @@ void report_t::normalize_options(const string& verb) "date>=[" + to_iso_extended_string(*interval.start) + "]"; HANDLER(limit_).on(string("?normalize"), predicate); } - if (! HANDLED(end_) && interval.end) { + if (! HANDLED(end_) && interval.finish) { string predicate = - "date<[" + to_iso_extended_string(*interval.end) + "]"; + "date<[" + to_iso_extended_string(*interval.finish) + "]"; HANDLER(limit_).on(string("?normalize"), predicate); } diff --git a/src/times.cc b/src/times.cc index 6afbab0a..8ab0c12c 100644 --- a/src/times.cc +++ b/src/times.cc @@ -345,8 +345,8 @@ void date_interval_t::resolve_end() "stabilize: end_of_duration = " << *end_of_duration); } - if (end && *end_of_duration > *end) { - end_of_duration = end; + if (finish && *end_of_duration > *finish) { + end_of_duration = finish; DEBUG("times.interval", "stabilize: end_of_duration reset to end: " << *end_of_duration); } @@ -384,16 +384,16 @@ void date_interval_t::stabilize(const optional& date) // 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 initial_start = start; - optional initial_end = end; + optional initial_start = start; + optional initial_finish = finish; #if defined(DEBUG_ON) if (initial_start) DEBUG("times.interval", "stabilize: initial_start = " << *initial_start); - if (initial_end) + if (initial_finish) DEBUG("times.interval", - "stabilize: initial_end = " << *initial_end); + "stabilize: initial_finish = " << *initial_finish); #endif date_t when = start ? *start : *date; @@ -439,22 +439,22 @@ void date_interval_t::stabilize(const optional& date) 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"); + if (initial_finish && (! finish || *finish > *initial_finish)) { + finish = initial_finish; + DEBUG("times.interval", "stabilize: finish reset to initial finish"); } } aligned = true; } // If there is no duration, then if we've reached here the date falls - // between begin and end. + // between start and finish. if (! duration) { DEBUG("times.interval", "stabilize: there was no duration given"); - if (! start && ! end) + if (! start && ! finish) throw_(date_error, - _("Invalid date interval: neither start, nor end, nor duration")); + _("Invalid date interval: neither start, nor finish, nor duration")); } else { resolve_end(); } @@ -464,9 +464,9 @@ bool date_interval_t::find_period(const date_t& date) { stabilize(date); - if (end && date > *end) { + if (finish && date > *finish) { DEBUG("times.interval", - "false: date [" << date << "] > end [" << *end << "]"); + "false: date [" << date << "] > finish [" << *finish << "]"); return false; } @@ -503,7 +503,7 @@ bool date_interval_t::find_period(const date_t& date) DEBUG("times.interval", "scan = " << scan); DEBUG("times.interval", "end_of_scan = " << end_of_scan); - while (date >= scan && (! end || scan < *end)) { + while (date >= scan && (! finish || scan < *finish)) { if (date < end_of_scan) { start = scan; end_of_duration = end_of_scan; @@ -535,7 +535,7 @@ date_interval_t& date_interval_t::operator++() assert(next); - if (end && *next >= *end) { + if (finish && *next >= *finish) { start = none; } else { start = *next; @@ -586,8 +586,8 @@ namespace { void parse_date_words(std::istream& in, string& word, date_interval_t& interval, - bool look_for_start = true, - bool look_for_end = true) + bool look_for_start = true, + bool look_for_finish = true) { string type; @@ -602,12 +602,12 @@ namespace { } date_t start = CURRENT_DATE(); - date_t end; + date_t finish; bool parse_specifier = false; optional duration; - assert(look_for_start || look_for_end); + assert(look_for_start || look_for_finish); if (word == _("year")) { duration = date_interval_t::duration_t(date_interval_t::duration_t::YEARS, 1); @@ -625,21 +625,21 @@ namespace { } if (parse_specifier) - parse_inclusion_specifier(word, &start, &end); + parse_inclusion_specifier(word, &start, &finish); else - end = duration->add(start); + finish = duration->add(start); if (type == _("last") && duration) { start = duration->subtract(start); - end = duration->subtract(end); + finish = duration->subtract(finish); } else if (type == _("next") && duration) { start = duration->add(start); - end = duration->add(end); + finish = duration->add(finish); } - if (look_for_start && is_valid(start)) interval.start = start; - if (look_for_end && is_valid(end)) interval.end = end; + if (look_for_start && is_valid(start)) interval.start = start; + if (look_for_finish && is_valid(finish)) interval.finish = finish; } } @@ -723,10 +723,10 @@ void date_interval_t::parse(std::istream& in) } else { // otherwise, it should be an explicit date - date_t b, e; - parse_inclusion_specifier(word, &b, &e); - start = b; - end = e; + date_t s, f; + parse_inclusion_specifier(word, &s, &f); + start = s; + finish = f; } } @@ -738,23 +738,23 @@ void date_interval_t::parse(std::istream& in) while (start->day_of_week() != *wday) *start -= gregorian::days(1); - if (! end) - end = *start + gregorian::days(1); + if (! finish) + finish = *start + gregorian::days(1); } else { - bool overwrite_end = false; + bool overwrite_finish = false; if (year) { start = date_t(*year, 1, 1); - if (! end) { - end = *start + gregorian::years(1); - overwrite_end = true; + if (! finish) { + finish = *start + gregorian::years(1); + overwrite_finish = true; } } if (mon) { start = date_t(start->year(), *mon, 1); - if (! end || overwrite_end) - end = *start + gregorian::months(1); + if (! finish || overwrite_finish) + finish = *start + gregorian::months(1); } } } diff --git a/src/times.h b/src/times.h index 77f25d9e..84970cd2 100644 --- a/src/times.h +++ b/src/times.h @@ -219,7 +219,7 @@ public: optional next; optional duration; optional end_of_duration; - optional end; + optional finish; explicit date_interval_t() : aligned(false), factor(1) { TRACE_CTOR(date_interval_t, ""); @@ -236,7 +236,7 @@ public: next(other.next), duration(other.duration), end_of_duration(other.end_of_duration), - end(other.end) { + finish(other.finish) { TRACE_CTOR(date_interval_t, "copy"); } ~date_interval_t() throw() { @@ -295,7 +295,7 @@ private: ar & next; ar & duration; ar & end_of_duration; - ar & end; + ar & finish; } #endif // HAVE_BOOST_SERIALIZATION }; diff --git a/test/regress/7F3650FD.test b/test/regress/7F3650FD.test index 0eb3a96f..dce5233f 100644 --- a/test/regress/7F3650FD.test +++ b/test/regress/7F3650FD.test @@ -4,7 +4,7 @@ period --now=2010/11/01 12/01 global details => start: 09-Dec-01 - end: 09-Dec-02 + finish: 09-Dec-02 factor: 1 >>>2 === 0 @@ -14,7 +14,7 @@ period --now=2010/11/01 10/01 global details => start: 10-Oct-01 - end: 10-Oct-02 + finish: 10-Oct-02 factor: 1 >>>2 === 0 @@ -24,7 +24,7 @@ period --now=2010/11/01 2009/10 global details => start: 09-Oct-01 - end: 09-Nov-01 + finish: 09-Nov-01 factor: 1 >>>2 === 0 @@ -34,7 +34,7 @@ period --now=2010/11/01 2009/10/01 global details => start: 09-Oct-01 - end: 09-Oct-02 + finish: 09-Oct-02 factor: 1 >>>2 === 0 @@ -44,7 +44,7 @@ period --now=2010/11/01 2009 global details => start: 09-Jan-01 - end: 10-Jan-01 + finish: 10-Jan-01 factor: 1 >>>2 === 0 diff --git a/test/regress/BBFA1759.test b/test/regress/BBFA1759.test index 26862703..b109f868 100644 --- a/test/regress/BBFA1759.test +++ b/test/regress/BBFA1759.test @@ -4,7 +4,7 @@ period june 2008 global details => start: 08-Jun-01 - end: 08-Jul-01 + finish: 08-Jul-01 factor: 1 >>>2 === 0 -- cgit v1.2.3 From a866f39210534052bc77c975344706c84c39a4a9 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Tue, 17 Nov 2009 22:03:32 -0500 Subject: Added a date_traits_t type --- src/times.cc | 50 ++++++++++++++++++++++++-------------------------- src/times.h | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 26 deletions(-) (limited to 'src') diff --git a/src/times.cc b/src/times.cc index 8ab0c12c..5cccec21 100644 --- a/src/times.cc +++ b/src/times.cc @@ -55,13 +55,15 @@ namespace { #endif // USE_BOOST_FACETS public: - bool has_year; - bool has_day; + date_traits_t traits; bool input; temporal_io_t(const char * _fmt_str, bool _input) - : fmt_str(_fmt_str), has_year(icontains(fmt_str, "%y")), - has_day(icontains(fmt_str, "%d")), input(_input) { + : fmt_str(_fmt_str), + traits(icontains(fmt_str, "%y"), + icontains(fmt_str, "%m") || icontains(fmt_str, "%b"), + icontains(fmt_str, "%d")), + input(_input) { #if defined(USE_BOOST_FACETS) if (input) { input_facet = new InputFacetType(fmt_str); @@ -75,9 +77,10 @@ namespace { void set_format(const char * fmt) { fmt_str = fmt; - has_year = icontains(fmt_str, "%y"); - has_day = icontains(fmt_str, "%d"); - + traits = date_traits_t(icontains(fmt_str, "%y"), + icontains(fmt_str, "%m") || + icontains(fmt_str, "%b"), + icontains(fmt_str, "%d")); #if defined(USE_BOOST_FACETS) if (input) input_facet->format(fmt_str); @@ -192,7 +195,7 @@ namespace { date_t parse_date_mask_routine(const char * date_str, date_io_t& io, optional year, - bool& saw_year, bool& saw_day) + date_traits_t * traits = NULL) { date_t when; @@ -214,38 +217,34 @@ namespace { DEBUG("times.parse", "Parsed date string: " << date_str); DEBUG("times.parse", "Parsed result is: " << when); - if (! io.has_year) { - saw_year = false; + if (traits) + *traits = io.traits; + if (! io.traits.has_year) { 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; - } - - saw_day = io.has_day; } return when; } date_t parse_date_mask(const char * date_str, optional year, - bool& saw_year, bool& saw_day) + date_traits_t * traits = NULL) { if (input_date_io.get()) { date_t when = parse_date_mask_routine(date_str, *input_date_io.get(), - year, saw_year, saw_day); + year, traits); if (! when.is_not_a_date()) return when; } foreach (shared_ptr& reader, readers) { date_t when = parse_date_mask_routine(date_str, *reader.get(), - year, saw_year, saw_day); + year, traits); if (! when.is_not_a_date()) return when; } @@ -316,9 +315,7 @@ datetime_t parse_datetime(const char * str, optional) date_t parse_date(const char * str, optional current_year) { - bool saw_year; - bool saw_day; - return parse_date_mask(str, current_year, saw_year, saw_day); + return parse_date_mask(str, current_year); } std::ostream& operator<<(std::ostream& out, @@ -555,9 +552,8 @@ namespace { date_t * begin, date_t * end) { - bool saw_year = true; - bool saw_day = true; - date_t when = parse_date_mask(word.c_str(), none, saw_year, saw_day); + date_traits_t traits; + date_t when = parse_date_mask(word.c_str(), none, &traits); if (when.is_not_a_date()) throw_(date_error, _("Could not parse date mask: %1") << word); @@ -566,10 +562,12 @@ namespace { *begin = when; if (end) { - if (saw_day) + if (traits.has_day) *end = *begin + gregorian::days(1); - else + else if (traits.has_month) *end = *begin + gregorian::months(1); + else + *end = *begin + gregorian::years(1); } } else if (end) { diff --git a/src/times.h b/src/times.h index 84970cd2..676ec450 100644 --- a/src/times.h +++ b/src/times.h @@ -138,6 +138,50 @@ inline void to_xml(std::ostream& out, const date_t& when, } } +struct date_traits_t +{ + bool has_year; + bool has_month; + bool has_day; + + date_traits_t(bool _has_year = false, + bool _has_month = false, + bool _has_day = false) + : has_year(_has_year), has_month(_has_month), has_day(_has_day) {} + + date_traits_t(const date_traits_t& traits) + : has_year(traits.has_year), + has_month(traits.has_month), + has_day(traits.has_day) {} + + date_traits_t& operator=(const date_traits_t& traits) { + has_year = traits.has_year; + has_month = traits.has_month; + has_day = traits.has_day; + return *this; + } + + bool operator==(const date_traits_t& traits) const { + return (has_year == traits.has_year && + has_month == traits.has_month && + has_day == traits.has_day); + } + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template + void serialize(Archive& ar, const unsigned int /* version */) { + ar & has_year; + ar & has_month; + ar & has_day; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + class date_interval_t : public equality_comparable { public: -- cgit v1.2.3 From d6cb382b20a1c65fde043af0ef41f21570027b27 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Tue, 17 Nov 2009 22:12:17 -0500 Subject: Moved date_interval_t::duration_t to date_duration_t --- src/times.cc | 58 +++++++++++----------- src/times.h | 156 +++++++++++++++++++++++++++++------------------------------ 2 files changed, 106 insertions(+), 108 deletions(-) (limited to 'src') diff --git a/src/times.cc b/src/times.cc index 5cccec21..2fb8479b 100644 --- a/src/times.cc +++ b/src/times.cc @@ -319,16 +319,16 @@ date_t parse_date(const char * str, optional current_year) } std::ostream& operator<<(std::ostream& out, - const date_interval_t::duration_t& duration) + const date_duration_t& duration) { - if (duration.quantum == date_interval_t::duration_t::DAYS) + if (duration.quantum == date_duration_t::DAYS) out << duration.length << " day(s)"; - else if (duration.quantum == date_interval_t::duration_t::WEEKS) + else if (duration.quantum == date_duration_t::WEEKS) out << duration.length << " week(s)"; - else if (duration.quantum == date_interval_t::duration_t::MONTHS) + else if (duration.quantum == date_duration_t::MONTHS) out << duration.length << " month(s)"; else { - assert(duration.quantum == date_interval_t::duration_t::YEARS); + assert(duration.quantum == date_duration_t::YEARS); out << duration.length << " year(s)"; } return out; @@ -395,8 +395,8 @@ void date_interval_t::stabilize(const optional& date) date_t when = start ? *start : *date; - if (duration->quantum == duration_t::MONTHS || - duration->quantum == duration_t::YEARS) { + if (duration->quantum == date_duration_t::MONTHS || + duration->quantum == date_duration_t::YEARS) { DEBUG("times.interval", "stabilize: monthly or yearly duration"); start = date_t(when.year(), gregorian::Jan, 1); @@ -405,7 +405,7 @@ void date_interval_t::stabilize(const optional& date) start = date_t(when - gregorian::days(400)); - if (duration->quantum == duration_t::WEEKS) { + if (duration->quantum == date_duration_t::WEEKS) { // Move it to a Sunday while (start->day_of_week() != start_of_week) *start += gregorian::days(1); @@ -603,20 +603,20 @@ namespace { date_t finish; bool parse_specifier = false; - optional duration; + optional duration; assert(look_for_start || look_for_finish); if (word == _("year")) { - duration = date_interval_t::duration_t(date_interval_t::duration_t::YEARS, 1); + duration = date_duration_t(date_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); + duration = date_duration_t(date_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); + duration = date_duration_t(date_duration_t::DAYS, 1); } else { parse_specifier = true; @@ -657,41 +657,41 @@ void date_interval_t::parse(std::istream& in) int quantity = lexical_cast(word); read_lower_word(in, word); if (word == _("days")) - duration = duration_t(duration_t::DAYS, quantity); + duration = date_duration_t(date_duration_t::DAYS, quantity); else if (word == _("weeks")) - duration = duration_t(duration_t::WEEKS, quantity); + duration = date_duration_t(date_duration_t::WEEKS, quantity); else if (word == _("months")) - duration = duration_t(duration_t::MONTHS, quantity); + duration = date_duration_t(date_duration_t::MONTHS, quantity); else if (word == _("quarters")) - duration = duration_t(duration_t::MONTHS, 3 * quantity); + duration = date_duration_t(date_duration_t::MONTHS, 3 * quantity); else if (word == _("years")) - duration = duration_t(duration_t::YEARS, quantity); + duration = date_duration_t(date_duration_t::YEARS, quantity); } else if (word == _("day")) - duration = duration_t(duration_t::DAYS, 1); + duration = date_duration_t(date_duration_t::DAYS, 1); else if (word == _("week")) - duration = duration_t(duration_t::WEEKS, 1); + duration = date_duration_t(date_duration_t::WEEKS, 1); else if (word == _("month")) - duration = duration_t(duration_t::MONTHS, 1); + duration = date_duration_t(date_duration_t::MONTHS, 1); else if (word == _("quarter")) - duration = duration_t(duration_t::MONTHS, 3); + duration = date_duration_t(date_duration_t::MONTHS, 3); else if (word == _("year")) - duration = duration_t(duration_t::YEARS, 1); + duration = date_duration_t(date_duration_t::YEARS, 1); } else if (word == _("daily")) - duration = duration_t(duration_t::DAYS, 1); + duration = date_duration_t(date_duration_t::DAYS, 1); else if (word == _("weekly")) - duration = duration_t(duration_t::WEEKS, 1); + duration = date_duration_t(date_duration_t::WEEKS, 1); else if (word == _("biweekly")) - duration = duration_t(duration_t::WEEKS, 2); + duration = date_duration_t(date_duration_t::WEEKS, 2); else if (word == _("monthly")) - duration = duration_t(duration_t::MONTHS, 1); + duration = date_duration_t(date_duration_t::MONTHS, 1); else if (word == _("bimonthly")) - duration = duration_t(duration_t::MONTHS, 2); + duration = date_duration_t(date_duration_t::MONTHS, 2); else if (word == _("quarterly")) - duration = duration_t(duration_t::MONTHS, 3); + duration = date_duration_t(date_duration_t::MONTHS, 3); else if (word == _("yearly")) - duration = duration_t(duration_t::YEARS, 1); + duration = date_duration_t(date_duration_t::YEARS, 1); else if (word == _("this") || word == _("last") || word == _("next") || word == _("today")) { parse_date_words(in, word, *this); diff --git a/src/times.h b/src/times.h index 676ec450..c50b0366 100644 --- a/src/times.h +++ b/src/times.h @@ -59,7 +59,6 @@ inline bool is_valid(const datetime_t& moment) { } typedef boost::gregorian::date date_t; -typedef boost::gregorian::date_duration date_duration_t; typedef boost::gregorian::date_iterator date_iterator_t; inline bool is_valid(const date_t& moment) { @@ -182,88 +181,88 @@ private: #endif // HAVE_BOOST_SERIALIZATION }; -class date_interval_t : public equality_comparable +struct date_duration_t { -public: - struct duration_t - { - enum skip_quantum_t { - DAYS, WEEKS, MONTHS, YEARS - } quantum; - int length; - - duration_t() : quantum(DAYS), length(0) { - TRACE_CTOR(date_interval_t::duration_t, ""); - } - duration_t(skip_quantum_t _quantum, int _length) - : quantum(_quantum), length(_length) { - TRACE_CTOR(date_interval_t::duration_t, "skip_quantum_t, int"); - } - duration_t(const duration_t& dur) - : quantum(dur.quantum), length(dur.length) { - TRACE_CTOR(date_interval_t::duration_t, "copy"); - } - ~duration_t() throw() { - TRACE_DTOR(date_interval_t::duration_t); - } - - date_t add(const date_t& date) const { - switch (quantum) { - case DAYS: - return date + gregorian::days(length); - case WEEKS: - return date + gregorian::weeks(length); - case MONTHS: - return date + gregorian::months(length); - case YEARS: - return date + gregorian::years(length); - default: - assert(false); return date_t(); - } + enum skip_quantum_t { + DAYS, WEEKS, MONTHS, YEARS + } quantum; + int length; + + date_duration_t() : quantum(DAYS), length(0) { + TRACE_CTOR(date_duration_t, ""); + } + date_duration_t(skip_quantum_t _quantum, int _length) + : quantum(_quantum), length(_length) { + TRACE_CTOR(date_duration_t, "skip_quantum_t, int"); + } + date_duration_t(const date_duration_t& dur) + : quantum(dur.quantum), length(dur.length) { + TRACE_CTOR(date_duration_t, "copy"); + } + ~date_duration_t() throw() { + TRACE_DTOR(date_duration_t); + } + + date_t add(const date_t& date) const { + switch (quantum) { + case DAYS: + return date + gregorian::days(length); + case WEEKS: + return date + gregorian::weeks(length); + case MONTHS: + return date + gregorian::months(length); + case YEARS: + return date + gregorian::years(length); + default: + assert(false); return date_t(); } + } - date_t subtract(const date_t& date) const { - switch (quantum) { - case DAYS: - return date - gregorian::days(length); - case WEEKS: - return date - gregorian::weeks(length); - case MONTHS: - return date - gregorian::months(length); - case YEARS: - return date - gregorian::years(length); - default: - assert(false); return date_t(); - } + date_t subtract(const date_t& date) const { + switch (quantum) { + case DAYS: + return date - gregorian::days(length); + case WEEKS: + return date - gregorian::weeks(length); + case MONTHS: + return date - gregorian::months(length); + case YEARS: + return date - gregorian::years(length); + default: + assert(false); return date_t(); } + } #if defined(HAVE_BOOST_SERIALIZATION) - private: - /** Serialization. */ +private: + /** Serialization. */ - friend class boost::serialization::access; + friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int /* version */) { - ar & quantum; - ar & length; - } + template + void serialize(Archive& ar, const unsigned int /* version */) { + ar & quantum; + ar & length; + } #endif // HAVE_BOOST_SERIALIZATION - }; - - static date_t add_duration(const date_t& date, - const duration_t& duration); - static date_t subtract_duration(const date_t& date, - const duration_t& duration); - - optional start; - bool aligned; - optional skip_duration; - std::size_t factor; - optional next; - optional duration; - optional end_of_duration; - optional finish; +}; + +class date_interval_t : public equality_comparable +{ +public: + static date_t add_duration(const date_t& date, + const date_duration_t& duration); + static date_t subtract_duration(const date_t& date, + const date_duration_t& duration); + + optional start; // the real start, after adjustment + optional finish; // the real end, likewise + bool aligned; + optional skip_duration; + std::size_t factor; + optional next; + optional duration; + optional end_of_duration; explicit date_interval_t() : aligned(false), factor(1) { TRACE_CTOR(date_interval_t, ""); @@ -274,13 +273,13 @@ public: } date_interval_t(const date_interval_t& other) : start(other.start), + finish(other.finish), aligned(other.aligned), skip_duration(other.skip_duration), factor(other.factor), next(other.next), duration(other.duration), - end_of_duration(other.end_of_duration), - finish(other.finish) { + end_of_duration(other.end_of_duration) { TRACE_CTOR(date_interval_t, "copy"); } ~date_interval_t() throw() { @@ -333,13 +332,13 @@ private: template void serialize(Archive& ar, const unsigned int /* version */) { ar & start; + ar & finish; ar & aligned; ar & skip_duration; ar & factor; ar & next; ar & duration; ar & end_of_duration; - ar & finish; } #endif // HAVE_BOOST_SERIALIZATION }; @@ -347,8 +346,7 @@ private: void times_initialize(); void times_shutdown(); -std::ostream& operator<<(std::ostream& out, - const date_interval_t::duration_t& duration); +std::ostream& operator<<(std::ostream& out, const date_duration_t& duration); } // namespace ledger -- cgit v1.2.3 From 3e91c3bf2c3662c40f0fe7c9cf197f6b0c725269 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Tue, 17 Nov 2009 22:23:46 -0500 Subject: Added several new types for working with dates and ranges date_specifier_t :: This is like a plain date_t, except it knows what wasn't specified. For example, if 2008/06 is parsed, it becomes date_specifier_t which knows that no day was given. If you ask for the begin() date of the specifier, it will be 2008/06/01; the end() date (which is exclusive) will be 2008/07/01. date_range_t :: A date range is a range of two specifiers, either of which (but not both) may be omitted. This makes it possible to represent expressions like "from june to july", where no day or year is given. The exact dates will be inferred by using the current year, and fixing the range from YEAR/06/01 to YEAR/07/01. That is, the range goes from the begin() of one date specifier to the begin() of the other. date_specifier_or_range_t :: A variadic type that can be either a date_specifier_t or a date_range_t. It's just a wrapper to represent the fact that ranges can be implicit via specifiers (such as, "in june"), or explicit via ranges ("since 2008"). --- src/times.cc | 288 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/times.h | 327 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 615 insertions(+) (limited to 'src') diff --git a/src/times.cc b/src/times.cc index 2fb8479b..3070249c 100644 --- a/src/times.cc +++ b/src/times.cc @@ -318,6 +318,43 @@ date_t parse_date(const char * str, optional current_year) return parse_date_mask(str, current_year); } +date_t +date_specifier_t::begin(const optional& current_year) const +{ + assert(year || current_year); + + year_type the_year = year ? *year : static_cast(*current_year); + month_type the_month = month ? *month : date_t::month_type(1); + day_type the_day = day ? *day : date_t::day_type(1); + + if (day) + assert(! wday); + else if (wday) + assert(! day); + + // jww (2009-11-16): Handle wday. If a month is set, find the most recent + // wday in that month; if the year is set, then in that year. + + return gregorian::date(static_cast(the_year), + static_cast(the_month), + static_cast(the_day)); +} + +date_t +date_specifier_t::end(const optional& current_year) const +{ + if (day || wday) + return begin(current_year) + gregorian::days(1); + else if (month) + return begin(current_year) + gregorian::months(1); + else if (year) + return begin(current_year) + gregorian::years(1); + else { + assert(false); + return date_t(); + } +} + std::ostream& operator<<(std::ostream& out, const date_duration_t& duration) { @@ -758,6 +795,171 @@ void date_interval_t::parse(std::istream& in) } } +date_parser_t::lexer_t::token_t date_parser_t::lexer_t::next_token() +{ + if (token_cache.kind != token_t::UNKNOWN) { + token_t tok = token_cache; + token_cache = token_t(); + return tok; + } + + while (begin != end && std::isspace(*begin)) + begin++; + + if (begin == end) + return token_t(token_t::END_REACHED); + + switch (*begin) { + case '/': ++begin; return token_t(token_t::TOK_SLASH); + case '-': ++begin; return token_t(token_t::TOK_DASH); + case '.': ++begin; return token_t(token_t::TOK_DOT); + default: break; + } + + string::const_iterator start = begin; + + // If the first character is a digit, try parsing the whole argument as a + // date using the typical date formats. This allows not only dates like + // "2009/08/01", but also dates that fit the user's --input-date-format, + // assuming their format fits in one argument and begins with a digit. + if (std::isdigit(*begin)) { + try { + string::const_iterator i = begin; + for (i = begin; i != end && ! std::isspace(*i); i++) {} + assert(i != begin); + + string possible_date(start, i); + date_traits_t traits; + + date_t when = parse_date_mask(possible_date.c_str(), none, &traits); + if (! when.is_not_a_date()) { + begin = i; + return token_t(token_t::TOK_DATE, + token_t::content_t(date_specifier_t(when, traits))); + } + } + catch (...) {} + } + + string term; + bool alnum = std::isalnum(*begin); + for (start = begin; (begin != end && ! std::isspace(*begin) && + alnum == std::isalnum(*begin)); begin++) + term.push_back(*begin); + + if (! term.empty()) { + if (std::isdigit(term[0])) { + return token_t(term.length() == 4 ? + token_t::TOK_A_YEAR : token_t::TOK_INT, + token_t::content_t(lexical_cast(term))); + } + else if (std::isalpha(term[0])) { + if (optional month = + string_to_month_of_year(term)) { + date_specifier_t specifier; + specifier.month = static_cast(*month); + return token_t(token_t::TOK_A_MONTH, token_t::content_t(specifier)); + } + else if (optional wday = + string_to_day_of_week(term)) { + date_specifier_t specifier; + specifier.wday = static_cast(*wday); + return token_t(token_t::TOK_A_WDAY, token_t::content_t(specifier)); + } + else if (term == _("from") || term == _("since")) + return token_t(token_t::TOK_SINCE); + else if (term == _("to") || term == _("until")) + return token_t(token_t::TOK_UNTIL); + else if (term == _("in")) + return token_t(token_t::TOK_IN); + else if (term == _("this")) + return token_t(token_t::TOK_THIS); + else if (term == _("next")) + return token_t(token_t::TOK_NEXT); + else if (term == _("last")) + return token_t(token_t::TOK_LAST); + else if (term == _("year")) + return token_t(token_t::TOK_YEAR); + else if (term == _("quarter")) + return token_t(token_t::TOK_QUARTER); + else if (term == _("month")) + return token_t(token_t::TOK_MONTH); + else if (term == _("week")) + return token_t(token_t::TOK_WEEK); + else if (term == _("day")) + return token_t(token_t::TOK_DAY); + else if (term == _("yearly")) + return token_t(token_t::TOK_YEARLY); + else if (term == _("quarterly")) + return token_t(token_t::TOK_QUARTERLY); + else if (term == _("bimonthly")) + return token_t(token_t::TOK_BIMONTHLY); + else if (term == _("monthly")) + return token_t(token_t::TOK_MONTHLY); + else if (term == _("biweekly")) + return token_t(token_t::TOK_BIWEEKLY); + else if (term == _("weekly")) + return token_t(token_t::TOK_WEEKLY); + else if (term == _("daily")) + return token_t(token_t::TOK_DAILY); + else if (term == _("years")) + return token_t(token_t::TOK_YEARS); + else if (term == _("quarters")) + return token_t(token_t::TOK_QUARTERS); + else if (term == _("months")) + return token_t(token_t::TOK_MONTHS); + else if (term == _("weeks")) + return token_t(token_t::TOK_WEEKS); + else if (term == _("days")) + return token_t(token_t::TOK_DAYS); + } + else { + token_t::expected('\0', term[0]); + begin = ++start; + } + } else { + token_t::expected('\0', *begin); + } + + return token_t(token_t::UNKNOWN); +} + +void date_parser_t::lexer_t::token_t::unexpected() +{ + kind_t prev_kind = kind; + + kind = UNKNOWN; + + switch (prev_kind) { + case END_REACHED: + throw_(date_error, _("Unexpected end of expression")); + default: + throw_(date_error, _("Unexpected token '%1'") << to_string()); + } +} + +void date_parser_t::lexer_t::token_t::expected(char wanted, char c) +{ + if (c == '\0' || c == -1) { + if (wanted == '\0' || wanted == -1) + throw_(date_error, _("Unexpected end")); + else + throw_(date_error, _("Missing '%1'") << wanted); + } else { + if (wanted == '\0' || wanted == -1) + throw_(date_error, _("Invalid char '%1'") << c); + else + throw_(date_error, _("Invalid char '%1' (wanted '%2')") << c << wanted); + } +} + +date_interval_t date_parser_t::parse_date_expr() +{ + date_interval_t interval; + + return interval; +} + namespace { typedef std::map datetime_io_map; typedef std::map date_io_map; @@ -880,4 +1082,90 @@ void times_shutdown() is_initialized = false; } } + +void show_period_tokens(std::ostream& out, const string& arg) +{ + date_parser_t::lexer_t lexer(arg.begin(), arg.end()); + + date_parser_t::lexer_t::token_t token; + do { + token = lexer.next_token(); + out << _("token: ") << token.to_string() << std::endl; + } + while (token.kind != date_parser_t::lexer_t::token_t::END_REACHED); +} + +void analyze_period(std::ostream& out, const string& arg) +{ + date_parser_t date_parser(arg); + + date_interval_t interval = date_parser.parse(); + + out << _("global details => ") << std::endl << std::endl; + + if (interval.start) + out << _(" start: ") << format_date(*interval.start) << std::endl; + else + out << _(" start: TODAY: ") << format_date(CURRENT_DATE()) << std::endl; + if (interval.finish) + out << _(" finish: ") << format_date(*interval.finish) << std::endl; + + if (interval.skip_duration) + out << _(" skip: ") << *interval.skip_duration << std::endl; + if (interval.factor) + out << _(" factor: ") << interval.factor << std::endl; + if (interval.duration) + out << _("duration: ") << *interval.duration << std::endl; + + if (interval.find_period(interval.start ? + *interval.start : CURRENT_DATE())) { + out << std::endl + << _("after finding first period => ") << std::endl + << std::endl; + + if (interval.start) + out << _(" start: ") << format_date(*interval.start) << std::endl; + if (interval.finish) + out << _(" finish: ") << format_date(*interval.finish) << std::endl; + + if (interval.skip_duration) + out << _(" skip: ") << *interval.skip_duration << std::endl; + if (interval.factor) + out << _(" factor: ") << interval.factor << std::endl; + if (interval.duration) + out << _("duration: ") << *interval.duration << std::endl; + + out << std::endl; + + for (int i = 0; i < 20 && interval; i++, ++interval) { + out << std::right; + out.width(2); + + out << i << "): " << format_date(*interval.start); + if (interval.end_of_duration) + out << " -- " << format_date(*interval.inclusive_end()); + out << std::endl; + + if (! interval.skip_duration) + break; + } + } +} + } // namespace ledger + +#if defined(TIMES_HARNESS) + +int main(int argc, char *argv[]) +{ + if (argc > 1) { + ledger::times_initialize(); + ledger::analyze_period(std::cout, argv[1]); + ledger::times_shutdown(); + } else { + std::cerr << "Usage: times " << std::endl; + } + return 0; +} + +#endif // TIMES_HARNESS diff --git a/src/times.h b/src/times.h index c50b0366..b13f48b1 100644 --- a/src/times.h +++ b/src/times.h @@ -181,6 +181,105 @@ private: #endif // HAVE_BOOST_SERIALIZATION }; +class date_specifier_t +{ + friend class date_parser_t; + +#if 0 + typedef date_t::year_type year_type; +#else + typedef unsigned short year_type; +#endif + typedef date_t::month_type month_type; + typedef date_t::day_type day_type; + typedef date_t::day_of_week_type day_of_week_type; + + optional year; + optional month; + optional day; + optional wday; + +public: + date_specifier_t() {} + date_specifier_t(const date_t& date, const date_traits_t& traits) { + if (traits.has_year) + year = date.year(); + if (traits.has_month) + month = date.month(); + if (traits.has_day) + day = date.day(); + } + + date_t begin(const optional& current_year = none) const; + date_t end(const optional& current_year = none) const; + + bool is_within(const date_t& date, + const optional& current_year = none) const { + return date >= begin(current_year) && date < end(current_year); + } + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template + void serialize(Archive& ar, const unsigned int /* version */) { + ar & year; + ar & month; + ar & day; + ar & wday; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +class date_range_t +{ + friend class date_parser_t; + + optional range_begin; + optional range_end; + +public: + optional + begin(const optional& current_year = none) const { + if (range_begin) + return range_begin->begin(current_year); + else + return none; + } + optional + end(const optional& current_year = none) const { + if (range_end) + return range_end->end(current_year); + else + return none; + } + + bool is_within(const date_t& date, + const optional& current_year = none) const { + optional b = begin(current_year); + optional e = end(current_year); + bool after_begin = b ? date >= *b : true; + bool before_end = e ? date < *e : true; + return after_begin && before_end; + } + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template + void serialize(Archive& ar, const unsigned int /* version */) { + ar & range_begin; + ar & range_end; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + struct date_duration_t { enum skip_quantum_t { @@ -247,6 +346,45 @@ private: #endif // HAVE_BOOST_SERIALIZATION }; +class date_specifier_or_range_t +{ + typedef variant value_type; + + value_type specifier_or_range; + +public: + optional + begin(const optional& current_year = none) const { + if (specifier_or_range.type() == typeid(date_specifier_t)) + return boost::get(specifier_or_range).begin(current_year); + else if (specifier_or_range.type() == typeid(date_range_t)) + return boost::get(specifier_or_range).begin(current_year); + else + return none; + } + optional + end(const optional& current_year = none) const { + if (specifier_or_range.type() == typeid(date_specifier_t)) + return boost::get(specifier_or_range).end(current_year); + else if (specifier_or_range.type() == typeid(date_range_t)) + return boost::get(specifier_or_range).end(current_year); + else + return none; + } + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template + void serialize(Archive& ar, const unsigned int /* version */) { + ar & specifier_or_range; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + class date_interval_t : public equality_comparable { public: @@ -343,9 +481,198 @@ private: #endif // HAVE_BOOST_SERIALIZATION }; +class date_parser_t +{ + friend void show_period_tokens(std::ostream& out, const string& arg); + + class lexer_t + { + friend class date_parser_t; + + string::const_iterator begin; + string::const_iterator end; + + public: + struct token_t + { + enum kind_t { + UNKNOWN, + + TOK_DATE, + TOK_INT, + TOK_SLASH, + TOK_DASH, + TOK_DOT, + + TOK_A_YEAR, + TOK_A_MONTH, + TOK_A_DAY, + TOK_A_WDAY, + + TOK_SINCE, + TOK_UNTIL, + TOK_IN, + TOK_THIS, + TOK_NEXT, + TOK_LAST, + + TOK_YEAR, + TOK_QUARTER, + TOK_MONTH, + TOK_WEEK, + TOK_DAY, + + TOK_YEARLY, + TOK_QUARTERLY, + TOK_BIMONTHLY, + TOK_MONTHLY, + TOK_BIWEEKLY, + TOK_WEEKLY, + TOK_DAILY, + + TOK_YEARS, + TOK_QUARTERS, + TOK_MONTHS, + TOK_WEEKS, + TOK_DAYS, + + END_REACHED + + } kind; + + typedef variant content_t; + + optional value; + + explicit token_t(kind_t _kind = UNKNOWN, + const optional& _value = none) + : kind(_kind), value(_value) { + TRACE_CTOR(date_parser_t::lexer_t::token_t, ""); + } + token_t(const token_t& tok) + : kind(tok.kind), value(tok.value) { + TRACE_CTOR(date_parser_t::lexer_t::token_t, "copy"); + } + ~token_t() throw() { + TRACE_DTOR(date_parser_t::lexer_t::token_t); + } + + token_t& operator=(const token_t& tok) { + if (this != &tok) { + kind = tok.kind; + value = tok.value; + } + return *this; + } + + operator bool() const { + return kind != END_REACHED; + } + + string to_string() const { + switch (kind) { + case UNKNOWN: return "UNKNOWN"; + case TOK_DATE: return "TOK_DATE"; + case TOK_INT: return "TOK_INT"; + case TOK_SLASH: return "TOK_SLASH"; + case TOK_DASH: return "TOK_DASH"; + case TOK_DOT: return "TOK_DOT"; + case TOK_A_YEAR: return "TOK_A_YEAR"; + case TOK_A_MONTH: return "TOK_A_MONTH"; + case TOK_A_DAY: return "TOK_A_DAY"; + case TOK_A_WDAY: return "TOK_A_WDAY"; + case TOK_SINCE: return "TOK_SINCE"; + case TOK_UNTIL: return "TOK_UNTIL"; + case TOK_IN: return "TOK_IN"; + case TOK_THIS: return "TOK_THIS"; + case TOK_NEXT: return "TOK_NEXT"; + case TOK_LAST: return "TOK_LAST"; + case TOK_YEAR: return "TOK_YEAR"; + case TOK_QUARTER: return "TOK_QUARTER"; + case TOK_MONTH: return "TOK_MONTH"; + case TOK_WEEK: return "TOK_WEEK"; + case TOK_DAY: return "TOK_DAY"; + case TOK_YEARLY: return "TOK_YEARLY"; + case TOK_QUARTERLY: return "TOK_QUARTERLY"; + case TOK_BIMONTHLY: return "TOK_BIMONTHLY"; + case TOK_MONTHLY: return "TOK_MONTHLY"; + case TOK_BIWEEKLY: return "TOK_BIWEEKLY"; + case TOK_WEEKLY: return "TOK_WEEKLY"; + case TOK_DAILY: return "TOK_DAILY"; + case TOK_YEARS: return "TOK_YEARS"; + case TOK_QUARTERS: return "TOK_QUARTERS"; + case TOK_MONTHS: return "TOK_MONTHS"; + case TOK_WEEKS: return "TOK_WEEKS"; + case TOK_DAYS: return "TOK_DAYS"; + case END_REACHED: return "END_REACHED"; + } + assert(false); + return empty_string; + } + + void unexpected(); + static void expected(char wanted, char c = '\0'); + }; + + token_t token_cache; + + lexer_t(string::const_iterator _begin, + string::const_iterator _end) + : begin(_begin), end(_end) + { + TRACE_CTOR(date_parser_t::lexer_t, ""); + } + lexer_t(const lexer_t& lexer) + : begin(lexer.begin), end(lexer.end), + token_cache(lexer.token_cache) + { + TRACE_CTOR(date_parser_t::lexer_t, "copy"); + } + ~lexer_t() throw() { + TRACE_DTOR(date_parser_t::lexer_t); + } + + token_t next_token(); + void push_token(token_t tok) { + assert(token_cache.kind == token_t::UNKNOWN); + token_cache = tok; + } + token_t peek_token() { + if (token_cache.kind == token_t::UNKNOWN) + token_cache = next_token(); + return token_cache; + } + }; + + string arg; + lexer_t lexer; + + date_interval_t parse_date_expr(); + +public: + date_parser_t(const string& _arg) + : arg(_arg), lexer(arg.begin(), arg.end()) { + TRACE_CTOR(date_parser_t, ""); + } + date_parser_t(const date_parser_t& parser) + : arg(parser.arg), lexer(parser.lexer) { + TRACE_CTOR(date_parser_t, "copy"); + } + ~date_parser_t() throw() { + TRACE_DTOR(date_parser_t); + } + + date_interval_t parse() { + return date_interval_t(); + } +}; + void times_initialize(); void times_shutdown(); +void show_period_tokens(std::ostream& out, const string& arg); +void analyze_period(std::ostream& out, const string& arg); + std::ostream& operator<<(std::ostream& out, const date_duration_t& duration); } // namespace ledger -- cgit v1.2.3 From fe9af7ace7a9b25a3eefe481b6b7a38ad2f758ed Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Wed, 18 Nov 2009 01:27:34 -0500 Subject: Added a "range" member to date_interval_t This is used to define the beginning/ending ranges of the time period, before it becomes fixed (by calling stabilize()) and then sets the values of start and end. --- src/times.cc | 19 ++++++++----------- src/times.h | 45 ++++++++++++++++++++++++++------------------- 2 files changed, 34 insertions(+), 30 deletions(-) (limited to 'src') diff --git a/src/times.cc b/src/times.cc index 3070249c..076cef3d 100644 --- a/src/times.cc +++ b/src/times.cc @@ -194,7 +194,7 @@ namespace { std::vector > readers; date_t parse_date_mask_routine(const char * date_str, date_io_t& io, - optional year, + optional_year year, date_traits_t * traits = NULL) { date_t when; @@ -231,8 +231,7 @@ namespace { return when; } - date_t parse_date_mask(const char * date_str, - optional year, + date_t parse_date_mask(const char * date_str, optional_year year, date_traits_t * traits = NULL) { if (input_date_io.get()) { @@ -305,7 +304,7 @@ string_to_month_of_year(const std::string& str) return none; } -datetime_t parse_datetime(const char * str, optional) +datetime_t parse_datetime(const char * str, optional_year) { datetime_t when = input_datetime_io->parse(str); if (when.is_not_a_date_time()) @@ -313,13 +312,12 @@ datetime_t parse_datetime(const char * str, optional) return when; } -date_t parse_date(const char * str, optional current_year) +date_t parse_date(const char * str, optional_year current_year) { return parse_date_mask(str, current_year); } -date_t -date_specifier_t::begin(const optional& current_year) const +date_t date_specifier_t::begin(const optional_year& current_year) const { assert(year || current_year); @@ -340,8 +338,7 @@ date_specifier_t::begin(const optional& current_year) const static_cast(the_day)); } -date_t -date_specifier_t::end(const optional& current_year) const +date_t date_specifier_t::end(const optional_year& current_year) const { if (day || wday) return begin(current_year) + gregorian::days(1); @@ -418,8 +415,8 @@ void date_interval_t::stabilize(const optional& date) // 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 initial_start = start; - optional initial_finish = finish; + optional initial_start = start ? start : begin(date->year()); + optional initial_finish = finish ? finish : end(date->year()); #if defined(DEBUG_ON) if (initial_start) diff --git a/src/times.h b/src/times.h index b13f48b1..94ae4752 100644 --- a/src/times.h +++ b/src/times.h @@ -84,19 +84,19 @@ string_to_day_of_week(const std::string& str); optional string_to_month_of_year(const std::string& str); -datetime_t parse_datetime(const char * str, - optional current_year = none); +typedef optional optional_year; + +datetime_t parse_datetime(const char * str, optional_year current_year = none); inline datetime_t parse_datetime(const std::string& str, - optional current_year = none) { + optional_year current_year = none) { return parse_datetime(str.c_str(), current_year); } -date_t parse_date(const char * str, - optional current_year = none); +date_t parse_date(const char * str, optional_year current_year = none); inline date_t parse_date(const std::string& str, - optional current_year = none) { + optional_year current_year = none) { return parse_date(str.c_str(), current_year); } @@ -210,11 +210,11 @@ public: day = date.day(); } - date_t begin(const optional& current_year = none) const; - date_t end(const optional& current_year = none) const; + date_t begin(const optional_year& current_year = none) const; + date_t end(const optional_year& current_year = none) const; bool is_within(const date_t& date, - const optional& current_year = none) const { + const optional_year& current_year = none) const { return date >= begin(current_year) && date < end(current_year); } @@ -242,15 +242,13 @@ class date_range_t optional range_end; public: - optional - begin(const optional& current_year = none) const { + optional begin(const optional_year& current_year = none) const { if (range_begin) return range_begin->begin(current_year); else return none; } - optional - end(const optional& current_year = none) const { + optional end(const optional_year& current_year = none) const { if (range_end) return range_end->end(current_year); else @@ -258,7 +256,7 @@ public: } bool is_within(const date_t& date, - const optional& current_year = none) const { + const optional_year& current_year = none) const { optional b = begin(current_year); optional e = end(current_year); bool after_begin = b ? date >= *b : true; @@ -353,8 +351,7 @@ class date_specifier_or_range_t value_type specifier_or_range; public: - optional - begin(const optional& current_year = none) const { + optional begin(const optional_year& current_year = none) const { if (specifier_or_range.type() == typeid(date_specifier_t)) return boost::get(specifier_or_range).begin(current_year); else if (specifier_or_range.type() == typeid(date_range_t)) @@ -362,8 +359,7 @@ public: else return none; } - optional - end(const optional& current_year = none) const { + optional end(const optional_year& current_year = none) const { if (specifier_or_range.type() == typeid(date_specifier_t)) return boost::get(specifier_or_range).end(current_year); else if (specifier_or_range.type() == typeid(date_range_t)) @@ -393,6 +389,8 @@ public: static date_t subtract_duration(const date_t& date, const date_duration_t& duration); + optional range; + optional start; // the real start, after adjustment optional finish; // the real end, likewise bool aligned; @@ -410,7 +408,8 @@ public: parse(str); } date_interval_t(const date_interval_t& other) - : start(other.start), + : range(other.range), + start(other.start), finish(other.finish), aligned(other.aligned), skip_duration(other.skip_duration), @@ -433,6 +432,13 @@ public: return is_valid(); } + optional begin(const optional_year& current_year = none) const { + return start ? start : (range ? range->begin(current_year) : none); + } + optional end(const optional_year& current_year = none) const { + return finish ? finish : (range ? range->end(current_year) : none); + } + void parse(std::istream& in); void parse(const string& str) { @@ -469,6 +475,7 @@ private: template void serialize(Archive& ar, const unsigned int /* version */) { + ar & range; ar & start; ar & finish; ar & aligned; -- cgit v1.2.3 From 7fe369eb492f737f570d0ccf4aaf5db68f900279 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Wed, 18 Nov 2009 04:11:14 -0500 Subject: The new period parser is implemented, but untested --- src/precmd.cc | 53 +--- src/times.cc | 959 +++++++++++++++++++++++++++++++++++++++------------------- src/times.h | 500 ++++++++++++++---------------- 3 files changed, 883 insertions(+), 629 deletions(-) (limited to 'src') diff --git a/src/precmd.cc b/src/precmd.cc index c4d7cbaa..0e024a39 100644 --- a/src/precmd.cc +++ b/src/precmd.cc @@ -165,57 +165,12 @@ value_t period_command(call_scope_t& args) report_t& report(find_scope(args)); std::ostream& out(report.output_stream); + show_period_tokens(out, arg); + out << std::endl; + date_interval_t interval(arg); + interval.dump(out); - out << _("global details => ") << std::endl << std::endl; - - if (interval.start) - out << _(" start: ") << format_date(*interval.start) << std::endl; - else - out << _(" start: TODAY: ") << format_date(CURRENT_DATE()) << std::endl; - if (interval.finish) - out << _(" finish: ") << format_date(*interval.finish) << std::endl; - - if (interval.skip_duration) - out << _(" skip: ") << *interval.skip_duration << std::endl; - if (interval.factor) - out << _(" factor: ") << interval.factor << std::endl; - if (interval.duration) - out << _("duration: ") << *interval.duration << std::endl; - - if (interval.find_period(interval.start ? - *interval.start : CURRENT_DATE())) { - out << std::endl - << _("after finding first period => ") << std::endl - << std::endl; - - if (interval.start) - out << _(" start: ") << format_date(*interval.start) << std::endl; - if (interval.finish) - out << _(" finish: ") << format_date(*interval.finish) << std::endl; - - if (interval.skip_duration) - out << _(" skip: ") << *interval.skip_duration << std::endl; - if (interval.factor) - out << _(" factor: ") << interval.factor << std::endl; - if (interval.duration) - out << _("duration: ") << *interval.duration << std::endl; - - out << std::endl; - - for (int i = 0; i < 20 && interval; i++, ++interval) { - out << std::right; - out.width(2); - - out << i << "): " << format_date(*interval.start); - if (interval.end_of_duration) - out << " -- " << format_date(*interval.inclusive_end()); - out << std::endl; - - if (! interval.skip_duration) - break; - } - } return NULL_VALUE; } diff --git a/src/times.cc b/src/times.cc index 076cef3d..97fc3ab8 100644 --- a/src/times.cc +++ b/src/times.cc @@ -361,6 +361,8 @@ std::ostream& operator<<(std::ostream& out, out << duration.length << " week(s)"; else if (duration.quantum == date_duration_t::MONTHS) out << duration.length << " month(s)"; + else if (duration.quantum == date_duration_t::QUARTERS) + out << duration.length << " quarter(s)"; else { assert(duration.quantum == date_duration_t::YEARS); out << duration.length << " year(s)"; @@ -368,6 +370,537 @@ std::ostream& operator<<(std::ostream& out, return out; } +class date_parser_t +{ + friend void show_period_tokens(std::ostream& out, const string& arg); + + class lexer_t + { + friend class date_parser_t; + + string::const_iterator begin; + string::const_iterator end; + + public: + struct token_t + { + enum kind_t { + UNKNOWN, + + TOK_DATE, + TOK_INT, + TOK_SLASH, + TOK_DASH, + TOK_DOT, + + TOK_A_YEAR, + TOK_A_MONTH, + TOK_A_WDAY, + + TOK_SINCE, + TOK_UNTIL, + TOK_IN, + TOK_THIS, + TOK_NEXT, + TOK_LAST, + TOK_EVERY, + + TOK_TODAY, + TOK_TOMORROW, + TOK_YESTERDAY, + + TOK_YEAR, + TOK_QUARTER, + TOK_MONTH, + TOK_WEEK, + TOK_DAY, + + TOK_YEARLY, + TOK_QUARTERLY, + TOK_BIMONTHLY, + TOK_MONTHLY, + TOK_BIWEEKLY, + TOK_WEEKLY, + TOK_DAILY, + + TOK_YEARS, + TOK_QUARTERS, + TOK_MONTHS, + TOK_WEEKS, + TOK_DAYS, + + END_REACHED + + } kind; + + typedef variant content_t; + + optional value; + + explicit token_t(kind_t _kind = UNKNOWN, + const optional& _value = none) + : kind(_kind), value(_value) { + TRACE_CTOR(date_parser_t::lexer_t::token_t, ""); + } + token_t(const token_t& tok) + : kind(tok.kind), value(tok.value) { + TRACE_CTOR(date_parser_t::lexer_t::token_t, "copy"); + } + ~token_t() throw() { + TRACE_DTOR(date_parser_t::lexer_t::token_t); + } + + token_t& operator=(const token_t& tok) { + if (this != &tok) { + kind = tok.kind; + value = tok.value; + } + return *this; + } + + operator bool() const { + return kind != END_REACHED; + } + + string to_string() const { + switch (kind) { + case UNKNOWN: return "UNKNOWN"; + case TOK_DATE: return "TOK_DATE"; + case TOK_INT: return "TOK_INT"; + case TOK_SLASH: return "TOK_SLASH"; + case TOK_DASH: return "TOK_DASH"; + case TOK_DOT: return "TOK_DOT"; + case TOK_A_YEAR: return "TOK_A_YEAR"; + case TOK_A_MONTH: return "TOK_A_MONTH"; + case TOK_A_WDAY: return "TOK_A_WDAY"; + case TOK_SINCE: return "TOK_SINCE"; + case TOK_UNTIL: return "TOK_UNTIL"; + case TOK_IN: return "TOK_IN"; + case TOK_THIS: return "TOK_THIS"; + case TOK_NEXT: return "TOK_NEXT"; + case TOK_LAST: return "TOK_LAST"; + case TOK_EVERY: return "TOK_EVERY"; + case TOK_TODAY: return "TOK_EVERY"; + case TOK_TOMORROW: return "TOK_TOMORROW"; + case TOK_YESTERDAY: return "TOK_YESTERDAY"; + case TOK_YEAR: return "TOK_YEAR"; + case TOK_QUARTER: return "TOK_QUARTER"; + case TOK_MONTH: return "TOK_MONTH"; + case TOK_WEEK: return "TOK_WEEK"; + case TOK_DAY: return "TOK_DAY"; + case TOK_YEARLY: return "TOK_YEARLY"; + case TOK_QUARTERLY: return "TOK_QUARTERLY"; + case TOK_BIMONTHLY: return "TOK_BIMONTHLY"; + case TOK_MONTHLY: return "TOK_MONTHLY"; + case TOK_BIWEEKLY: return "TOK_BIWEEKLY"; + case TOK_WEEKLY: return "TOK_WEEKLY"; + case TOK_DAILY: return "TOK_DAILY"; + case TOK_YEARS: return "TOK_YEARS"; + case TOK_QUARTERS: return "TOK_QUARTERS"; + case TOK_MONTHS: return "TOK_MONTHS"; + case TOK_WEEKS: return "TOK_WEEKS"; + case TOK_DAYS: return "TOK_DAYS"; + case END_REACHED: return "END_REACHED"; + } + assert(false); + return empty_string; + } + + void unexpected(); + static void expected(char wanted, char c = '\0'); + }; + + token_t token_cache; + + lexer_t(string::const_iterator _begin, + string::const_iterator _end) + : begin(_begin), end(_end) + { + TRACE_CTOR(date_parser_t::lexer_t, ""); + } + lexer_t(const lexer_t& lexer) + : begin(lexer.begin), end(lexer.end), + token_cache(lexer.token_cache) + { + TRACE_CTOR(date_parser_t::lexer_t, "copy"); + } + ~lexer_t() throw() { + TRACE_DTOR(date_parser_t::lexer_t); + } + + token_t next_token(); + void push_token(token_t tok) { + assert(token_cache.kind == token_t::UNKNOWN); + token_cache = tok; + } + token_t peek_token() { + if (token_cache.kind == token_t::UNKNOWN) + token_cache = next_token(); + return token_cache; + } + }; + + string arg; + lexer_t lexer; + +public: + date_parser_t(const string& _arg) + : arg(_arg), lexer(arg.begin(), arg.end()) { + TRACE_CTOR(date_parser_t, ""); + } + date_parser_t(const date_parser_t& parser) + : arg(parser.arg), lexer(parser.lexer) { + TRACE_CTOR(date_parser_t, "copy"); + } + ~date_parser_t() throw() { + TRACE_DTOR(date_parser_t); + } + + date_interval_t parse(); + +private: + void determine_when(lexer_t::token_t& tok, date_specifier_t& specifier); +}; + +void date_parser_t::determine_when(date_parser_t::lexer_t::token_t& tok, + date_specifier_t& specifier) +{ + switch (tok.kind) { + case lexer_t::token_t::TOK_DATE: + specifier = boost::get(*tok.value); + break; + + case lexer_t::token_t::TOK_INT: + specifier.day = + date_specifier_t::day_type(boost::get(*tok.value)); + break; + case lexer_t::token_t::TOK_A_YEAR: + specifier.year = boost::get(*tok.value); + break; + case lexer_t::token_t::TOK_A_MONTH: + specifier.month = + date_specifier_t::month_type + (boost::get(*tok.value)); + break; + case lexer_t::token_t::TOK_A_WDAY: + specifier.wday = + date_specifier_t::day_of_week_type + (boost::get(*tok.value)); + break; + + default: + tok.unexpected(); + break; + } +} + +date_interval_t date_parser_t::parse() +{ + optional since_specifier; + optional until_specifier; + optional inclusion_specifier; + + date_interval_t period; + date_t today = CURRENT_DATE(); + bool end_inclusive = false; + + for (lexer_t::token_t tok = lexer.next_token(); + tok.kind != lexer_t::token_t::END_REACHED; + tok = lexer.next_token()) { + switch (tok.kind) { +#if 0 + case lexer_t::token_t::TOK_INT: + // jww (2009-11-18): NYI + assert(! "Need to allow for expressions like \"4 months ago\""); + tok.unexpected(); + break; +#endif + + case lexer_t::token_t::TOK_DATE: + if (! inclusion_specifier) + inclusion_specifier = date_specifier_t(); + determine_when(tok, *inclusion_specifier); + break; + + case lexer_t::token_t::TOK_INT: + if (! inclusion_specifier) + inclusion_specifier = date_specifier_t(); + determine_when(tok, *inclusion_specifier); + break; + + case lexer_t::token_t::TOK_A_YEAR: + if (! inclusion_specifier) + inclusion_specifier = date_specifier_t(); + determine_when(tok, *inclusion_specifier); + break; + + case lexer_t::token_t::TOK_A_MONTH: + if (! inclusion_specifier) + inclusion_specifier = date_specifier_t(); + determine_when(tok, *inclusion_specifier); + break; + + case lexer_t::token_t::TOK_A_WDAY: + if (! inclusion_specifier) + inclusion_specifier = date_specifier_t(); + determine_when(tok, *inclusion_specifier); + break; + + case lexer_t::token_t::TOK_DASH: + if (inclusion_specifier) { + since_specifier = inclusion_specifier; + until_specifier = date_specifier_t(); + inclusion_specifier = none; + + tok = lexer.next_token(); + determine_when(tok, *until_specifier); + + // The dash operator is special: it has an _inclusive_ end. + end_inclusive = true; + } else { + tok.unexpected(); + } + break; + + case lexer_t::token_t::TOK_SINCE: + if (since_specifier) { + tok.unexpected(); + } else { + since_specifier = date_specifier_t(); + tok = lexer.next_token(); + determine_when(tok, *since_specifier); + } + break; + + case lexer_t::token_t::TOK_UNTIL: + if (until_specifier) { + tok.unexpected(); + } else { + until_specifier = date_specifier_t(); + tok = lexer.next_token(); + determine_when(tok, *until_specifier); + } + break; + + case lexer_t::token_t::TOK_IN: + if (inclusion_specifier) { + tok.unexpected(); + } else { + inclusion_specifier = date_specifier_t(); + tok = lexer.next_token(); + determine_when(tok, *inclusion_specifier); + } + break; + + case lexer_t::token_t::TOK_THIS: + case lexer_t::token_t::TOK_NEXT: + case lexer_t::token_t::TOK_LAST: { + int8_t adjust = 0; + if (tok.kind == lexer_t::token_t::TOK_NEXT) + adjust = 1; + else if (tok.kind == lexer_t::token_t::TOK_LAST) + adjust = -1; + + tok = lexer.next_token(); + switch (tok.kind) { + case lexer_t::token_t::TOK_INT: + // jww (2009-11-18): Allow things like "last 5 weeks" + assert(! "Need to allow for expressions like \"last 5 weeks\""); + tok.unexpected(); + break; + + case lexer_t::token_t::TOK_A_MONTH: { + inclusion_specifier = date_specifier_t(); + determine_when(tok, *inclusion_specifier); + + date_t temp(today.year(), *inclusion_specifier->month, 1); + temp += gregorian::years(adjust); + inclusion_specifier = + date_specifier_t(static_cast(temp.year()), + temp.month()); + break; + } + + case lexer_t::token_t::TOK_A_WDAY: { + inclusion_specifier = date_specifier_t(); + determine_when(tok, *inclusion_specifier); + + date_t temp = + date_duration_t::find_nearest(today, date_duration_t::WEEKS); + while (temp.day_of_week() != inclusion_specifier->wday) + temp += gregorian::days(1); + temp += gregorian::days(7 * adjust); + inclusion_specifier = date_specifier_t(temp); + break; + } + + case lexer_t::token_t::TOK_YEAR: { + date_t temp(today); + temp += gregorian::years(adjust); + inclusion_specifier = + date_specifier_t(static_cast(temp.year())); + break; + } + + case lexer_t::token_t::TOK_QUARTER: { + date_t temp = + date_duration_t::find_nearest(today, date_duration_t::QUARTERS); + temp += gregorian::months(3 * adjust); + inclusion_specifier = + date_specifier_t(static_cast(temp.year()), + temp.month()); + period.duration = date_duration_t(date_duration_t::QUARTERS, 1); + break; + } + + case lexer_t::token_t::TOK_MONTH: { + date_t temp(today); + temp += gregorian::months(adjust); + inclusion_specifier = + date_specifier_t(static_cast(temp.year()), + temp.month()); + break; + } + + case lexer_t::token_t::TOK_WEEK: { + date_t temp = + date_duration_t::find_nearest(today, date_duration_t::WEEKS); + temp += gregorian::days(7 * adjust); + inclusion_specifier = date_specifier_t(today); + period.duration = date_duration_t(date_duration_t::WEEKS, 1); + break; + } + + case lexer_t::token_t::TOK_DAY: { + date_t temp(today); + temp += gregorian::days(adjust); + inclusion_specifier = date_specifier_t(temp); + break; + } + + default: + tok.unexpected(); + break; + } + break; + } + + case lexer_t::token_t::TOK_TODAY: + inclusion_specifier = date_specifier_t(today); + break; + case lexer_t::token_t::TOK_TOMORROW: + inclusion_specifier = date_specifier_t(today + gregorian::days(1)); + break; + case lexer_t::token_t::TOK_YESTERDAY: + inclusion_specifier = date_specifier_t(today - gregorian::days(1)); + break; + + case lexer_t::token_t::TOK_EVERY: + tok = lexer.next_token(); + if (tok == lexer_t::token_t::TOK_INT) { + int quantity = boost::get(*tok.value); + tok = lexer.next_token(); + switch (tok.kind) { + case lexer_t::token_t::TOK_YEARS: + period.skip_duration = date_duration_t(date_duration_t::YEARS, quantity); + break; + case lexer_t::token_t::TOK_QUARTERS: + period.skip_duration = date_duration_t(date_duration_t::QUARTERS, quantity); + break; + case lexer_t::token_t::TOK_MONTHS: + period.skip_duration = date_duration_t(date_duration_t::MONTHS, quantity); + break; + case lexer_t::token_t::TOK_WEEKS: + period.skip_duration = date_duration_t(date_duration_t::WEEKS, quantity); + break; + case lexer_t::token_t::TOK_DAYS: + period.skip_duration = date_duration_t(date_duration_t::DAYS, quantity); + break; + default: + tok.unexpected(); + break; + } + } else { + switch (tok.kind) { + case lexer_t::token_t::TOK_YEAR: + period.skip_duration = date_duration_t(date_duration_t::YEARS, 1); + break; + case lexer_t::token_t::TOK_QUARTER: + period.skip_duration = date_duration_t(date_duration_t::QUARTERS, 1); + break; + case lexer_t::token_t::TOK_MONTH: + period.skip_duration = date_duration_t(date_duration_t::MONTHS, 1); + break; + case lexer_t::token_t::TOK_WEEK: + period.skip_duration = date_duration_t(date_duration_t::WEEKS, 1); + break; + case lexer_t::token_t::TOK_DAY: + period.skip_duration = date_duration_t(date_duration_t::DAYS, 1); + break; + default: + tok.unexpected(); + break; + } + } + break; + + case lexer_t::token_t::TOK_YEARLY: + period.skip_duration = date_duration_t(date_duration_t::YEARS, 1); + break; + case lexer_t::token_t::TOK_QUARTERLY: + period.skip_duration = date_duration_t(date_duration_t::QUARTERS, 1); + break; + case lexer_t::token_t::TOK_BIMONTHLY: + period.skip_duration = date_duration_t(date_duration_t::MONTHS, 2); + break; + case lexer_t::token_t::TOK_MONTHLY: + period.skip_duration = date_duration_t(date_duration_t::MONTHS, 1); + break; + case lexer_t::token_t::TOK_BIWEEKLY: + period.skip_duration = date_duration_t(date_duration_t::WEEKS, 2); + break; + case lexer_t::token_t::TOK_WEEKLY: + period.skip_duration = date_duration_t(date_duration_t::WEEKS, 1); + break; + case lexer_t::token_t::TOK_DAILY: + period.skip_duration = date_duration_t(date_duration_t::DAYS, 1); + break; + + default: + tok.unexpected(); + break; + } + } + + if (! period.duration && inclusion_specifier) + period.duration = inclusion_specifier->implied_duration(); + + if (since_specifier || until_specifier) { + date_range_t range(since_specifier, until_specifier); + range.end_inclusive = end_inclusive; + + period.range = date_specifier_or_range_t(range); + } + else if (inclusion_specifier) { + period.range = date_specifier_or_range_t(*inclusion_specifier); + } + else { + /* otherwise, it's something like "monthly", with no date reference */ + } + + return period; +} + +void date_interval_t::parse(const string& str) +{ + date_parser_t parser(str); + *this = parser.parse(); +} + void date_interval_t::resolve_end() { if (start && ! end_of_duration) { @@ -395,6 +928,40 @@ void date_interval_t::resolve_end() } } +date_t date_duration_t::find_nearest(const date_t& date, skip_quantum_t skip) +{ + date_t result; + + switch (skip) { + case date_duration_t::YEARS: + result = date_t(date.year(), gregorian::Jan, 1); + break; + case date_duration_t::QUARTERS: + result = date_t(date.year(), date.month(), 1); + while (result.month() != gregorian::Jan && + result.month() != gregorian::Apr && + result.month() != gregorian::Jul && + result.month() != gregorian::Oct) + result -= gregorian::months(1); + break; + case date_duration_t::MONTHS: + result = date_t(date.year(), date.month(), 1); + break; + case date_duration_t::WEEKS: + result = date; + while (result.day_of_week() != start_of_week) + result -= gregorian::days(1); + break; + case date_duration_t::DAYS: + result = date; + break; + default: + assert(false); + break; + } + return result; +} + void date_interval_t::stabilize(const optional& date) { #if defined(DEBUG_ON) @@ -430,20 +997,15 @@ void date_interval_t::stabilize(const optional& date) date_t when = start ? *start : *date; if (duration->quantum == date_duration_t::MONTHS || + duration->quantum == date_duration_t::QUARTERS || duration->quantum == date_duration_t::YEARS) { - DEBUG("times.interval", "stabilize: monthly or yearly duration"); - - start = date_t(when.year(), gregorian::Jan, 1); + DEBUG("times.interval", + "stabilize: monthly, quarterly or yearly duration"); + start = date_duration_t::find_nearest(when, duration->quantum); } else { DEBUG("times.interval", "stabilize: daily or weekly duration"); - - start = date_t(when - gregorian::days(400)); - - if (duration->quantum == date_duration_t::WEEKS) { - // Move it to a Sunday - while (start->day_of_week() != start_of_week) - *start += gregorian::days(1); - } + start = date_duration_t::find_nearest(when - gregorian::days(400), + duration->quantum); } DEBUG("times.interval", @@ -475,6 +1037,14 @@ void date_interval_t::stabilize(const optional& date) DEBUG("times.interval", "stabilize: finish reset to initial finish"); } } + else if (range) { + start = range->begin(); + finish = range->end(); + + if (start && finish) + duration = date_duration_t(date_duration_t::DAYS, + static_cast((*finish - *start).days())); + } aligned = true; } @@ -546,7 +1116,7 @@ bool date_interval_t::find_period(const date_t& date) return true; } - scan = skip_duration->add(scan); + scan = skip_duration->add(scan); end_of_scan = duration->add(scan); } @@ -581,214 +1151,57 @@ date_interval_t& date_interval_t::operator++() return *this; } -namespace { - void parse_inclusion_specifier(const string& word, - date_t * begin, - date_t * end) - { - date_traits_t traits; - date_t when = parse_date_mask(word.c_str(), none, &traits); - - if (when.is_not_a_date()) - throw_(date_error, _("Could not parse date mask: %1") << word); - - if (begin) { - *begin = when; - - if (end) { - if (traits.has_day) - *end = *begin + gregorian::days(1); - else if (traits.has_month) - *end = *begin + gregorian::months(1); - else - *end = *begin + gregorian::years(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(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_finish = 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 finish; - bool parse_specifier = false; - - optional duration; - - assert(look_for_start || look_for_finish); - - if (word == _("year")) { - duration = date_duration_t(date_duration_t::YEARS, 1); - start = gregorian::date(start.year(), 1, 1); - } - else if (word == _("month")) { - duration = date_duration_t(date_duration_t::MONTHS, 1); - start = gregorian::date(start.year(), start.month(), 1); - } - else if (word == _("today") || word == _("day")) { - duration = date_duration_t(date_duration_t::DAYS, 1); - } - else { - parse_specifier = true; - } - - if (parse_specifier) - parse_inclusion_specifier(word, &start, &finish); - else - finish = duration->add(start); - - if (type == _("last") && duration) { - start = duration->subtract(start); - finish = duration->subtract(finish); - } - else if (type == _("next") && duration) { - start = duration->add(start); - finish = duration->add(finish); - } - - if (look_for_start && is_valid(start)) interval.start = start; - if (look_for_finish && is_valid(finish)) interval.finish = finish; - } -} - -void date_interval_t::parse(std::istream& in) +void date_interval_t::dump(std::ostream& out) { - string word; - - optional mon; - optional wday; - optional 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(word); - read_lower_word(in, word); - if (word == _("days")) - duration = date_duration_t(date_duration_t::DAYS, quantity); - else if (word == _("weeks")) - duration = date_duration_t(date_duration_t::WEEKS, quantity); - else if (word == _("months")) - duration = date_duration_t(date_duration_t::MONTHS, quantity); - else if (word == _("quarters")) - duration = date_duration_t(date_duration_t::MONTHS, 3 * quantity); - else if (word == _("years")) - duration = date_duration_t(date_duration_t::YEARS, quantity); - } - else if (word == _("day")) - duration = date_duration_t(date_duration_t::DAYS, 1); - else if (word == _("week")) - duration = date_duration_t(date_duration_t::WEEKS, 1); - else if (word == _("month")) - duration = date_duration_t(date_duration_t::MONTHS, 1); - else if (word == _("quarter")) - duration = date_duration_t(date_duration_t::MONTHS, 3); - else if (word == _("year")) - duration = date_duration_t(date_duration_t::YEARS, 1); - } - else if (word == _("daily")) - duration = date_duration_t(date_duration_t::DAYS, 1); - else if (word == _("weekly")) - duration = date_duration_t(date_duration_t::WEEKS, 1); - else if (word == _("biweekly")) - duration = date_duration_t(date_duration_t::WEEKS, 2); - else if (word == _("monthly")) - duration = date_duration_t(date_duration_t::MONTHS, 1); - else if (word == _("bimonthly")) - duration = date_duration_t(date_duration_t::MONTHS, 2); - else if (word == _("quarterly")) - duration = date_duration_t(date_duration_t::MONTHS, 3); - else if (word == _("yearly")) - duration = date_duration_t(date_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 - m = string_to_month_of_year(word)) { - mon = m; - } - else if (optional - d = string_to_day_of_week(word)) { - wday = d; - } - else if (all(word, is_digit())) { - year = lexical_cast(word); - } - else { - // otherwise, it should be an explicit date - date_t s, f; - parse_inclusion_specifier(word, &s, &f); - start = s; - finish = f; - } - } - - if (year || mon || wday) { - if (! start) - start = CURRENT_DATE(); - - if (wday) { - while (start->day_of_week() != *wday) - *start -= gregorian::days(1); - - if (! finish) - finish = *start + gregorian::days(1); - } else { - bool overwrite_finish = false; - - if (year) { - start = date_t(*year, 1, 1); - if (! finish) { - finish = *start + gregorian::years(1); - overwrite_finish = true; - } - } + out << _("--- Before stabilization ---") << std::endl; + + if (range) + out << _(" range: ") << range->to_string() << std::endl; + if (start) + out << _(" start: ") << format_date(*start) << std::endl; + if (finish) + out << _(" finish: ") << format_date(*finish) << std::endl; + + if (skip_duration) + out << _(" skip: ") << skip_duration->to_string() << std::endl; + if (factor) + out << _(" factor: ") << factor << std::endl; + if (duration) + out << _("duration: ") << duration->to_string() << std::endl; + + stabilize(begin()); + + out << std::endl + << _("--- After stabilization ---") << std::endl; + + if (range) + out << _(" range: ") << range->to_string() << std::endl; + if (start) + out << _(" start: ") << format_date(*start) << std::endl; + if (finish) + out << _(" finish: ") << format_date(*finish) << std::endl; + + if (skip_duration) + out << _(" skip: ") << skip_duration->to_string() << std::endl; + if (factor) + out << _(" factor: ") << factor << std::endl; + if (duration) + out << _("duration: ") << duration->to_string() << std::endl; + + out << std::endl + << _("--- Sample dates in range (max. 20) ---") << std::endl; + + for (int i = 0; i < 20 && *this; i++, ++*this) { + out << std::right; + out.width(2); + + out << (i + 1) << ": " << format_date(*start); + if (skip_duration) + out << " -- " << format_date(*inclusive_skip_end()); + out << std::endl; - if (mon) { - start = date_t(start->year(), *mon, 1); - if (! finish || overwrite_finish) - finish = *start + gregorian::months(1); - } - } + if (! skip_duration) + break; } } @@ -846,22 +1259,22 @@ date_parser_t::lexer_t::token_t date_parser_t::lexer_t::next_token() if (! term.empty()) { if (std::isdigit(term[0])) { - return token_t(term.length() == 4 ? - token_t::TOK_A_YEAR : token_t::TOK_INT, - token_t::content_t(lexical_cast(term))); + if (term.length() == 4) + return token_t(token_t::TOK_A_YEAR, + token_t::content_t + (lexical_cast(term))); + else + return token_t(token_t::TOK_INT, + token_t::content_t(lexical_cast(term))); } else if (std::isalpha(term[0])) { if (optional month = string_to_month_of_year(term)) { - date_specifier_t specifier; - specifier.month = static_cast(*month); - return token_t(token_t::TOK_A_MONTH, token_t::content_t(specifier)); + return token_t(token_t::TOK_A_MONTH, token_t::content_t(*month)); } else if (optional wday = string_to_day_of_week(term)) { - date_specifier_t specifier; - specifier.wday = static_cast(*wday); - return token_t(token_t::TOK_A_WDAY, token_t::content_t(specifier)); + return token_t(token_t::TOK_A_WDAY, token_t::content_t(*wday)); } else if (term == _("from") || term == _("since")) return token_t(token_t::TOK_SINCE); @@ -875,6 +1288,14 @@ date_parser_t::lexer_t::token_t date_parser_t::lexer_t::next_token() return token_t(token_t::TOK_NEXT); else if (term == _("last")) return token_t(token_t::TOK_LAST); + else if (term == _("every")) + return token_t(token_t::TOK_EVERY); + else if (term == _("today")) + return token_t(token_t::TOK_TODAY); + else if (term == _("tomorrow")) + return token_t(token_t::TOK_TOMORROW); + else if (term == _("yesterday")) + return token_t(token_t::TOK_YESTERDAY); else if (term == _("year")) return token_t(token_t::TOK_YEAR); else if (term == _("quarter")) @@ -950,13 +1371,6 @@ void date_parser_t::lexer_t::token_t::expected(char wanted, char c) } } -date_interval_t date_parser_t::parse_date_expr() -{ - date_interval_t interval; - - return interval; -} - namespace { typedef std::map datetime_io_map; typedef std::map date_io_map; @@ -1084,85 +1498,14 @@ void show_period_tokens(std::ostream& out, const string& arg) { date_parser_t::lexer_t lexer(arg.begin(), arg.end()); + out << _("--- Period expression tokens ---") << std::endl; + date_parser_t::lexer_t::token_t token; do { token = lexer.next_token(); - out << _("token: ") << token.to_string() << std::endl; + out << token.to_string() << std::endl; } while (token.kind != date_parser_t::lexer_t::token_t::END_REACHED); } -void analyze_period(std::ostream& out, const string& arg) -{ - date_parser_t date_parser(arg); - - date_interval_t interval = date_parser.parse(); - - out << _("global details => ") << std::endl << std::endl; - - if (interval.start) - out << _(" start: ") << format_date(*interval.start) << std::endl; - else - out << _(" start: TODAY: ") << format_date(CURRENT_DATE()) << std::endl; - if (interval.finish) - out << _(" finish: ") << format_date(*interval.finish) << std::endl; - - if (interval.skip_duration) - out << _(" skip: ") << *interval.skip_duration << std::endl; - if (interval.factor) - out << _(" factor: ") << interval.factor << std::endl; - if (interval.duration) - out << _("duration: ") << *interval.duration << std::endl; - - if (interval.find_period(interval.start ? - *interval.start : CURRENT_DATE())) { - out << std::endl - << _("after finding first period => ") << std::endl - << std::endl; - - if (interval.start) - out << _(" start: ") << format_date(*interval.start) << std::endl; - if (interval.finish) - out << _(" finish: ") << format_date(*interval.finish) << std::endl; - - if (interval.skip_duration) - out << _(" skip: ") << *interval.skip_duration << std::endl; - if (interval.factor) - out << _(" factor: ") << interval.factor << std::endl; - if (interval.duration) - out << _("duration: ") << *interval.duration << std::endl; - - out << std::endl; - - for (int i = 0; i < 20 && interval; i++, ++interval) { - out << std::right; - out.width(2); - - out << i << "): " << format_date(*interval.start); - if (interval.end_of_duration) - out << " -- " << format_date(*interval.inclusive_end()); - out << std::endl; - - if (! interval.skip_duration) - break; - } - } -} - } // namespace ledger - -#if defined(TIMES_HARNESS) - -int main(int argc, char *argv[]) -{ - if (argc > 1) { - ledger::times_initialize(); - ledger::analyze_period(std::cout, argv[1]); - ledger::times_shutdown(); - } else { - std::cerr << "Usage: times " << std::endl; - } - return 0; -} - -#endif // TIMES_HARNESS diff --git a/src/times.h b/src/times.h index 94ae4752..d378dd81 100644 --- a/src/times.h +++ b/src/times.h @@ -146,12 +146,18 @@ struct date_traits_t date_traits_t(bool _has_year = false, bool _has_month = false, bool _has_day = false) - : has_year(_has_year), has_month(_has_month), has_day(_has_day) {} - + : has_year(_has_year), has_month(_has_month), has_day(_has_day) { + TRACE_CTOR(date_traits_t, "bool, bool, bool"); + } date_traits_t(const date_traits_t& traits) : has_year(traits.has_year), has_month(traits.has_month), - has_day(traits.has_day) {} + has_day(traits.has_day) { + TRACE_CTOR(date_traits_t, "copy"); + } + ~date_traits_t() throw() { + TRACE_DTOR(date_traits_t); + } date_traits_t& operator=(const date_traits_t& traits) { has_year = traits.has_year; @@ -181,6 +187,100 @@ private: #endif // HAVE_BOOST_SERIALIZATION }; +struct date_duration_t +{ + enum skip_quantum_t { + DAYS, WEEKS, MONTHS, QUARTERS, YEARS + } quantum; + int length; + + date_duration_t() : quantum(DAYS), length(0) { + TRACE_CTOR(date_duration_t, ""); + } + date_duration_t(skip_quantum_t _quantum, int _length) + : quantum(_quantum), length(_length) { + TRACE_CTOR(date_duration_t, "skip_quantum_t, int"); + } + date_duration_t(const date_duration_t& dur) + : quantum(dur.quantum), length(dur.length) { + TRACE_CTOR(date_duration_t, "copy"); + } + ~date_duration_t() throw() { + TRACE_DTOR(date_duration_t); + } + + date_t add(const date_t& date) const { + switch (quantum) { + case DAYS: + return date + gregorian::days(length); + case WEEKS: + return date + gregorian::weeks(length); + case MONTHS: + return date + gregorian::months(length); + case QUARTERS: + return date + gregorian::months(length * 3); + case YEARS: + return date + gregorian::years(length); + default: + assert(false); return date_t(); + } + } + + date_t subtract(const date_t& date) const { + switch (quantum) { + case DAYS: + return date - gregorian::days(length); + case WEEKS: + return date - gregorian::weeks(length); + case MONTHS: + return date - gregorian::months(length); + case QUARTERS: + return date - gregorian::months(length * 3); + case YEARS: + return date - gregorian::years(length); + default: + assert(false); return date_t(); + } + } + + string to_string() const { + std::ostringstream out; + + out << length << ' '; + + switch (quantum) { + case DAYS: out << "day"; break; + case WEEKS: out << "week"; break; + case MONTHS: out << "month"; break; + case QUARTERS: out << "quarter"; break; + case YEARS: out << "year"; break; + default: + assert(false); + break; + } + + if (length > 1) + out << 's'; + + return out.str(); + } + + static date_t find_nearest(const date_t& date, skip_quantum_t skip); + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template + void serialize(Archive& ar, const unsigned int /* version */) { + ar & quantum; + ar & length; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + class date_specifier_t { friend class date_parser_t; @@ -200,15 +300,32 @@ class date_specifier_t optional wday; public: - date_specifier_t() {} - date_specifier_t(const date_t& date, const date_traits_t& traits) { - if (traits.has_year) + date_specifier_t(const optional& _year = none, + const optional& _month = none, + const optional& _day = none, + const optional& _wday = none) + : year(_year), month(_month), day(_day), wday(_wday) { + TRACE_CTOR(date_specifier_t, + "year_type, month_type, day_type, day_of_week_type"); + } + date_specifier_t(const date_t& date, + const optional& traits = none) { + TRACE_CTOR(date_specifier_t, "date_t, date_traits_t"); + if (! traits || traits->has_year) year = date.year(); - if (traits.has_month) + if (! traits || traits->has_month) month = date.month(); - if (traits.has_day) + if (! traits || traits->has_day) day = date.day(); } + date_specifier_t(const date_specifier_t& other) + : year(other.year), month(other.month), + day(other.day), wday(other.wday) { + TRACE_CTOR(date_specifier_t, "copy"); + } + ~date_specifier_t() throw() { + TRACE_DTOR(date_specifier_t); + } date_t begin(const optional_year& current_year = none) const; date_t end(const optional_year& current_year = none) const; @@ -218,6 +335,32 @@ public: return date >= begin(current_year) && date < end(current_year); } + optional implied_duration() const { + if (day || wday) + return date_duration_t(date_duration_t::DAYS, 1); + else if (month) + return date_duration_t(date_duration_t::MONTHS, 1); + else if (year) + return date_duration_t(date_duration_t::YEARS, 1); + else + return none; + } + + string to_string() const { + std::ostringstream out; + + if (year) + out << " year " << *year; + if (month) + out << " month " << *month; + if (day) + out << " day " << *day; + if (wday) + out << " wday " << *wday; + + return out.str(); + } + #if defined(HAVE_BOOST_SERIALIZATION) private: /** Serialization. */ @@ -241,7 +384,24 @@ class date_range_t optional range_begin; optional range_end; + bool end_inclusive; + public: + date_range_t(const optional& _range_begin = none, + const optional& _range_end = none) + : range_begin(_range_begin), range_end(_range_end), + end_inclusive(false) { + TRACE_CTOR(date_range_t, "date_specifier_t, date_specifier_t"); + } + date_range_t(const date_range_t& other) + : range_begin(other.range_begin), range_end(other.range_end), + end_inclusive(other.end_inclusive) { + TRACE_CTOR(date_range_t, "date_range_t"); + } + ~date_range_t() throw() { + TRACE_DTOR(date_range_t); + } + optional begin(const optional_year& current_year = none) const { if (range_begin) return range_begin->begin(current_year); @@ -249,10 +409,14 @@ public: return none; } optional end(const optional_year& current_year = none) const { - if (range_end) - return range_end->end(current_year); - else + if (range_end) { + if (end_inclusive) + return range_end->end(current_year); + else + return range_end->begin(current_year); + } else { return none; + } } bool is_within(const date_t& date, @@ -264,70 +428,15 @@ public: return after_begin && before_end; } -#if defined(HAVE_BOOST_SERIALIZATION) -private: - /** Serialization. */ - - friend class boost::serialization::access; - - template - void serialize(Archive& ar, const unsigned int /* version */) { - ar & range_begin; - ar & range_end; - } -#endif // HAVE_BOOST_SERIALIZATION -}; - -struct date_duration_t -{ - enum skip_quantum_t { - DAYS, WEEKS, MONTHS, YEARS - } quantum; - int length; + string to_string() const { + std::ostringstream out; - date_duration_t() : quantum(DAYS), length(0) { - TRACE_CTOR(date_duration_t, ""); - } - date_duration_t(skip_quantum_t _quantum, int _length) - : quantum(_quantum), length(_length) { - TRACE_CTOR(date_duration_t, "skip_quantum_t, int"); - } - date_duration_t(const date_duration_t& dur) - : quantum(dur.quantum), length(dur.length) { - TRACE_CTOR(date_duration_t, "copy"); - } - ~date_duration_t() throw() { - TRACE_DTOR(date_duration_t); - } - - date_t add(const date_t& date) const { - switch (quantum) { - case DAYS: - return date + gregorian::days(length); - case WEEKS: - return date + gregorian::weeks(length); - case MONTHS: - return date + gregorian::months(length); - case YEARS: - return date + gregorian::years(length); - default: - assert(false); return date_t(); - } - } - - date_t subtract(const date_t& date) const { - switch (quantum) { - case DAYS: - return date - gregorian::days(length); - case WEEKS: - return date - gregorian::weeks(length); - case MONTHS: - return date - gregorian::months(length); - case YEARS: - return date - gregorian::years(length); - default: - assert(false); return date_t(); - } + if (range_begin) + out << "from" << range_begin->to_string(); + if (range_end) + out << " to" << range_end->to_string(); + + return out.str(); } #if defined(HAVE_BOOST_SERIALIZATION) @@ -338,8 +447,8 @@ private: template void serialize(Archive& ar, const unsigned int /* version */) { - ar & quantum; - ar & length; + ar & range_begin; + ar & range_end; } #endif // HAVE_BOOST_SERIALIZATION }; @@ -351,6 +460,25 @@ class date_specifier_or_range_t value_type specifier_or_range; public: + date_specifier_or_range_t() { + TRACE_CTOR(date_specifier_or_range_t, ""); + } + date_specifier_or_range_t(const date_specifier_or_range_t& other) + : specifier_or_range(other.specifier_or_range) { + TRACE_CTOR(date_specifier_or_range_t, "copy"); + } + date_specifier_or_range_t(const date_specifier_t& specifier) + : specifier_or_range(specifier) { + TRACE_CTOR(date_specifier_or_range_t, "date_specifier_t"); + } + date_specifier_or_range_t(const date_range_t& range) + : specifier_or_range(range) { + TRACE_CTOR(date_specifier_or_range_t, "date_range_t"); + } + ~date_specifier_or_range_t() throw() { + TRACE_DTOR(date_specifier_or_range_t); + } + optional begin(const optional_year& current_year = none) const { if (specifier_or_range.type() == typeid(date_specifier_t)) return boost::get(specifier_or_range).begin(current_year); @@ -368,6 +496,18 @@ public: return none; } + + string to_string() const { + std::ostringstream out; + + if (specifier_or_range.type() == typeid(date_specifier_t)) + out << "in" << boost::get(specifier_or_range).to_string(); + else if (specifier_or_range.type() == typeid(date_range_t)) + out << boost::get(specifier_or_range).to_string(); + + return out.str(); + } + #if defined(HAVE_BOOST_SERIALIZATION) private: /** Serialization. */ @@ -439,17 +579,12 @@ public: return finish ? finish : (range ? range->end(current_year) : none); } - void parse(std::istream& in); - - void parse(const string& str) { - std::istringstream in(str); - parse(in); - } + void parse(const string& str); - void resolve_end(); - void stabilize(const optional& date = none); + void resolve_end(); + void stabilize(const optional& date = none); - bool is_valid() const { + bool is_valid() const { return start; } @@ -458,6 +593,12 @@ public: containing date, or false if no such period can be found. */ bool find_period(const date_t& date); + optional inclusive_skip_end() const { + if (skip_duration) + return skip_duration->add(*start) - gregorian::days(1); + else + return none; + } optional inclusive_end() const { if (end_of_duration) return *end_of_duration - gregorian::days(1); @@ -467,6 +608,8 @@ public: date_interval_t& operator++(); + void dump(std::ostream& out); + #if defined(HAVE_BOOST_SERIALIZATION) private: /** Serialization. */ @@ -488,197 +631,10 @@ private: #endif // HAVE_BOOST_SERIALIZATION }; -class date_parser_t -{ - friend void show_period_tokens(std::ostream& out, const string& arg); - - class lexer_t - { - friend class date_parser_t; - - string::const_iterator begin; - string::const_iterator end; - - public: - struct token_t - { - enum kind_t { - UNKNOWN, - - TOK_DATE, - TOK_INT, - TOK_SLASH, - TOK_DASH, - TOK_DOT, - - TOK_A_YEAR, - TOK_A_MONTH, - TOK_A_DAY, - TOK_A_WDAY, - - TOK_SINCE, - TOK_UNTIL, - TOK_IN, - TOK_THIS, - TOK_NEXT, - TOK_LAST, - - TOK_YEAR, - TOK_QUARTER, - TOK_MONTH, - TOK_WEEK, - TOK_DAY, - - TOK_YEARLY, - TOK_QUARTERLY, - TOK_BIMONTHLY, - TOK_MONTHLY, - TOK_BIWEEKLY, - TOK_WEEKLY, - TOK_DAILY, - - TOK_YEARS, - TOK_QUARTERS, - TOK_MONTHS, - TOK_WEEKS, - TOK_DAYS, - - END_REACHED - - } kind; - - typedef variant content_t; - - optional value; - - explicit token_t(kind_t _kind = UNKNOWN, - const optional& _value = none) - : kind(_kind), value(_value) { - TRACE_CTOR(date_parser_t::lexer_t::token_t, ""); - } - token_t(const token_t& tok) - : kind(tok.kind), value(tok.value) { - TRACE_CTOR(date_parser_t::lexer_t::token_t, "copy"); - } - ~token_t() throw() { - TRACE_DTOR(date_parser_t::lexer_t::token_t); - } - - token_t& operator=(const token_t& tok) { - if (this != &tok) { - kind = tok.kind; - value = tok.value; - } - return *this; - } - - operator bool() const { - return kind != END_REACHED; - } - - string to_string() const { - switch (kind) { - case UNKNOWN: return "UNKNOWN"; - case TOK_DATE: return "TOK_DATE"; - case TOK_INT: return "TOK_INT"; - case TOK_SLASH: return "TOK_SLASH"; - case TOK_DASH: return "TOK_DASH"; - case TOK_DOT: return "TOK_DOT"; - case TOK_A_YEAR: return "TOK_A_YEAR"; - case TOK_A_MONTH: return "TOK_A_MONTH"; - case TOK_A_DAY: return "TOK_A_DAY"; - case TOK_A_WDAY: return "TOK_A_WDAY"; - case TOK_SINCE: return "TOK_SINCE"; - case TOK_UNTIL: return "TOK_UNTIL"; - case TOK_IN: return "TOK_IN"; - case TOK_THIS: return "TOK_THIS"; - case TOK_NEXT: return "TOK_NEXT"; - case TOK_LAST: return "TOK_LAST"; - case TOK_YEAR: return "TOK_YEAR"; - case TOK_QUARTER: return "TOK_QUARTER"; - case TOK_MONTH: return "TOK_MONTH"; - case TOK_WEEK: return "TOK_WEEK"; - case TOK_DAY: return "TOK_DAY"; - case TOK_YEARLY: return "TOK_YEARLY"; - case TOK_QUARTERLY: return "TOK_QUARTERLY"; - case TOK_BIMONTHLY: return "TOK_BIMONTHLY"; - case TOK_MONTHLY: return "TOK_MONTHLY"; - case TOK_BIWEEKLY: return "TOK_BIWEEKLY"; - case TOK_WEEKLY: return "TOK_WEEKLY"; - case TOK_DAILY: return "TOK_DAILY"; - case TOK_YEARS: return "TOK_YEARS"; - case TOK_QUARTERS: return "TOK_QUARTERS"; - case TOK_MONTHS: return "TOK_MONTHS"; - case TOK_WEEKS: return "TOK_WEEKS"; - case TOK_DAYS: return "TOK_DAYS"; - case END_REACHED: return "END_REACHED"; - } - assert(false); - return empty_string; - } - - void unexpected(); - static void expected(char wanted, char c = '\0'); - }; - - token_t token_cache; - - lexer_t(string::const_iterator _begin, - string::const_iterator _end) - : begin(_begin), end(_end) - { - TRACE_CTOR(date_parser_t::lexer_t, ""); - } - lexer_t(const lexer_t& lexer) - : begin(lexer.begin), end(lexer.end), - token_cache(lexer.token_cache) - { - TRACE_CTOR(date_parser_t::lexer_t, "copy"); - } - ~lexer_t() throw() { - TRACE_DTOR(date_parser_t::lexer_t); - } - - token_t next_token(); - void push_token(token_t tok) { - assert(token_cache.kind == token_t::UNKNOWN); - token_cache = tok; - } - token_t peek_token() { - if (token_cache.kind == token_t::UNKNOWN) - token_cache = next_token(); - return token_cache; - } - }; - - string arg; - lexer_t lexer; - - date_interval_t parse_date_expr(); - -public: - date_parser_t(const string& _arg) - : arg(_arg), lexer(arg.begin(), arg.end()) { - TRACE_CTOR(date_parser_t, ""); - } - date_parser_t(const date_parser_t& parser) - : arg(parser.arg), lexer(parser.lexer) { - TRACE_CTOR(date_parser_t, "copy"); - } - ~date_parser_t() throw() { - TRACE_DTOR(date_parser_t); - } - - date_interval_t parse() { - return date_interval_t(); - } -}; - void times_initialize(); void times_shutdown(); void show_period_tokens(std::ostream& out, const string& arg); -void analyze_period(std::ostream& out, const string& arg); std::ostream& operator<<(std::ostream& out, const date_duration_t& duration); -- cgit v1.2.3 From e4b3f0bb3a74b799f0f67d8b2f1efeedad5e2021 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Wed, 18 Nov 2009 05:45:48 -0500 Subject: The new period parser is passing all tests --- src/archive.cc | 2 +- src/filters.cc | 14 ++- src/precmd.cc | 2 +- src/report.cc | 13 ++- src/report.h | 31 ++--- src/times.cc | 275 ++++++++++++++++++++++++++++----------------- src/times.h | 18 +-- src/token.cc | 5 +- test/regress/7F3650FD.test | 69 ++++++++++-- test/regress/BBFA1759.test | 14 ++- 10 files changed, 283 insertions(+), 160 deletions(-) (limited to 'src') diff --git a/src/archive.cc b/src/archive.cc index d36712ca..7306f8d3 100644 --- a/src/archive.cc +++ b/src/archive.cc @@ -43,7 +43,7 @@ #include "xact.h" #define LEDGER_MAGIC 0x4c454447 -#define ARCHIVE_VERSION 0x03000005 +#define ARCHIVE_VERSION 0x03000006 //BOOST_IS_ABSTRACT(ledger::scope_t) BOOST_CLASS_EXPORT(ledger::scope_t) diff --git a/src/filters.cc b/src/filters.cc index 5a92421b..47e7f66d 100644 --- a/src/filters.cc +++ b/src/filters.cc @@ -515,12 +515,14 @@ void subtotal_posts::report_subtotal(const char * spec_fmt, optional range_start = interval ? interval->start : none; optional range_finish = interval ? interval->inclusive_end() : none; - foreach (post_t * post, component_posts) { - date_t date = post->date(); - if (! range_start || date < *range_start) - range_start = date; - if (! range_finish || date > *range_finish) - range_finish = date; + if (! range_start || ! range_finish) { + foreach (post_t * post, component_posts) { + date_t date = post->date(); + if (! range_start || date < *range_start) + range_start = date; + if (! range_finish || date > *range_finish) + range_finish = date; + } } component_posts.clear(); diff --git a/src/precmd.cc b/src/precmd.cc index 0e024a39..516b90dd 100644 --- a/src/precmd.cc +++ b/src/precmd.cc @@ -169,7 +169,7 @@ value_t period_command(call_scope_t& args) out << std::endl; date_interval_t interval(arg); - interval.dump(out); + interval.dump(out, report.session.current_year); return NULL_VALUE; } diff --git a/src/report.cc b/src/report.cc index efe162e2..78ed05c0 100644 --- a/src/report.cc +++ b/src/report.cc @@ -120,14 +120,15 @@ void report_t::normalize_options(const string& verb) date_interval_t interval(HANDLER(period_).str()); - if (! HANDLED(begin_) && interval.start) { - string predicate = - "date>=[" + to_iso_extended_string(*interval.start) + "]"; + optional begin = interval.begin(session.current_year); + optional end = interval.end(session.current_year); + + if (! HANDLED(begin_) && begin) { + string predicate = "date>=[" + to_iso_extended_string(*begin) + "]"; HANDLER(limit_).on(string("?normalize"), predicate); } - if (! HANDLED(end_) && interval.finish) { - string predicate = - "date<[" + to_iso_extended_string(*interval.finish) + "]"; + if (! HANDLED(end_) && end) { + string predicate = "date<[" + to_iso_extended_string(*end) + "]"; HANDLER(limit_).on(string("?normalize"), predicate); } diff --git a/src/report.h b/src/report.h index 93f6e9e0..0b1baff1 100644 --- a/src/report.h +++ b/src/report.h @@ -50,6 +50,7 @@ #include "option.h" #include "commodity.h" #include "annotate.h" +#include "session.h" #include "format.h" namespace ledger { @@ -352,7 +353,7 @@ public: set_expr(args[0].to_string(), args[1].to_string()); }); - OPTION(report_t, amount_data); + OPTION(report_t, amount_data); // -j OPTION(report_t, anon); OPTION_(report_t, average, DO() { // -A @@ -377,14 +378,14 @@ public: }); OPTION_(report_t, begin_, DO_(args) { // -b - date_interval_t interval(args[1].to_string()); - if (! interval.start) + date_interval_t interval(args[1].to_string()); + optional begin = interval.begin(parent->session.current_year); + if (! begin) throw_(std::invalid_argument, _("Could not determine beginning of period '%1'") << args[1].to_string()); - string predicate = - "date>=[" + to_iso_extended_string(*interval.start) + "]"; + string predicate = "date>=[" + to_iso_extended_string(*begin) + "]"; parent->HANDLER(limit_).on(string("--begin"), predicate); }); @@ -524,17 +525,19 @@ public: OPTION(report_t, empty); // -E OPTION_(report_t, end_, DO_(args) { // -e - date_interval_t interval(args[1].to_string()); - if (! interval.start) + date_interval_t interval(args[1].to_string()); + // Use begin() here so that if the user says --end=2008, we end on + // 2008/01/01 instead of 2009/01/01 (which is what end() would return). + optional end = interval.begin(parent->session.current_year); + if (! end) throw_(std::invalid_argument, _("Could not determine end of period '%1'") << args[1].to_string()); - string predicate = - "date<[" + to_iso_extended_string(*interval.start) + "]"; + string predicate = "date<[" + to_iso_extended_string(*end) + "]"; parent->HANDLER(limit_).on(string("--end"), predicate); - parent->terminus = datetime_t(*interval.start); + parent->terminus = datetime_t(*end); }); OPTION(report_t, equity); @@ -622,11 +625,13 @@ public: OPTION_(report_t, now_, DO_(args) { date_interval_t interval(args[1].to_string()); - if (! interval.start) + optional begin = interval.begin(parent->session.current_year); + if (! begin) throw_(std::invalid_argument, _("Could not determine beginning of period '%1'") << args[1].to_string()); - ledger::epoch = datetime_t(*interval.start); + ledger::epoch = parent->terminus = datetime_t(*begin); + parent->session.current_year = ledger::epoch->date().year(); }); OPTION__ @@ -844,7 +849,7 @@ public: set_expr(args[0].to_string(), args[1].to_string()); }); - OPTION(report_t, total_data); + OPTION(report_t, total_data); // -J OPTION_(report_t, truncate_, DO_(args) { string style(args[1].to_string()); diff --git a/src/times.cc b/src/times.cc index 97fc3ab8..646f2a84 100644 --- a/src/times.cc +++ b/src/times.cc @@ -434,6 +434,7 @@ class date_parser_t } kind; typedef variant value; explicit token_t(kind_t _kind = UNKNOWN, - const optional& _value = none) + const optional& _value = + content_t(empty_string)) : kind(_kind), value(_value) { TRACE_CTOR(date_parser_t::lexer_t::token_t, ""); } @@ -467,47 +469,110 @@ class date_parser_t } string to_string() const { + std::ostringstream out; + + switch (kind) { + case UNKNOWN: + out << boost::get(*value); + break; + case TOK_DATE: + return boost::get(*value).to_string(); + case TOK_INT: + out << boost::get(*value); + break; + case TOK_SLASH: return "/"; + case TOK_DASH: return "-"; + case TOK_DOT: return "."; + case TOK_A_YEAR: + out << boost::get(*value); + break; + case TOK_A_MONTH: + out << date_specifier_t::month_type + (boost::get(*value)); + break; + case TOK_A_WDAY: + out << date_specifier_t::day_of_week_type + (boost::get(*value)); + break; + case TOK_SINCE: return "since"; + case TOK_UNTIL: return "until"; + case TOK_IN: return "in"; + case TOK_THIS: return "this"; + case TOK_NEXT: return "next"; + case TOK_LAST: return "last"; + case TOK_EVERY: return "every"; + case TOK_TODAY: return "today"; + case TOK_TOMORROW: return "tomorrow"; + case TOK_YESTERDAY: return "yesterday"; + case TOK_YEAR: return "year"; + case TOK_QUARTER: return "quarter"; + case TOK_MONTH: return "month"; + case TOK_WEEK: return "week"; + case TOK_DAY: return "day"; + case TOK_YEARLY: return "yearly"; + case TOK_QUARTERLY: return "quarterly"; + case TOK_BIMONTHLY: return "bimonthly"; + case TOK_MONTHLY: return "monthly"; + case TOK_BIWEEKLY: return "biweekly"; + case TOK_WEEKLY: return "weekly"; + case TOK_DAILY: return "daily"; + case TOK_YEARS: return "years"; + case TOK_QUARTERS: return "quarters"; + case TOK_MONTHS: return "months"; + case TOK_WEEKS: return "weeks"; + case TOK_DAYS: return "days"; + case END_REACHED: return ""; + default: + assert(false); + return empty_string; + } + + return out.str(); + } + + void dump(std::ostream& out) const { switch (kind) { - case UNKNOWN: return "UNKNOWN"; - case TOK_DATE: return "TOK_DATE"; - case TOK_INT: return "TOK_INT"; - case TOK_SLASH: return "TOK_SLASH"; - case TOK_DASH: return "TOK_DASH"; - case TOK_DOT: return "TOK_DOT"; - case TOK_A_YEAR: return "TOK_A_YEAR"; - case TOK_A_MONTH: return "TOK_A_MONTH"; - case TOK_A_WDAY: return "TOK_A_WDAY"; - case TOK_SINCE: return "TOK_SINCE"; - case TOK_UNTIL: return "TOK_UNTIL"; - case TOK_IN: return "TOK_IN"; - case TOK_THIS: return "TOK_THIS"; - case TOK_NEXT: return "TOK_NEXT"; - case TOK_LAST: return "TOK_LAST"; - case TOK_EVERY: return "TOK_EVERY"; - case TOK_TODAY: return "TOK_EVERY"; - case TOK_TOMORROW: return "TOK_TOMORROW"; - case TOK_YESTERDAY: return "TOK_YESTERDAY"; - case TOK_YEAR: return "TOK_YEAR"; - case TOK_QUARTER: return "TOK_QUARTER"; - case TOK_MONTH: return "TOK_MONTH"; - case TOK_WEEK: return "TOK_WEEK"; - case TOK_DAY: return "TOK_DAY"; - case TOK_YEARLY: return "TOK_YEARLY"; - case TOK_QUARTERLY: return "TOK_QUARTERLY"; - case TOK_BIMONTHLY: return "TOK_BIMONTHLY"; - case TOK_MONTHLY: return "TOK_MONTHLY"; - case TOK_BIWEEKLY: return "TOK_BIWEEKLY"; - case TOK_WEEKLY: return "TOK_WEEKLY"; - case TOK_DAILY: return "TOK_DAILY"; - case TOK_YEARS: return "TOK_YEARS"; - case TOK_QUARTERS: return "TOK_QUARTERS"; - case TOK_MONTHS: return "TOK_MONTHS"; - case TOK_WEEKS: return "TOK_WEEKS"; - case TOK_DAYS: return "TOK_DAYS"; - case END_REACHED: return "END_REACHED"; + case UNKNOWN: out << "UNKNOWN"; break; + case TOK_DATE: out << "TOK_DATE"; break; + case TOK_INT: out << "TOK_INT"; break; + case TOK_SLASH: out << "TOK_SLASH"; break; + case TOK_DASH: out << "TOK_DASH"; break; + case TOK_DOT: out << "TOK_DOT"; break; + case TOK_A_YEAR: out << "TOK_A_YEAR"; break; + case TOK_A_MONTH: out << "TOK_A_MONTH"; break; + case TOK_A_WDAY: out << "TOK_A_WDAY"; break; + case TOK_SINCE: out << "TOK_SINCE"; break; + case TOK_UNTIL: out << "TOK_UNTIL"; break; + case TOK_IN: out << "TOK_IN"; break; + case TOK_THIS: out << "TOK_THIS"; break; + case TOK_NEXT: out << "TOK_NEXT"; break; + case TOK_LAST: out << "TOK_LAST"; break; + case TOK_EVERY: out << "TOK_EVERY"; break; + case TOK_TODAY: out << "TOK_TODAY"; break; + case TOK_TOMORROW: out << "TOK_TOMORROW"; break; + case TOK_YESTERDAY: out << "TOK_YESTERDAY"; break; + case TOK_YEAR: out << "TOK_YEAR"; break; + case TOK_QUARTER: out << "TOK_QUARTER"; break; + case TOK_MONTH: out << "TOK_MONTH"; break; + case TOK_WEEK: out << "TOK_WEEK"; break; + case TOK_DAY: out << "TOK_DAY"; break; + case TOK_YEARLY: out << "TOK_YEARLY"; break; + case TOK_QUARTERLY: out << "TOK_QUARTERLY"; break; + case TOK_BIMONTHLY: out << "TOK_BIMONTHLY"; break; + case TOK_MONTHLY: out << "TOK_MONTHLY"; break; + case TOK_BIWEEKLY: out << "TOK_BIWEEKLY"; break; + case TOK_WEEKLY: out << "TOK_WEEKLY"; break; + case TOK_DAILY: out << "TOK_DAILY"; break; + case TOK_YEARS: out << "TOK_YEARS"; break; + case TOK_QUARTERS: out << "TOK_QUARTERS"; break; + case TOK_MONTHS: out << "TOK_MONTHS"; break; + case TOK_WEEKS: out << "TOK_WEEKS"; break; + case TOK_DAYS: out << "TOK_DAYS"; break; + case END_REACHED: out << "END_REACHED"; break; + default: + assert(false); + break; } - assert(false); - return empty_string; } void unexpected(); @@ -753,7 +818,9 @@ date_interval_t date_parser_t::parse() inclusion_specifier = date_specifier_t(static_cast(temp.year()), temp.month()); +#if 0 period.duration = date_duration_t(date_duration_t::QUARTERS, 1); +#endif break; } @@ -771,7 +838,9 @@ date_interval_t date_parser_t::parse() date_duration_t::find_nearest(today, date_duration_t::WEEKS); temp += gregorian::days(7 * adjust); inclusion_specifier = date_specifier_t(today); +#if 0 period.duration = date_duration_t(date_duration_t::WEEKS, 1); +#endif break; } @@ -806,19 +875,19 @@ date_interval_t date_parser_t::parse() tok = lexer.next_token(); switch (tok.kind) { case lexer_t::token_t::TOK_YEARS: - period.skip_duration = date_duration_t(date_duration_t::YEARS, quantity); + period.duration = date_duration_t(date_duration_t::YEARS, quantity); break; case lexer_t::token_t::TOK_QUARTERS: - period.skip_duration = date_duration_t(date_duration_t::QUARTERS, quantity); + period.duration = date_duration_t(date_duration_t::QUARTERS, quantity); break; case lexer_t::token_t::TOK_MONTHS: - period.skip_duration = date_duration_t(date_duration_t::MONTHS, quantity); + period.duration = date_duration_t(date_duration_t::MONTHS, quantity); break; case lexer_t::token_t::TOK_WEEKS: - period.skip_duration = date_duration_t(date_duration_t::WEEKS, quantity); + period.duration = date_duration_t(date_duration_t::WEEKS, quantity); break; case lexer_t::token_t::TOK_DAYS: - period.skip_duration = date_duration_t(date_duration_t::DAYS, quantity); + period.duration = date_duration_t(date_duration_t::DAYS, quantity); break; default: tok.unexpected(); @@ -827,19 +896,19 @@ date_interval_t date_parser_t::parse() } else { switch (tok.kind) { case lexer_t::token_t::TOK_YEAR: - period.skip_duration = date_duration_t(date_duration_t::YEARS, 1); + period.duration = date_duration_t(date_duration_t::YEARS, 1); break; case lexer_t::token_t::TOK_QUARTER: - period.skip_duration = date_duration_t(date_duration_t::QUARTERS, 1); + period.duration = date_duration_t(date_duration_t::QUARTERS, 1); break; case lexer_t::token_t::TOK_MONTH: - period.skip_duration = date_duration_t(date_duration_t::MONTHS, 1); + period.duration = date_duration_t(date_duration_t::MONTHS, 1); break; case lexer_t::token_t::TOK_WEEK: - period.skip_duration = date_duration_t(date_duration_t::WEEKS, 1); + period.duration = date_duration_t(date_duration_t::WEEKS, 1); break; case lexer_t::token_t::TOK_DAY: - period.skip_duration = date_duration_t(date_duration_t::DAYS, 1); + period.duration = date_duration_t(date_duration_t::DAYS, 1); break; default: tok.unexpected(); @@ -849,25 +918,25 @@ date_interval_t date_parser_t::parse() break; case lexer_t::token_t::TOK_YEARLY: - period.skip_duration = date_duration_t(date_duration_t::YEARS, 1); + period.duration = date_duration_t(date_duration_t::YEARS, 1); break; case lexer_t::token_t::TOK_QUARTERLY: - period.skip_duration = date_duration_t(date_duration_t::QUARTERS, 1); + period.duration = date_duration_t(date_duration_t::QUARTERS, 1); break; case lexer_t::token_t::TOK_BIMONTHLY: - period.skip_duration = date_duration_t(date_duration_t::MONTHS, 2); + period.duration = date_duration_t(date_duration_t::MONTHS, 2); break; case lexer_t::token_t::TOK_MONTHLY: - period.skip_duration = date_duration_t(date_duration_t::MONTHS, 1); + period.duration = date_duration_t(date_duration_t::MONTHS, 1); break; case lexer_t::token_t::TOK_BIWEEKLY: - period.skip_duration = date_duration_t(date_duration_t::WEEKS, 2); + period.duration = date_duration_t(date_duration_t::WEEKS, 2); break; case lexer_t::token_t::TOK_WEEKLY: - period.skip_duration = date_duration_t(date_duration_t::WEEKS, 1); + period.duration = date_duration_t(date_duration_t::WEEKS, 1); break; case lexer_t::token_t::TOK_DAILY: - period.skip_duration = date_duration_t(date_duration_t::DAYS, 1); + period.duration = date_duration_t(date_duration_t::DAYS, 1); break; default: @@ -876,8 +945,10 @@ date_interval_t date_parser_t::parse() } } +#if 0 if (! period.duration && inclusion_specifier) period.duration = inclusion_specifier->implied_duration(); +#endif if (since_specifier || until_specifier) { date_range_t range(since_specifier, until_specifier); @@ -915,16 +986,9 @@ void date_interval_t::resolve_end() "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); + next = end_of_duration; + DEBUG("times.interval", "stabilize: next set to: " << *next); } } @@ -988,10 +1052,10 @@ void date_interval_t::stabilize(const optional& date) #if defined(DEBUG_ON) if (initial_start) DEBUG("times.interval", - "stabilize: initial_start = " << *initial_start); + "stabilize: initial_start = " << *initial_start); if (initial_finish) DEBUG("times.interval", - "stabilize: initial_finish = " << *initial_finish); + "stabilize: initial_finish = " << *initial_finish); #endif date_t when = start ? *start : *date; @@ -1024,9 +1088,10 @@ void date_interval_t::stabilize(const optional& date) } } - DEBUG("times.interval", "stabilize: final start date = " << *start); + DEBUG("times.interval", "stabilize: proposed start date = " << *start); if (initial_start && (! start || *start < *initial_start)) { + // Using the discovered start, find the end of the period resolve_end(); start = initial_start; @@ -1036,14 +1101,20 @@ void date_interval_t::stabilize(const optional& date) finish = initial_finish; DEBUG("times.interval", "stabilize: finish reset to initial finish"); } + + if (start) + DEBUG("times.interval", "stabilize: final start = " << *start); + if (finish) + DEBUG("times.interval", "stabilize: final finish = " << *finish); } else if (range) { - start = range->begin(); - finish = range->end(); - - if (start && finish) - duration = date_duration_t(date_duration_t::DAYS, - static_cast((*finish - *start).days())); + if (date) { + start = range->begin(date->year()); + finish = range->end(date->year()); + } else { + start = range->begin(); + finish = range->end(); + } } aligned = true; } @@ -1116,7 +1187,7 @@ bool date_interval_t::find_period(const date_t& date) return true; } - scan = skip_duration->add(scan); + scan = duration->add(scan); end_of_scan = duration->add(scan); } @@ -1130,7 +1201,7 @@ date_interval_t& date_interval_t::operator++() stabilize(); - if (! skip_duration || ! duration) + if (! duration) throw_(date_error, _("Cannot increment a date interval without a duration")); @@ -1140,10 +1211,8 @@ date_interval_t& date_interval_t::operator++() start = none; } else { start = *next; - end_of_duration = duration->add(*start); } - next = none; resolve_end(); @@ -1151,7 +1220,7 @@ date_interval_t& date_interval_t::operator++() return *this; } -void date_interval_t::dump(std::ostream& out) +void date_interval_t::dump(std::ostream& out, optional_year current_year) { out << _("--- Before stabilization ---") << std::endl; @@ -1162,14 +1231,10 @@ void date_interval_t::dump(std::ostream& out) if (finish) out << _(" finish: ") << format_date(*finish) << std::endl; - if (skip_duration) - out << _(" skip: ") << skip_duration->to_string() << std::endl; - if (factor) - out << _(" factor: ") << factor << std::endl; if (duration) out << _("duration: ") << duration->to_string() << std::endl; - stabilize(begin()); + stabilize(begin(current_year)); out << std::endl << _("--- After stabilization ---") << std::endl; @@ -1181,27 +1246,30 @@ void date_interval_t::dump(std::ostream& out) if (finish) out << _(" finish: ") << format_date(*finish) << std::endl; - if (skip_duration) - out << _(" skip: ") << skip_duration->to_string() << std::endl; - if (factor) - out << _(" factor: ") << factor << std::endl; if (duration) out << _("duration: ") << duration->to_string() << std::endl; out << std::endl << _("--- Sample dates in range (max. 20) ---") << std::endl; - for (int i = 0; i < 20 && *this; i++, ++*this) { + date_t last_date; + + for (int i = 0; i < 20 && *this; ++i, ++*this) { out << std::right; out.width(2); + if (! last_date.is_not_a_date() && last_date == *start) + break; + out << (i + 1) << ": " << format_date(*start); - if (skip_duration) - out << " -- " << format_date(*inclusive_skip_end()); + if (duration) + out << " -- " << format_date(*inclusive_end()); out << std::endl; - if (! skip_duration) + if (! duration) break; + + last_date = *start; } } @@ -1268,6 +1336,8 @@ date_parser_t::lexer_t::token_t date_parser_t::lexer_t::next_token() token_t::content_t(lexical_cast(term))); } else if (std::isalpha(term[0])) { + to_lower(term); + if (optional month = string_to_month_of_year(term)) { return token_t(token_t::TOK_A_MONTH, token_t::content_t(*month)); @@ -1339,20 +1409,20 @@ date_parser_t::lexer_t::token_t date_parser_t::lexer_t::next_token() token_t::expected('\0', *begin); } - return token_t(token_t::UNKNOWN); + return token_t(token_t::UNKNOWN, token_t::content_t(term)); } void date_parser_t::lexer_t::token_t::unexpected() { - kind_t prev_kind = kind; - - kind = UNKNOWN; - - switch (prev_kind) { + switch (kind) { case END_REACHED: + kind = UNKNOWN; throw_(date_error, _("Unexpected end of expression")); - default: - throw_(date_error, _("Unexpected token '%1'") << to_string()); + default: { + string desc = to_string(); + kind = UNKNOWN; + throw_(date_error, _("Unexpected date period token '%1'") << desc); + } } } @@ -1503,7 +1573,8 @@ void show_period_tokens(std::ostream& out, const string& arg) date_parser_t::lexer_t::token_t token; do { token = lexer.next_token(); - out << token.to_string() << std::endl; + token.dump(out); + out << ": " << token.to_string() << std::endl; } while (token.kind != date_parser_t::lexer_t::token_t::END_REACHED); } diff --git a/src/times.h b/src/times.h index d378dd81..1c9d812e 100644 --- a/src/times.h +++ b/src/times.h @@ -534,16 +534,14 @@ public: optional start; // the real start, after adjustment optional finish; // the real end, likewise bool aligned; - optional skip_duration; - std::size_t factor; optional next; optional duration; optional end_of_duration; - explicit date_interval_t() : aligned(false), factor(1) { + explicit date_interval_t() : aligned(false) { TRACE_CTOR(date_interval_t, ""); } - date_interval_t(const string& str) : aligned(false), factor(1) { + date_interval_t(const string& str) : aligned(false) { TRACE_CTOR(date_interval_t, "const string&"); parse(str); } @@ -552,8 +550,6 @@ public: start(other.start), finish(other.finish), aligned(other.aligned), - skip_duration(other.skip_duration), - factor(other.factor), next(other.next), duration(other.duration), end_of_duration(other.end_of_duration) { @@ -593,12 +589,6 @@ public: containing date, or false if no such period can be found. */ bool find_period(const date_t& date); - optional inclusive_skip_end() const { - if (skip_duration) - return skip_duration->add(*start) - gregorian::days(1); - else - return none; - } optional inclusive_end() const { if (end_of_duration) return *end_of_duration - gregorian::days(1); @@ -608,7 +598,7 @@ public: date_interval_t& operator++(); - void dump(std::ostream& out); + void dump(std::ostream& out, optional_year current_year = none); #if defined(HAVE_BOOST_SERIALIZATION) private: @@ -622,8 +612,6 @@ private: ar & start; ar & finish; ar & aligned; - ar & skip_duration; - ar & factor; ar & next; ar & duration; ar & end_of_duration; diff --git a/src/token.cc b/src/token.cc index 3df072a7..81c54a82 100644 --- a/src/token.cc +++ b/src/token.cc @@ -206,11 +206,12 @@ void expr_t::token_t::next(std::istream& in, const parse_flags_t& pflags) length++; date_interval_t timespan(buf); - if (! timespan) + optional begin = timespan.begin(); + if (! begin) throw_(parse_error, _("Date specifier does not refer to a starting date")); kind = VALUE; - value = *timespan.start; + value = *begin; break; } diff --git a/test/regress/7F3650FD.test b/test/regress/7F3650FD.test index dce5233f..f7154eb8 100644 --- a/test/regress/7F3650FD.test +++ b/test/regress/7F3650FD.test @@ -1,50 +1,95 @@ period --now=2010/11/01 12/01 <<< >>>1 -global details => +--- Period expression tokens --- +TOK_DATE: month Dec day 1 +END_REACHED: - start: 09-Dec-01 - finish: 09-Dec-02 - factor: 1 +--- Before stabilization --- + range: in month Dec day 1 + +--- After stabilization --- + range: in month Dec day 1 + start: 10-Dec-01 + finish: 10-Dec-02 + +--- Sample dates in range (max. 20) --- + 1: 10-Dec-01 >>>2 === 0 period --now=2010/11/01 10/01 <<< >>>1 -global details => +--- Period expression tokens --- +TOK_DATE: month Oct day 1 +END_REACHED: + +--- Before stabilization --- + range: in month Oct day 1 +--- After stabilization --- + range: in month Oct day 1 start: 10-Oct-01 finish: 10-Oct-02 - factor: 1 + +--- Sample dates in range (max. 20) --- + 1: 10-Oct-01 >>>2 === 0 period --now=2010/11/01 2009/10 <<< >>>1 -global details => +--- Period expression tokens --- +TOK_DATE: year 2009 month Oct +END_REACHED: + +--- Before stabilization --- + range: in year 2009 month Oct +--- After stabilization --- + range: in year 2009 month Oct start: 09-Oct-01 finish: 09-Nov-01 - factor: 1 + +--- Sample dates in range (max. 20) --- + 1: 09-Oct-01 >>>2 === 0 period --now=2010/11/01 2009/10/01 <<< >>>1 -global details => +--- Period expression tokens --- +TOK_DATE: year 2009 month Oct day 1 +END_REACHED: + +--- Before stabilization --- + range: in year 2009 month Oct day 1 +--- After stabilization --- + range: in year 2009 month Oct day 1 start: 09-Oct-01 finish: 09-Oct-02 - factor: 1 + +--- Sample dates in range (max. 20) --- + 1: 09-Oct-01 >>>2 === 0 period --now=2010/11/01 2009 <<< >>>1 -global details => +--- Period expression tokens --- +TOK_A_YEAR: 2009 +END_REACHED: +--- Before stabilization --- + range: in year 2009 + +--- After stabilization --- + range: in year 2009 start: 09-Jan-01 finish: 10-Jan-01 - factor: 1 + +--- Sample dates in range (max. 20) --- + 1: 09-Jan-01 >>>2 === 0 diff --git a/test/regress/BBFA1759.test b/test/regress/BBFA1759.test index b109f868..cd5990fc 100644 --- a/test/regress/BBFA1759.test +++ b/test/regress/BBFA1759.test @@ -1,10 +1,20 @@ period june 2008 <<< >>>1 -global details => +--- Period expression tokens --- +TOK_A_MONTH: Jun +TOK_A_YEAR: 2008 +END_REACHED: +--- Before stabilization --- + range: in year 2008 month Jun + +--- After stabilization --- + range: in year 2008 month Jun start: 08-Jun-01 finish: 08-Jul-01 - factor: 1 + +--- Sample dates in range (max. 20) --- + 1: 08-Jun-01 >>>2 === 0 -- cgit v1.2.3 From 6cd0ba584e7bc64cbf86c58ca6fd208ba19e9dae Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Wed, 18 Nov 2009 05:50:21 -0500 Subject: Guarded some debug code --- src/times.cc | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') diff --git a/src/times.cc b/src/times.cc index 646f2a84..e3ccaff8 100644 --- a/src/times.cc +++ b/src/times.cc @@ -1102,10 +1102,12 @@ void date_interval_t::stabilize(const optional& date) DEBUG("times.interval", "stabilize: finish reset to initial finish"); } +#if defined(DEBUG_ON) if (start) DEBUG("times.interval", "stabilize: final start = " << *start); if (finish) DEBUG("times.interval", "stabilize: final finish = " << *finish); +#endif } else if (range) { if (date) { -- cgit v1.2.3 From 97122cf1f7ba03ecafd6aef72a20cfd46a8103ec Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Wed, 18 Nov 2009 16:44:07 -0500 Subject: Wasn't serializing date_range_t::end_inclusive member --- src/times.h | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/times.h b/src/times.h index 1c9d812e..826937bb 100644 --- a/src/times.h +++ b/src/times.h @@ -449,6 +449,7 @@ private: void serialize(Archive& ar, const unsigned int /* version */) { ar & range_begin; ar & range_end; + ar & end_inclusive; } #endif // HAVE_BOOST_SERIALIZATION }; -- cgit v1.2.3 From 9a389650ea9b159b17a61b99c5bd3a017511337a Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Wed, 18 Nov 2009 16:56:58 -0500 Subject: Allow ! and - operators on sequence values --- src/value.cc | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'src') diff --git a/src/value.cc b/src/value.cc index 797c144c..f4df3329 100644 --- a/src/value.cc +++ b/src/value.cc @@ -1193,6 +1193,13 @@ void value_t::in_place_negate() case BALANCE: as_balance_lval().in_place_negate(); return; + case SEQUENCE: { + value_t temp; + foreach (const value_t& value, as_sequence()) + temp.push_back(- value); + *this = temp; + return; + } default: break; } @@ -1222,6 +1229,13 @@ void value_t::in_place_not() case STRING: set_boolean(as_string().empty()); return; + case SEQUENCE: { + value_t temp; + foreach (const value_t& value, as_sequence()) + temp.push_back(! value); + *this = temp; + return; + } default: break; } -- cgit v1.2.3 From cd284f98c8d1cb13de4b6f29a1d98db8893a024f Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Wed, 18 Nov 2009 23:12:31 -0500 Subject: Added an ACCOUNT_GENERATED flag --- src/account.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/account.h b/src/account.h index 1e56fe23..73cd35ac 100644 --- a/src/account.h +++ b/src/account.h @@ -55,9 +55,10 @@ typedef std::map accounts_map; class account_t : public supports_flags<>, public scope_t { -#define ACCOUNT_NORMAL 0x00 // no flags at all, a basic account -#define ACCOUNT_KNOWN 0x01 -#define ACCOUNT_TEMP 0x02 // account is a temporary object +#define ACCOUNT_NORMAL 0x00 // no flags at all, a basic account +#define ACCOUNT_KNOWN 0x01 +#define ACCOUNT_TEMP 0x02 // account is a temporary object +#define ACCOUNT_GENERATED 0x04 // account never actually existed public: account_t * parent; -- cgit v1.2.3 From 539887b3fbbb28e67f61437817dd617607724897 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Wed, 18 Nov 2009 23:13:33 -0500 Subject: Changed only_preliminaries to for_accounts_report --- src/chain.cc | 8 ++++---- src/chain.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/chain.cc b/src/chain.cc index 55ef467b..479f9812 100644 --- a/src/chain.cc +++ b/src/chain.cc @@ -41,7 +41,7 @@ namespace ledger { post_handler_ptr chain_post_handlers(report_t& report, post_handler_ptr base_handler, - bool only_preliminaries) + bool for_accounts_report) { post_handler_ptr handler(base_handler); predicate_t display_predicate; @@ -51,7 +51,7 @@ post_handler_ptr chain_post_handlers(report_t& report, expr_t& expr(report.HANDLER(amount_).expr); expr.set_context(&report); - if (! only_preliminaries) { + if (! for_accounts_report) { // Make sure only forecast postings which match are allowed through if (report.HANDLED(forecast_while_)) { handler.reset(new filter_posts @@ -95,7 +95,7 @@ post_handler_ptr chain_post_handlers(report_t& report, // calc_posts computes the running total. When this appears will determine, // for example, whether filtered posts are included or excluded from the // running total. - handler.reset(new calc_posts(handler, expr, only_preliminaries)); + handler.reset(new calc_posts(handler, expr)); // filter_posts will only pass through posts matching the // `secondary_predicate'. @@ -105,7 +105,7 @@ post_handler_ptr chain_post_handlers(report_t& report, handler.reset(new filter_posts(handler, only_predicate, report)); } - if (! only_preliminaries) { + if (! for_accounts_report) { // sort_posts will sort all the posts it sees, based on the `sort_order' // value expression. if (report.HANDLED(sort_)) { diff --git a/src/chain.h b/src/chain.h index 0384027c..a2b75b3a 100644 --- a/src/chain.h +++ b/src/chain.h @@ -84,7 +84,7 @@ class report_t; post_handler_ptr chain_post_handlers(report_t& report, post_handler_ptr base_handler, - bool only_preliminaries = false); + bool for_accounts_report = false); } // namespace ledger -- cgit v1.2.3 From 93b8f3fe54814850fb2944e7467cd6ccc817b3ba Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Wed, 18 Nov 2009 23:14:07 -0500 Subject: Whitespace fix --- src/filters.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src') diff --git a/src/filters.cc b/src/filters.cc index 47e7f66d..ff5a9775 100644 --- a/src/filters.cc +++ b/src/filters.cc @@ -502,8 +502,7 @@ void changed_value_posts::operator()(post_t& post) bind_scope_t bound_scope(report, post); last_total = total_expr.calc(bound_scope); - - last_post = &post; + last_post = &post; } void subtotal_posts::report_subtotal(const char * spec_fmt, -- cgit v1.2.3 From e27ba3e1ff77537ade8287f1d66cf6fe626c8962 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Wed, 18 Nov 2009 23:15:22 -0500 Subject: It's OK for a report query to be empty It's always possible the user only specified a display predicate. --- src/precmd.cc | 24 ++++++++++-------------- src/report.cc | 24 ++++++++++-------------- 2 files changed, 20 insertions(+), 28 deletions(-) (limited to 'src') diff --git a/src/precmd.cc b/src/precmd.cc index 516b90dd..31249016 100644 --- a/src/precmd.cc +++ b/src/precmd.cc @@ -184,14 +184,12 @@ value_t args_command(call_scope_t& args) out << std::endl << std::endl; query_t query(args.value(), report.what_to_keep()); - if (! query) - throw_(std::runtime_error, - _("Invalid query predicate: %1") << join_args(args)); + if (query) { + call_scope_t sub_args(static_cast(args)); + sub_args.push_back(string_value(query.text())); - call_scope_t sub_args(static_cast(args)); - sub_args.push_back(string_value(query.text())); - - parse_command(sub_args); + parse_command(sub_args); + } if (query.tokens_remaining()) { out << std::endl << _("====== Display predicate ======") @@ -199,14 +197,12 @@ value_t args_command(call_scope_t& args) query.parse_again(); - if (! query) - throw_(std::runtime_error, - _("Invalid display predicate: %1") << join_args(args)); + if (query) { + call_scope_t disp_sub_args(static_cast(args)); + disp_sub_args.push_back(string_value(query.text())); - call_scope_t disp_sub_args(static_cast(args)); - disp_sub_args.push_back(string_value(query.text())); - - parse_command(disp_sub_args); + parse_command(disp_sub_args); + } } return NULL_VALUE; diff --git a/src/report.cc b/src/report.cc index 78ed05c0..ed3238bc 100644 --- a/src/report.cc +++ b/src/report.cc @@ -222,25 +222,21 @@ void report_t::normalize_options(const string& verb) void report_t::parse_query_args(const value_t& args, const string& whence) { query_t query(args, what_to_keep()); - if (! query) - throw_(std::runtime_error, - _("Invalid query predicate: %1") << query.text()); + if (query) { + HANDLER(limit_).on(whence, query.text()); - HANDLER(limit_).on(whence, query.text()); - - DEBUG("report.predicate", - "Predicate = " << HANDLER(limit_).str()); + DEBUG("report.predicate", + "Predicate = " << HANDLER(limit_).str()); + } if (query.tokens_remaining()) { query.parse_again(); - if (! query) - throw_(std::runtime_error, - _("Invalid display predicate: %1") << query.text()); + if (query) { + HANDLER(display_).on(whence, query.text()); - HANDLER(display_).on(whence, query.text()); - - DEBUG("report.predicate", - "Display predicate = " << HANDLER(display_).str()); + DEBUG("report.predicate", + "Display predicate = " << HANDLER(display_).str()); + } } } -- cgit v1.2.3 From 56d6df6123d84abe408e16fcecd7a7600eb33055 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Wed, 18 Nov 2009 23:15:49 -0500 Subject: Decompile all amount expressions for accounts reports This happens after running through all the post handlers, before running any of the account handlers. --- src/report.cc | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src') diff --git a/src/report.cc b/src/report.cc index ed3238bc..92525565 100644 --- a/src/report.cc +++ b/src/report.cc @@ -278,6 +278,12 @@ void report_t::accounts_report(acct_handler_ptr handler) chain_post_handlers(*this, post_handler_ptr(new ignore_posts), true); pass_down_posts(chain, walker); + HANDLER(amount_).expr.mark_uncompiled(); + HANDLER(total_).expr.mark_uncompiled(); + HANDLER(display_amount_).expr.mark_uncompiled(); + HANDLER(display_total_).expr.mark_uncompiled(); + HANDLER(revalued_total_).expr.mark_uncompiled(); + scoped_ptr iter; if (! HANDLED(sort_)) { iter.reset(new basic_accounts_iterator(*session.journal->master)); -- cgit v1.2.3 From ae8b57f15785f0fbe300e7d6d2b709b4730556d1 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Wed, 18 Nov 2009 23:24:53 -0500 Subject: Renamed bool controlling running total calculations It used to be "account_wise", since it only happens for non-account-wise reports. Now it's called just "calc_running_total", so anyone can request it. --- src/chain.cc | 2 +- src/filters.cc | 4 ++-- src/filters.h | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/chain.cc b/src/chain.cc index 479f9812..06bcdf27 100644 --- a/src/chain.cc +++ b/src/chain.cc @@ -95,7 +95,7 @@ post_handler_ptr chain_post_handlers(report_t& report, // calc_posts computes the running total. When this appears will determine, // for example, whether filtered posts are included or excluded from the // running total. - handler.reset(new calc_posts(handler, expr)); + handler.reset(new calc_posts(handler, expr, ! for_accounts_report)); // filter_posts will only pass through posts matching the // `secondary_predicate'. diff --git a/src/filters.cc b/src/filters.cc index ff5a9775..4c69dd78 100644 --- a/src/filters.cc +++ b/src/filters.cc @@ -217,7 +217,7 @@ void calc_posts::operator()(post_t& post) if (last_post) { assert(last_post->has_xdata()); - if (! account_wise) + if (calc_running_total) xdata.total = last_post->xdata().total; xdata.count = last_post->xdata().count + 1; } else { @@ -230,7 +230,7 @@ void calc_posts::operator()(post_t& post) account_t * acct = post.reported_account(); acct->xdata().add_flags(ACCOUNT_EXT_VISITED); - if (! account_wise) + if (calc_running_total) add_or_set_value(xdata.total, xdata.visited_value); item_handler::operator()(post); diff --git a/src/filters.h b/src/filters.h index 57b3edd2..40119d6d 100644 --- a/src/filters.h +++ b/src/filters.h @@ -280,16 +280,16 @@ class calc_posts : public item_handler { post_t * last_post; expr_t& amount_expr; - bool account_wise; + bool calc_running_total; calc_posts(); public: calc_posts(post_handler_ptr handler, expr_t& _amount_expr, - bool _account_wise = false) + bool _calc_running_total = false) : item_handler(handler), last_post(NULL), - amount_expr(_amount_expr), account_wise(_account_wise) { + amount_expr(_amount_expr), calc_running_total(_calc_running_total) { TRACE_CTOR(calc_posts, "post_handler_ptr, expr_t&, bool"); } virtual ~calc_posts() { -- cgit v1.2.3 From 20965d9fa3c2b355280f25520f0cd74cafcf5fbc Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Thu, 19 Nov 2009 00:54:19 -0500 Subject: Changed several pointers to references in filters --- src/chain.cc | 9 +------- src/filters.cc | 67 ++++++++++++++++++++++++++++++++++++---------------------- src/filters.h | 22 +++++-------------- 3 files changed, 48 insertions(+), 50 deletions(-) (limited to 'src') diff --git a/src/chain.cc b/src/chain.cc index 06bcdf27..a3695923 100644 --- a/src/chain.cc +++ b/src/chain.cc @@ -82,14 +82,7 @@ post_handler_ptr chain_post_handlers(report_t& report, // changes in market value of commodities, which otherwise would affect // the running total unpredictably. if (report.HANDLED(revalued)) - handler.reset(new changed_value_posts - (handler, - report.HANDLER(display_amount_).expr, - report.HANDLED(revalued_total_) ? - report.HANDLER(revalued_total_).expr : - report.HANDLER(display_total_).expr, - report.HANDLER(display_total_).expr, - report, report.HANDLED(revalued_only))); + handler.reset(new changed_value_posts(handler, report)); } // calc_posts computes the running total. When this appears will determine, diff --git a/src/filters.cc b/src/filters.cc index 4c69dd78..4ac3de4c 100644 --- a/src/filters.cc +++ b/src/filters.cc @@ -239,19 +239,20 @@ void calc_posts::operator()(post_t& post) } namespace { - typedef function post_functor_t; + typedef function post_functor_t; void handle_value(const value_t& value, - account_t * account, + account_t * account, xact_t * xact, temporaries_t& temps, - item_handler& handler, - const date_t& date = date_t(), - const value_t& total = value_t(), + post_handler_ptr handler, + const date_t& date = date_t(), + const value_t& total = value_t(), const bool direct_amount = false, const optional& functor = none) { post_t& post = temps.create_post(*xact, account); + post.add_flags(ITEM_GENERATED); // If the account for this post is all virtual, then report the post as // such. This allows subtotal reports to show "(Account)" for accounts @@ -302,11 +303,11 @@ namespace { xdata.add_flags(POST_EXT_DIRECT_AMT); if (functor) - (*functor)(&post); + (*functor)(post); DEBUG("filter.changed_value.rounding", "post.amount = " << post.amount); - handler(post); + (*handler)(post); } } @@ -344,7 +345,7 @@ void collapse_posts::report_subtotal() earliest_date : last_xact->_date); DEBUG("filter.collapse", "Pseudo-xact date = " << *xact._date); - handle_value(subtotal, &totals_account, &xact, temps, *handler); + handle_value(subtotal, &totals_account, &xact, temps, handler); } component_posts.clear(); @@ -404,30 +405,46 @@ void related_posts::flush() item_handler::flush(); } +changed_value_posts::changed_value_posts(post_handler_ptr handler, + report_t& _report) + : item_handler(handler), report(_report), last_post(NULL), + revalued_account(temps.create_account(_(""))), + rounding_account(temps.create_account(_(""))) +{ + TRACE_CTOR(changed_value_posts, "post_handler_ptr, report_t&, bool"); + + display_amount_expr = report.HANDLER(display_amount_).expr; + total_expr = (report.HANDLED(revalued_total_) ? + report.HANDLER(revalued_total_).expr : + report.HANDLER(display_total_).expr); + display_total_expr = report.HANDLER(display_total_).expr; + changed_values_only = report.HANDLED(revalued_only); +} + void changed_value_posts::flush() { if (last_post && last_post->date() <= report.terminus.date()) { - output_revaluation(last_post, report.terminus.date()); + output_revaluation(*last_post, report.terminus.date()); last_post = NULL; } item_handler::flush(); } -void changed_value_posts::output_revaluation(post_t * post, const date_t& date) +void changed_value_posts::output_revaluation(post_t& post, const date_t& date) { if (is_valid(date)) - post->xdata().date = date; + post.xdata().date = date; value_t repriced_total; try { - bind_scope_t bound_scope(report, *post); + bind_scope_t bound_scope(report, post); repriced_total = total_expr.calc(bound_scope); } catch (...) { - post->xdata().date = date_t(); + post.xdata().date = date_t(); throw; } - post->xdata().date = date_t(); + post.xdata().date = date_t(); DEBUG("filter.changed_value", "output_revaluation(last_balance) = " << last_total); @@ -441,9 +458,9 @@ void changed_value_posts::output_revaluation(post_t * post, const date_t& date) xact_t& xact = temps.create_xact(); xact.payee = _("Commodities revalued"); - xact._date = is_valid(date) ? date : post->date(); + xact._date = is_valid(date) ? date : post.date(); - handle_value(diff, &revalued_account, &xact, temps, *handler, + handle_value(diff, &revalued_account, &xact, temps, handler, *xact._date, repriced_total, false, optional (bind(&changed_value_posts::output_rounding, this, _1))); @@ -451,9 +468,9 @@ void changed_value_posts::output_revaluation(post_t * post, const date_t& date) } } -void changed_value_posts::output_rounding(post_t * post) +void changed_value_posts::output_rounding(post_t& post) { - bind_scope_t bound_scope(report, *post); + bind_scope_t bound_scope(report, post); value_t new_display_total(display_total_expr.calc(bound_scope)); DEBUG("filter.changed_value.rounding", @@ -478,9 +495,9 @@ void changed_value_posts::output_rounding(post_t * post) xact_t& xact = temps.create_xact(); xact.payee = _("Commodity rounding"); - xact._date = post->date(); + xact._date = post.date(); - handle_value(diff, &rounding_account, &xact, temps, *handler, + handle_value(diff, &rounding_account, &xact, temps, handler, *xact._date, precise_display_total, true); } } @@ -491,12 +508,12 @@ void changed_value_posts::output_rounding(post_t * post) void changed_value_posts::operator()(post_t& post) { if (last_post) - output_revaluation(last_post, post.date()); + output_revaluation(*last_post, post.date()); if (changed_values_only) post.xdata().add_flags(POST_EXT_DISPLAYED); - output_rounding(&post); + output_rounding(post); item_handler::operator()(post); @@ -543,7 +560,7 @@ void subtotal_posts::report_subtotal(const char * spec_fmt, foreach (values_map::value_type& pair, values) handle_value(pair.second.value, pair.second.account, &xact, temps, - *handler); + handler); values.clear(); } @@ -653,10 +670,10 @@ void posts_as_equity::report_subtotal() foreach (balance_t::amounts_map::value_type amount_pair, pair.second.value.as_balance().amounts) handle_value(amount_pair.second, pair.second.account, &xact, temps, - *handler); + handler); } else { handle_value(pair.second.value, pair.second.account, &xact, temps, - *handler); + handler); } total += pair.second.value; } diff --git a/src/filters.h b/src/filters.h index 40119d6d..2222878e 100644 --- a/src/filters.h +++ b/src/filters.h @@ -382,7 +382,7 @@ class changed_value_posts : public item_handler post_t * last_post; value_t last_total; value_t last_display_total; - temporaries_t temps; + temporaries_t temps; account_t& revalued_account; account_t& rounding_account; @@ -390,28 +390,16 @@ class changed_value_posts : public item_handler public: changed_value_posts(post_handler_ptr handler, - const expr_t& _display_amount_expr, - const expr_t& _total_expr, - const expr_t& _display_total_expr, - report_t& _report, - bool _changed_values_only) - : item_handler(handler), - display_amount_expr(_display_amount_expr), total_expr(_total_expr), - display_total_expr(_display_total_expr), report(_report), - changed_values_only(_changed_values_only), last_post(NULL), - revalued_account(temps.create_account(_(""))), - rounding_account(temps.create_account(_(""))) { - TRACE_CTOR(changed_value_posts, - "post_handler_ptr, const expr_t&, const expr_t&, report_t&, bool"); - } + report_t& _report); + virtual ~changed_value_posts() { TRACE_DTOR(changed_value_posts); } virtual void flush(); - void output_revaluation(post_t * post, const date_t& current); - void output_rounding(post_t * post); + void output_revaluation(post_t& post, const date_t& current); + void output_rounding(post_t& post); virtual void operator()(post_t& post); }; -- cgit v1.2.3 From 0c3a6234a96daea14eb13cac60ef674984655b90 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Thu, 19 Nov 2009 01:26:50 -0500 Subject: Don't auto-sort transactions for the balance report --- src/chain.cc | 4 ---- 1 file changed, 4 deletions(-) (limited to 'src') diff --git a/src/chain.cc b/src/chain.cc index a3695923..9ab26bd6 100644 --- a/src/chain.cc +++ b/src/chain.cc @@ -132,10 +132,6 @@ post_handler_ptr chain_post_handlers(report_t& report, else if (report.HANDLED(subtotal)) handler.reset(new subtotal_posts(handler, expr)); } - else if (! report.HANDLED(period_) && - ! report.HANDLED(unsorted)) { - handler.reset(new sort_posts(handler, "date")); - } if (report.HANDLED(dow)) handler.reset(new dow_posts(handler, expr)); -- cgit v1.2.3 From 3f1861fb1e9e374fb7756117638edcb6fb1e73fb Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Thu, 19 Nov 2009 01:27:10 -0500 Subject: Fixed a debug string --- src/filters.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src') diff --git a/src/filters.h b/src/filters.h index 2222878e..f9412407 100644 --- a/src/filters.h +++ b/src/filters.h @@ -241,8 +241,7 @@ public: const predicate_t& predicate, scope_t& _context) : item_handler(handler), pred(predicate), context(_context) { - TRACE_CTOR(filter_posts, - "post_handler_ptr, const predicate_t&, scope_t&"); + TRACE_CTOR(filter_posts, "post_handler_ptr, predicate_t, scope_t&"); } virtual ~filter_posts() { TRACE_DTOR(filter_posts); -- cgit v1.2.3 From 63fee4c83775f79364199ea547dbc7e068b0abc8 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Thu, 19 Nov 2009 02:00:10 -0500 Subject: Added an --unrealized option, for use with bal -V When this option is on, then in balance report which show market values, any gains or losses in value will be balanced into a pair of accounts called Equity:Unrealized Gains and Equity:Unrealized Losses. --- src/chain.cc | 19 ++++++++----- src/filters.cc | 58 ++++++++++++++++++++++++++++++++++----- src/filters.h | 8 +++++- src/report.cc | 1 + src/report.h | 3 ++ test/baseline/opt-unrealized.test | 20 ++++++++++++++ 6 files changed, 94 insertions(+), 15 deletions(-) create mode 100644 test/baseline/opt-unrealized.test (limited to 'src') diff --git a/src/chain.cc b/src/chain.cc index 9ab26bd6..113a71d8 100644 --- a/src/chain.cc +++ b/src/chain.cc @@ -77,18 +77,23 @@ post_handler_ptr chain_post_handlers(report_t& report, report.what_to_keep()); handler.reset(new filter_posts(handler, display_predicate, report)); } - - // changed_value_posts adds virtual posts to the list to account for - // changes in market value of commodities, which otherwise would affect - // the running total unpredictably. - if (report.HANDLED(revalued)) - handler.reset(new changed_value_posts(handler, report)); } + // changed_value_posts adds virtual posts to the list to account for changes + // in market value of commodities, which otherwise would affect the running + // total unpredictably. + if (report.HANDLED(revalued) && (! for_accounts_report || + report.HANDLED(unrealized))) + handler.reset(new changed_value_posts(handler, report, + for_accounts_report, + report.HANDLED(unrealized))); + // calc_posts computes the running total. When this appears will determine, // for example, whether filtered posts are included or excluded from the // running total. - handler.reset(new calc_posts(handler, expr, ! for_accounts_report)); + handler.reset(new calc_posts(handler, expr, (! for_accounts_report || + (report.HANDLED(revalued) && + report.HANDLED(unrealized))))); // filter_posts will only pass through posts matching the // `secondary_predicate'. diff --git a/src/filters.cc b/src/filters.cc index 4ac3de4c..39097c58 100644 --- a/src/filters.cc +++ b/src/filters.cc @@ -249,6 +249,7 @@ namespace { const date_t& date = date_t(), const value_t& total = value_t(), const bool direct_amount = false, + const bool mark_visited = false, const optional& functor = none) { post_t& post = temps.create_post(*xact, account); @@ -308,6 +309,11 @@ namespace { DEBUG("filter.changed_value.rounding", "post.amount = " << post.amount); (*handler)(post); + + if (mark_visited) { + post.xdata().add_flags(POST_EXT_VISITED); + post.account->xdata().add_flags(ACCOUNT_EXT_VISITED); + } } } @@ -406,8 +412,12 @@ void related_posts::flush() } changed_value_posts::changed_value_posts(post_handler_ptr handler, - report_t& _report) - : item_handler(handler), report(_report), last_post(NULL), + report_t& _report, + bool _for_accounts_report, + bool _show_unrealized) + : item_handler(handler), report(_report), + for_accounts_report(_for_accounts_report), + show_unrealized(_show_unrealized), last_post(NULL), revalued_account(temps.create_account(_(""))), rounding_account(temps.create_account(_(""))) { @@ -419,6 +429,14 @@ changed_value_posts::changed_value_posts(post_handler_ptr handler, report.HANDLER(display_total_).expr); display_total_expr = report.HANDLER(display_total_).expr; changed_values_only = report.HANDLED(revalued_only); + + gains_equity_account = + report.session.journal->master->find_account(_("Equity:Unrealized Gains")); + gains_equity_account->add_flags(ACCOUNT_GENERATED); + + losses_equity_account = + report.session.journal->master->find_account(_("Equity:Unrealized Losses")); + losses_equity_account->add_flags(ACCOUNT_GENERATED); } void changed_value_posts::flush() @@ -460,10 +478,35 @@ void changed_value_posts::output_revaluation(post_t& post, const date_t& date) xact.payee = _("Commodities revalued"); xact._date = is_valid(date) ? date : post.date(); - handle_value(diff, &revalued_account, &xact, temps, handler, - *xact._date, repriced_total, false, - optional - (bind(&changed_value_posts::output_rounding, this, _1))); + if (! for_accounts_report) { + handle_value + (/* value= */ diff, + /* account= */ &revalued_account, + /* xact= */ &xact, + /* temps= */ temps, + /* handler= */ handler, + /* date= */ *xact._date, + /* total= */ repriced_total, + /* direct_amount= */ false, + /* mark_visited= */ false, + /* functor= */ (optional + (bind(&changed_value_posts::output_rounding, + this, _1)))); + } + else if (show_unrealized) { + handle_value + (/* value= */ - diff, + /* account= */ (diff < 0L ? + losses_equity_account : + gains_equity_account), + /* xact= */ &xact, + /* temps= */ temps, + /* handler= */ handler, + /* date= */ *xact._date, + /* total= */ value_t(), + /* direct_amount= */ false, + /* mark_visited= */ true); + } } } } @@ -513,7 +556,8 @@ void changed_value_posts::operator()(post_t& post) if (changed_values_only) post.xdata().add_flags(POST_EXT_DISPLAYED); - output_rounding(post); + if (! for_accounts_report) + output_rounding(post); item_handler::operator()(post); diff --git a/src/filters.h b/src/filters.h index f9412407..92148dbe 100644 --- a/src/filters.h +++ b/src/filters.h @@ -378,18 +378,24 @@ class changed_value_posts : public item_handler expr_t display_total_expr; report_t& report; bool changed_values_only; + bool for_accounts_report; + bool show_unrealized; post_t * last_post; value_t last_total; value_t last_display_total; temporaries_t temps; account_t& revalued_account; account_t& rounding_account; + account_t * gains_equity_account; + account_t * losses_equity_account; changed_value_posts(); public: changed_value_posts(post_handler_ptr handler, - report_t& _report); + report_t& _report, + bool _for_accounts_report, + bool _show_unrealized); virtual ~changed_value_posts() { TRACE_DTOR(changed_value_posts); diff --git a/src/report.cc b/src/report.cc index 92525565..7da44f8c 100644 --- a/src/report.cc +++ b/src/report.cc @@ -887,6 +887,7 @@ option_t * report_t::lookup_option(const char * p) case 'u': OPT(unbudgeted); else OPT(uncleared); + else OPT(unrealized); else OPT(unround); else OPT(unsorted); break; diff --git a/src/report.h b/src/report.h index 0b1baff1..354e31b6 100644 --- a/src/report.h +++ b/src/report.h @@ -301,6 +301,7 @@ public: HANDLER(truncate_).report(out); HANDLER(unbudgeted).report(out); HANDLER(uncleared).report(out); + HANDLER(unrealized).report(out); HANDLER(unround).report(out); HANDLER(unsorted).report(out); HANDLER(weekly).report(out); @@ -873,6 +874,8 @@ public: parent->HANDLER(limit_).on(string("--uncleared"), "uncleared|pending"); }); + OPTION(report_t, unrealized); + OPTION_(report_t, unround, DO() { parent->HANDLER(display_amount_) .set_expr(string("--unround"), "unrounded(amount_expr)"); diff --git a/test/baseline/opt-unrealized.test b/test/baseline/opt-unrealized.test new file mode 100644 index 00000000..7d5d20fb --- /dev/null +++ b/test/baseline/opt-unrealized.test @@ -0,0 +1,20 @@ +bal -V --unrealized +<<< +2008/10/01 Sample + Assets:Brokerage 10 AAPL + Assets:Checking $-200.00 + +P 2008/10/20 12:00:00 AAPL $30.00 + +; 2008/10/20 +; Assets:Brokerage $100 +; Equity:Unrealized Gains +>>>1 + $100.00 Assets + $300.00 Brokerage + $-200.00 Checking + $-100.00 Equity:Unrealized Gains +-------------------- + 0 +>>>2 +=== 0 -- cgit v1.2.3