diff options
author | John Wiegley <johnw@newartisans.com> | 2009-11-19 03:37:16 -0500 |
---|---|---|
committer | John Wiegley <johnw@newartisans.com> | 2009-11-19 03:37:16 -0500 |
commit | cc9110a43a1e2d006de8d24a84bbfb2c6918cf33 (patch) | |
tree | 4230ae03ebea8e2cbc0b6e1de821730e4819af47 | |
parent | 6b557f810e1c6eb2dcd8a24eeeacfcf9cc8210e3 (diff) | |
parent | 63fee4c83775f79364199ea547dbc7e068b0abc8 (diff) | |
download | fork-ledger-cc9110a43a1e2d006de8d24a84bbfb2c6918cf33.tar.gz fork-ledger-cc9110a43a1e2d006de8d24a84bbfb2c6918cf33.tar.bz2 fork-ledger-cc9110a43a1e2d006de8d24a84bbfb2c6918cf33.zip |
Merge branch 'next'
-rw-r--r-- | src/account.h | 7 | ||||
-rw-r--r-- | src/archive.cc | 2 | ||||
-rw-r--r-- | src/chain.cc | 36 | ||||
-rw-r--r-- | src/chain.h | 2 | ||||
-rw-r--r-- | src/commodity.cc | 3 | ||||
-rw-r--r-- | src/filters.cc | 146 | ||||
-rw-r--r-- | src/filters.h | 37 | ||||
-rw-r--r-- | src/precmd.cc | 77 | ||||
-rw-r--r-- | src/query.cc | 4 | ||||
-rw-r--r-- | src/query.h | 72 | ||||
-rw-r--r-- | src/report.cc | 44 | ||||
-rw-r--r-- | src/report.h | 34 | ||||
-rw-r--r-- | src/times.cc | 1209 | ||||
-rw-r--r-- | src/times.h | 519 | ||||
-rw-r--r-- | src/token.cc | 5 | ||||
-rw-r--r-- | src/utils.h | 5 | ||||
-rw-r--r-- | src/value.cc | 36 | ||||
-rw-r--r-- | test/baseline/opt-unrealized.test | 20 | ||||
-rw-r--r-- | test/regress/7F3650FD.test | 77 | ||||
-rw-r--r-- | test/regress/BBFA1759.test | 16 |
20 files changed, 1744 insertions, 607 deletions
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<const string, account_t *> 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; 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/chain.cc b/src/chain.cc index 55ef467b..113a71d8 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 @@ -77,25 +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.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))); } + // 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, only_preliminaries)); + 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'. @@ -105,7 +103,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_)) { @@ -139,10 +137,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)); 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 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/filters.cc b/src/filters.cc index bef4dc24..39097c58 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<post_t>::operator()(post); @@ -239,19 +239,21 @@ void calc_posts::operator()(post_t& post) } namespace { - typedef function<void (post_t *)> post_functor_t; + typedef function<void (post_t&)> post_functor_t; void handle_value(const value_t& value, - account_t * account, + account_t * account, xact_t * xact, temporaries_t& temps, - item_handler<post_t>& 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 bool mark_visited = false, const optional<post_functor_t>& 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 +304,16 @@ 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); + + if (mark_visited) { + post.xdata().add_flags(POST_EXT_VISITED); + post.account->xdata().add_flags(ACCOUNT_EXT_VISITED); + } } } @@ -344,7 +351,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 +411,58 @@ void related_posts::flush() item_handler<post_t>::flush(); } +changed_value_posts::changed_value_posts(post_handler_ptr handler, + report_t& _report, + bool _for_accounts_report, + bool _show_unrealized) + : item_handler<post_t>(handler), report(_report), + for_accounts_report(_for_accounts_report), + show_unrealized(_show_unrealized), last_post(NULL), + revalued_account(temps.create_account(_("<Revalued>"))), + rounding_account(temps.create_account(_("<Rounding>"))) +{ + 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); + + 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() { 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<post_t>::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,19 +476,44 @@ 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(); - - handle_value(diff, &revalued_account, &xact, temps, *handler, - *xact._date, repriced_total, false, - optional<post_functor_t> - (bind(&changed_value_posts::output_rounding, this, _1))); + xact._date = is_valid(date) ? date : post.date(); + + 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<post_functor_t> + (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); + } } } } -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 +538,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,19 +551,19 @@ 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); + if (! for_accounts_report) + output_rounding(post); item_handler<post_t>::operator()(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, @@ -515,12 +575,14 @@ void subtotal_posts::report_subtotal(const char * spec_fmt, optional<date_t> range_start = interval ? interval->start : none; optional<date_t> 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(); @@ -542,7 +604,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(); } @@ -652,10 +714,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; } @@ -784,7 +846,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 +967,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/filters.h b/src/filters.h index 57b3edd2..92148dbe 100644 --- a/src/filters.h +++ b/src/filters.h @@ -241,8 +241,7 @@ public: const predicate_t& predicate, scope_t& _context) : item_handler<post_t>(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); @@ -280,16 +279,16 @@ class calc_posts : public item_handler<post_t> { 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<post_t>(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() { @@ -379,39 +378,33 @@ class changed_value_posts : public item_handler<post_t> 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; + 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, - 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<post_t>(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(_("<Revalued>"))), - rounding_account(temps.create_account(_("<Rounding>"))) { - TRACE_CTOR(changed_value_posts, - "post_handler_ptr, const expr_t&, const expr_t&, report_t&, bool"); - } + report_t& _report, + bool _for_accounts_report, + bool _show_unrealized); + 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); }; diff --git a/src/precmd.cc b/src/precmd.cc index 92483dc8..31249016 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<report_t>(args)); std::ostream& out(report.output_stream); + show_period_tokens(out, arg); + out << std::endl; + date_interval_t interval(arg); + interval.dump(out, report.session.current_year); - 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.end) - out << _(" end: ") << format_date(*interval.end) << 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.end) - out << _(" end: ") << format_date(*interval.end) << 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; } @@ -229,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<scope_t&>(args)); + sub_args.push_back(string_value(query.text())); - call_scope_t sub_args(static_cast<scope_t&>(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 ======") @@ -244,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<scope_t&>(args)); + disp_sub_args.push_back(string_value(query.text())); - call_scope_t disp_sub_args(static_cast<scope_t&>(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/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/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<string>& _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 "<EOF>"; @@ -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() { diff --git a/src/report.cc b/src/report.cc index 267a4a3d..7da44f8c 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<date_t> begin = interval.begin(session.current_year); + optional<date_t> 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.end) { - string predicate = - "date<[" + to_iso_extended_string(*interval.end) + "]"; + if (! HANDLED(end_) && end) { + string predicate = "date<[" + to_iso_extended_string(*end) + "]"; HANDLER(limit_).on(string("?normalize"), predicate); } @@ -221,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()); - - HANDLER(limit_).on(whence, query.text()); + if (query) { + 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()); + } } } @@ -281,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<accounts_iterator> iter; if (! HANDLED(sort_)) { iter.reset(new basic_accounts_iterator(*session.journal->master)); @@ -884,6 +887,7 @@ option_t<report_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 93f6e9e0..354e31b6 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 { @@ -300,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); @@ -352,7 +354,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 +379,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<date_t> 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 +526,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<date_t> 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 +626,13 @@ public: OPTION_(report_t, now_, DO_(args) { date_interval_t interval(args[1].to_string()); - if (! interval.start) + optional<date_t> 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 +850,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()); @@ -868,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/src/times.cc b/src/times.cc index 6afbab0a..e3ccaff8 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); @@ -191,8 +194,8 @@ namespace { std::vector<shared_ptr<date_io_t> > readers; date_t parse_date_mask_routine(const char * date_str, date_io_t& io, - optional<date_t::year_type> year, - bool& saw_year, bool& saw_day) + optional_year year, + date_traits_t * traits = NULL) { date_t when; @@ -214,38 +217,33 @@ 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<date_t::year_type> year, - bool& saw_year, bool& saw_day) + date_t parse_date_mask(const char * date_str, optional_year year, + 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<date_io_t>& 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; } @@ -306,7 +304,7 @@ string_to_month_of_year(const std::string& str) return none; } -datetime_t parse_datetime(const char * str, optional<date_t::year_type>) +datetime_t parse_datetime(const char * str, optional_year) { datetime_t when = input_datetime_io->parse(str); if (when.is_not_a_date_time()) @@ -314,29 +312,666 @@ datetime_t parse_datetime(const char * str, optional<date_t::year_type>) return when; } -date_t parse_date(const char * str, optional<date_t::year_type> current_year) +date_t parse_date(const char * str, optional_year 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); +} + +date_t date_specifier_t::begin(const optional_year& current_year) const +{ + assert(year || current_year); + + year_type the_year = year ? *year : static_cast<year_type>(*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<date_t::year_type>(the_year), + static_cast<date_t::month_type>(the_month), + static_cast<date_t::day_type>(the_day)); +} + +date_t date_specifier_t::end(const optional_year& 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_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 if (duration.quantum == date_duration_t::QUARTERS) + out << duration.length << " quarter(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; } +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<unsigned short, + string, + date_specifier_t::year_type, + date_time::months_of_year, + date_time::weekdays, + date_specifier_t> content_t; + + optional<content_t> value; + + explicit token_t(kind_t _kind = UNKNOWN, + const optional<content_t>& _value = + content_t(empty_string)) + : 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 { + std::ostringstream out; + + switch (kind) { + case UNKNOWN: + out << boost::get<string>(*value); + break; + case TOK_DATE: + return boost::get<date_specifier_t>(*value).to_string(); + case TOK_INT: + out << boost::get<unsigned short>(*value); + break; + case TOK_SLASH: return "/"; + case TOK_DASH: return "-"; + case TOK_DOT: return "."; + case TOK_A_YEAR: + out << boost::get<date_specifier_t::year_type>(*value); + break; + case TOK_A_MONTH: + out << date_specifier_t::month_type + (boost::get<date_time::months_of_year>(*value)); + break; + case TOK_A_WDAY: + out << date_specifier_t::day_of_week_type + (boost::get<date_time::weekdays>(*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 "<EOF>"; + default: + assert(false); + return empty_string; + } + + return out.str(); + } + + void dump(std::ostream& out) const { + switch (kind) { + 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; + } + } + + 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<date_specifier_t>(*tok.value); + break; + + case lexer_t::token_t::TOK_INT: + specifier.day = + date_specifier_t::day_type(boost::get<unsigned short>(*tok.value)); + break; + case lexer_t::token_t::TOK_A_YEAR: + specifier.year = boost::get<date_specifier_t::year_type>(*tok.value); + break; + case lexer_t::token_t::TOK_A_MONTH: + specifier.month = + date_specifier_t::month_type + (boost::get<date_time::months_of_year>(*tok.value)); + break; + case lexer_t::token_t::TOK_A_WDAY: + specifier.wday = + date_specifier_t::day_of_week_type + (boost::get<date_time::weekdays>(*tok.value)); + break; + + default: + tok.unexpected(); + break; + } +} + +date_interval_t date_parser_t::parse() +{ + optional<date_specifier_t> since_specifier; + optional<date_specifier_t> until_specifier; + optional<date_specifier_t> 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<date_specifier_t::year_type>(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<date_specifier_t::year_type>(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<date_specifier_t::year_type>(temp.year()), + temp.month()); +#if 0 + period.duration = date_duration_t(date_duration_t::QUARTERS, 1); +#endif + break; + } + + case lexer_t::token_t::TOK_MONTH: { + date_t temp(today); + temp += gregorian::months(adjust); + inclusion_specifier = + date_specifier_t(static_cast<date_specifier_t::year_type>(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); +#if 0 + period.duration = date_duration_t(date_duration_t::WEEKS, 1); +#endif + 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<unsigned short>(*tok.value); + tok = lexer.next_token(); + switch (tok.kind) { + case lexer_t::token_t::TOK_YEARS: + period.duration = date_duration_t(date_duration_t::YEARS, quantity); + break; + case lexer_t::token_t::TOK_QUARTERS: + period.duration = date_duration_t(date_duration_t::QUARTERS, quantity); + break; + case lexer_t::token_t::TOK_MONTHS: + period.duration = date_duration_t(date_duration_t::MONTHS, quantity); + break; + case lexer_t::token_t::TOK_WEEKS: + period.duration = date_duration_t(date_duration_t::WEEKS, quantity); + break; + case lexer_t::token_t::TOK_DAYS: + period.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.duration = date_duration_t(date_duration_t::YEARS, 1); + break; + case lexer_t::token_t::TOK_QUARTER: + period.duration = date_duration_t(date_duration_t::QUARTERS, 1); + break; + case lexer_t::token_t::TOK_MONTH: + period.duration = date_duration_t(date_duration_t::MONTHS, 1); + break; + case lexer_t::token_t::TOK_WEEK: + period.duration = date_duration_t(date_duration_t::WEEKS, 1); + break; + case lexer_t::token_t::TOK_DAY: + period.duration = date_duration_t(date_duration_t::DAYS, 1); + break; + default: + tok.unexpected(); + break; + } + } + break; + + case lexer_t::token_t::TOK_YEARLY: + period.duration = date_duration_t(date_duration_t::YEARS, 1); + break; + case lexer_t::token_t::TOK_QUARTERLY: + period.duration = date_duration_t(date_duration_t::QUARTERS, 1); + break; + case lexer_t::token_t::TOK_BIMONTHLY: + period.duration = date_duration_t(date_duration_t::MONTHS, 2); + break; + case lexer_t::token_t::TOK_MONTHLY: + period.duration = date_duration_t(date_duration_t::MONTHS, 1); + break; + case lexer_t::token_t::TOK_BIWEEKLY: + period.duration = date_duration_t(date_duration_t::WEEKS, 2); + break; + case lexer_t::token_t::TOK_WEEKLY: + period.duration = date_duration_t(date_duration_t::WEEKS, 1); + break; + case lexer_t::token_t::TOK_DAILY: + period.duration = date_duration_t(date_duration_t::DAYS, 1); + break; + + default: + tok.unexpected(); + break; + } + } + +#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); + 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) { @@ -345,23 +980,50 @@ 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); } - if (! skip_duration) { - skip_duration = duration; - DEBUG("times.interval", - "stabilize: skip_duration set to: " << *skip_duration); + if (start && ! next) { + next = end_of_duration; + DEBUG("times.interval", "stabilize: next set to: " << *next); } +} - if (start && ! next) { - next = skip_duration->add(*start); - DEBUG("times.interval", - "stabilize: next set to: " << *next); +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_t>& date) @@ -384,35 +1046,30 @@ void date_interval_t::stabilize(const optional<date_t>& 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<date_t> initial_start = start; - optional<date_t> initial_end = end; + optional<date_t> initial_start = start ? start : begin(date->year()); + optional<date_t> initial_finish = finish ? finish : end(date->year()); #if defined(DEBUG_ON) if (initial_start) DEBUG("times.interval", - "stabilize: initial_start = " << *initial_start); - if (initial_end) + "stabilize: initial_start = " << *initial_start); + if (initial_finish) DEBUG("times.interval", - "stabilize: initial_end = " << *initial_end); + "stabilize: initial_finish = " << *initial_finish); #endif date_t when = start ? *start : *date; - if (duration->quantum == duration_t::MONTHS || - duration->quantum == duration_t::YEARS) { - DEBUG("times.interval", "stabilize: monthly or yearly duration"); - - start = date_t(when.year(), gregorian::Jan, 1); + if (duration->quantum == date_duration_t::MONTHS || + duration->quantum == date_duration_t::QUARTERS || + duration->quantum == date_duration_t::YEARS) { + 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 == 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", @@ -431,30 +1088,47 @@ void date_interval_t::stabilize(const optional<date_t>& 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; 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"); + } + +#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) { + start = range->begin(date->year()); + finish = range->end(date->year()); + } else { + start = range->begin(); + finish = range->end(); } } 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 +1138,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 +1177,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; @@ -515,7 +1189,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); } @@ -529,20 +1203,18 @@ 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")); assert(next); - if (end && *next >= *end) { + if (finish && *next >= *finish) { start = none; } else { start = *next; - end_of_duration = duration->add(*start); } - next = none; resolve_end(); @@ -550,213 +1222,224 @@ date_interval_t& date_interval_t::operator++() return *this; } -namespace { - void parse_inclusion_specifier(const string& word, - 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); +void date_interval_t::dump(std::ostream& out, optional_year current_year) +{ + out << _("--- Before stabilization ---") << std::endl; - if (when.is_not_a_date()) - throw_(date_error, _("Could not parse date mask: %1") << word); + 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 (begin) { - *begin = when; + if (duration) + out << _("duration: ") << duration->to_string() << std::endl; - if (end) { - if (saw_day) - *end = *begin + gregorian::days(1); - else - *end = *begin + gregorian::months(1); - } - } - else if (end) { - *end = when; - } - } + stabilize(begin(current_year)); - inline void read_lower_word(std::istream& in, string& word) { - in >> word; - for (string::size_type i = 0, l = word.length(); i < l; i++) - word[i] = static_cast<char>(std::tolower(word[i])); - } + out << std::endl + << _("--- After stabilization ---") << std::endl; - void parse_date_words(std::istream& in, - string& word, - date_interval_t& interval, - bool look_for_start = true, - bool look_for_end = true) - { - string type; + if (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 (word == _("this") || word == _("last") || word == _("next")) { - type = word; - if (! in.eof()) - read_lower_word(in, word); - else - word = _("month"); - } else { - type = _("this"); - } + if (duration) + out << _("duration: ") << duration->to_string() << std::endl; - date_t start = CURRENT_DATE(); - date_t end; - bool parse_specifier = false; + out << std::endl + << _("--- Sample dates in range (max. 20) ---") << std::endl; - optional<date_interval_t::duration_t> duration; + date_t last_date; - assert(look_for_start || look_for_end); + for (int i = 0; i < 20 && *this; ++i, ++*this) { + out << std::right; + out.width(2); - if (word == _("year")) { - duration = date_interval_t::duration_t(date_interval_t::duration_t::YEARS, 1); - start = gregorian::date(start.year(), 1, 1); - } - else if (word == _("month")) { - duration = date_interval_t::duration_t(date_interval_t::duration_t::MONTHS, 1); - start = gregorian::date(start.year(), start.month(), 1); - } - else if (word == _("today") || word == _("day")) { - duration = date_interval_t::duration_t(date_interval_t::duration_t::DAYS, 1); - } - else { - parse_specifier = true; - } + if (! last_date.is_not_a_date() && last_date == *start) + break; - if (parse_specifier) - parse_inclusion_specifier(word, &start, &end); - else - end = duration->add(start); + out << (i + 1) << ": " << format_date(*start); + if (duration) + out << " -- " << format_date(*inclusive_end()); + out << std::endl; - if (type == _("last") && duration) { - start = duration->subtract(start); - end = duration->subtract(end); - } - else if (type == _("next") && duration) { - start = duration->add(start); - end = duration->add(end); - } + if (! duration) + break; - if (look_for_start && is_valid(start)) interval.start = start; - if (look_for_end && is_valid(end)) interval.end = end; + last_date = *start; } } -void date_interval_t::parse(std::istream& in) +date_parser_t::lexer_t::token_t date_parser_t::lexer_t::next_token() { - string word; - - optional<date_time::months_of_year> mon; - optional<date_time::weekdays> wday; - optional<date_t::year_type> year; - - while (! in.eof()) { - read_lower_word(in, word); - if (word == _("every")) { - read_lower_word(in, word); - if (std::isdigit(word[0])) { - int quantity = lexical_cast<int>(word); - read_lower_word(in, word); - if (word == _("days")) - duration = duration_t(duration_t::DAYS, quantity); - else if (word == _("weeks")) - duration = duration_t(duration_t::WEEKS, quantity); - else if (word == _("months")) - duration = duration_t(duration_t::MONTHS, quantity); - else if (word == _("quarters")) - duration = duration_t(duration_t::MONTHS, 3 * quantity); - else if (word == _("years")) - duration = duration_t(duration_t::YEARS, quantity); - } - else if (word == _("day")) - duration = duration_t(duration_t::DAYS, 1); - else if (word == _("week")) - duration = duration_t(duration_t::WEEKS, 1); - else if (word == _("month")) - duration = duration_t(duration_t::MONTHS, 1); - else if (word == _("quarter")) - duration = duration_t(duration_t::MONTHS, 3); - else if (word == _("year")) - duration = duration_t(duration_t::YEARS, 1); - } - else if (word == _("daily")) - duration = duration_t(duration_t::DAYS, 1); - else if (word == _("weekly")) - duration = duration_t(duration_t::WEEKS, 1); - else if (word == _("biweekly")) - duration = duration_t(duration_t::WEEKS, 2); - else if (word == _("monthly")) - duration = duration_t(duration_t::MONTHS, 1); - else if (word == _("bimonthly")) - duration = duration_t(duration_t::MONTHS, 2); - else if (word == _("quarterly")) - duration = duration_t(duration_t::MONTHS, 3); - else if (word == _("yearly")) - duration = duration_t(duration_t::YEARS, 1); - else if (word == _("this") || word == _("last") || word == _("next") || - word == _("today")) { - parse_date_words(in, word, *this); - } - else if (word == _("in")) { - read_lower_word(in, word); - parse_date_words(in, word, *this); - } - else if (word == _("from") || word == _("since")) { - read_lower_word(in, word); - parse_date_words(in, word, *this, true, false); - } - else if (word == _("to") || word == _("until")) { - read_lower_word(in, word); - parse_date_words(in, word, *this, false, true); - } - else if (optional<date_time::months_of_year> - m = string_to_month_of_year(word)) { - mon = m; - } - else if (optional<date_time::weekdays> - d = string_to_day_of_week(word)) { - wday = d; - } - else if (all(word, is_digit())) { - year = lexical_cast<unsigned short>(word); - } - else { - // otherwise, it should be an explicit date - date_t b, e; - parse_inclusion_specifier(word, &b, &e); - start = b; - end = e; - } + if (token_cache.kind != token_t::UNKNOWN) { + token_t tok = token_cache; + token_cache = token_t(); + return tok; } - if (year || mon || wday) { - if (! start) - start = CURRENT_DATE(); + while (begin != end && std::isspace(*begin)) + begin++; - if (wday) { - while (start->day_of_week() != *wday) - *start -= gregorian::days(1); + if (begin == end) + return token_t(token_t::END_REACHED); - if (! end) - end = *start + gregorian::days(1); - } else { - bool overwrite_end = false; + 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; + } - if (year) { - start = date_t(*year, 1, 1); - if (! end) { - end = *start + gregorian::years(1); - overwrite_end = true; - } + 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 (...) {} + } - if (mon) { - start = date_t(start->year(), *mon, 1); - if (! end || overwrite_end) - end = *start + gregorian::months(1); + 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])) { + if (term.length() == 4) + return token_t(token_t::TOK_A_YEAR, + token_t::content_t + (lexical_cast<date_specifier_t::year_type>(term))); + else + return token_t(token_t::TOK_INT, + token_t::content_t(lexical_cast<unsigned short>(term))); + } + else if (std::isalpha(term[0])) { + to_lower(term); + + if (optional<date_time::months_of_year> month = + string_to_month_of_year(term)) { + return token_t(token_t::TOK_A_MONTH, token_t::content_t(*month)); } + else if (optional<date_time::weekdays> wday = + string_to_day_of_week(term)) { + 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); + 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 == _("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")) + 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, token_t::content_t(term)); +} + +void date_parser_t::lexer_t::token_t::unexpected() +{ + switch (kind) { + case END_REACHED: + kind = UNKNOWN; + throw_(date_error, _("Unexpected end of expression")); + default: { + string desc = to_string(); + kind = UNKNOWN; + throw_(date_error, _("Unexpected date period token '%1'") << desc); + } + } +} + +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); } } @@ -882,4 +1565,20 @@ 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()); + + out << _("--- Period expression tokens ---") << std::endl; + + date_parser_t::lexer_t::token_t token; + do { + token = lexer.next_token(); + token.dump(out); + out << ": " << token.to_string() << std::endl; + } + while (token.kind != date_parser_t::lexer_t::token_t::END_REACHED); +} + } // namespace ledger diff --git a/src/times.h b/src/times.h index 77f25d9e..826937bb 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) { @@ -85,19 +84,19 @@ string_to_day_of_week(const std::string& str); optional<date_time::months_of_year> string_to_month_of_year(const std::string& str); -datetime_t parse_datetime(const char * str, - optional<date_t::year_type> current_year = none); +typedef optional<date_t::year_type> optional_year; + +datetime_t parse_datetime(const char * str, optional_year current_year = none); inline datetime_t parse_datetime(const std::string& str, - optional<date_t::year_type> current_year = none) { + optional_year current_year = none) { return parse_datetime(str.c_str(), current_year); } -date_t parse_date(const char * str, - optional<date_t::year_type> 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<date_t::year_type> current_year = none) { + optional_year current_year = none) { return parse_date(str.c_str(), current_year); } @@ -138,105 +137,423 @@ inline void to_xml(std::ostream& out, const date_t& when, } } -class date_interval_t : public equality_comparable<date_interval_t> +struct date_traits_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"); + 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) { + 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) { + 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; + 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<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & has_year; + ar & has_month; + ar & has_day; + } +#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(); } - ~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(); - } + } + + 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 << ' '; - 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(); - } + 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<class Archive> + 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; + +#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_type> year; + optional<month_type> month; + optional<day_type> day; + optional<day_of_week_type> wday; + +public: + date_specifier_t(const optional<year_type>& _year = none, + const optional<month_type>& _month = none, + const optional<day_type>& _day = none, + const optional<day_of_week_type>& _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<date_traits_t>& traits = none) { + TRACE_CTOR(date_specifier_t, "date_t, date_traits_t"); + if (! traits || traits->has_year) + year = date.year(); + if (! traits || traits->has_month) + month = date.month(); + 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; + + bool is_within(const date_t& date, + const optional_year& current_year = none) const { + return date >= begin(current_year) && date < end(current_year); + } + + optional<date_duration_t> 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. */ +private: + /** Serialization. */ - friend class boost::serialization::access; + friend class boost::serialization::access; - template<class Archive> - void serialize(Archive& ar, const unsigned int /* version */) { - ar & quantum; - ar & length; + template<class Archive> + 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<date_specifier_t> range_begin; + optional<date_specifier_t> range_end; + + bool end_inclusive; + +public: + date_range_t(const optional<date_specifier_t>& _range_begin = none, + const optional<date_specifier_t>& _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<date_t> begin(const optional_year& current_year = none) const { + if (range_begin) + return range_begin->begin(current_year); + else + return none; + } + optional<date_t> end(const optional_year& current_year = none) const { + 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, + const optional_year& current_year = none) const { + optional<date_t> b = begin(current_year); + optional<date_t> e = end(current_year); + bool after_begin = b ? date >= *b : true; + bool before_end = e ? date < *e : true; + return after_begin && before_end; + } + + string to_string() const { + std::ostringstream out; + + 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) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & range_begin; + ar & range_end; + ar & end_inclusive; + } #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<date_t> start; - bool aligned; - optional<duration_t> skip_duration; - std::size_t factor; - optional<date_t> next; - optional<duration_t> duration; - optional<date_t> end_of_duration; - optional<date_t> end; - - explicit date_interval_t() : aligned(false), factor(1) { +}; + +class date_specifier_or_range_t +{ + typedef variant<int, date_specifier_t, date_range_t> value_type; + + 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<date_t> begin(const optional_year& current_year = none) const { + if (specifier_or_range.type() == typeid(date_specifier_t)) + return boost::get<date_specifier_t>(specifier_or_range).begin(current_year); + else if (specifier_or_range.type() == typeid(date_range_t)) + return boost::get<date_range_t>(specifier_or_range).begin(current_year); + else + return none; + } + optional<date_t> end(const optional_year& current_year = none) const { + if (specifier_or_range.type() == typeid(date_specifier_t)) + return boost::get<date_specifier_t>(specifier_or_range).end(current_year); + else if (specifier_or_range.type() == typeid(date_range_t)) + return boost::get<date_range_t>(specifier_or_range).end(current_year); + else + return none; + } + + + string to_string() const { + std::ostringstream out; + + if (specifier_or_range.type() == typeid(date_specifier_t)) + out << "in" << boost::get<date_specifier_t>(specifier_or_range).to_string(); + else if (specifier_or_range.type() == typeid(date_range_t)) + out << boost::get<date_range_t>(specifier_or_range).to_string(); + + return out.str(); + } + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & specifier_or_range; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + +class date_interval_t : public equality_comparable<date_interval_t> +{ +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<date_specifier_or_range_t> range; + + optional<date_t> start; // the real start, after adjustment + optional<date_t> finish; // the real end, likewise + bool aligned; + optional<date_t> next; + optional<date_duration_t> duration; + optional<date_t> end_of_duration; + + 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); } 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), - factor(other.factor), next(other.next), duration(other.duration), - end_of_duration(other.end_of_duration), - end(other.end) { + end_of_duration(other.end_of_duration) { TRACE_CTOR(date_interval_t, "copy"); } ~date_interval_t() throw() { @@ -252,17 +569,19 @@ public: return is_valid(); } - void parse(std::istream& in); - - void parse(const string& str) { - std::istringstream in(str); - parse(in); + optional<date_t> begin(const optional_year& current_year = none) const { + return start ? start : (range ? range->begin(current_year) : none); } + optional<date_t> end(const optional_year& current_year = none) const { + return finish ? finish : (range ? range->end(current_year) : none); + } + + void parse(const string& str); - void resolve_end(); - void stabilize(const optional<date_t>& date = none); + void resolve_end(); + void stabilize(const optional<date_t>& date = none); - bool is_valid() const { + bool is_valid() const { return start; } @@ -280,6 +599,8 @@ public: date_interval_t& operator++(); + void dump(std::ostream& out, optional_year current_year = none); + #if defined(HAVE_BOOST_SERIALIZATION) private: /** Serialization. */ @@ -288,14 +609,13 @@ private: template<class Archive> void serialize(Archive& ar, const unsigned int /* version */) { + ar & range; ar & start; + ar & finish; ar & aligned; - ar & skip_duration; - ar & factor; ar & next; ar & duration; ar & end_of_duration; - ar & end; } #endif // HAVE_BOOST_SERIALIZATION }; @@ -303,8 +623,9 @@ private: void times_initialize(); void times_shutdown(); -std::ostream& operator<<(std::ostream& out, - const date_interval_t::duration_t& duration); +void show_period_tokens(std::ostream& out, const string& arg); + +std::ostream& operator<<(std::ostream& out, const date_duration_t& duration); } // namespace ledger 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<date_t> 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/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<class _InputIterator> + 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); diff --git a/src/value.cc b/src/value.cc index 3f70ab3d..f4df3329 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<time_duration_t::sec_type>(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<time_duration_t::sec_type> + (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<time_duration_t::sec_type>(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<time_duration_t::sec_type> + (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; @@ -1187,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; } @@ -1216,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; } 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 <Generated Transaction> +; Assets:Brokerage $100 +; Equity:Unrealized Gains +>>>1 + $100.00 Assets + $300.00 Brokerage + $-200.00 Checking + $-100.00 Equity:Unrealized Gains +-------------------- + 0 +>>>2 +=== 0 diff --git a/test/regress/7F3650FD.test b/test/regress/7F3650FD.test index 0eb3a96f..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: <EOF> - start: 09-Dec-01 - end: 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: <EOF> + +--- Before stabilization --- + range: in month Oct day 1 +--- After stabilization --- + range: in month Oct day 1 start: 10-Oct-01 - end: 10-Oct-02 - factor: 1 + finish: 10-Oct-02 + +--- 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: <EOF> + +--- Before stabilization --- + range: in year 2009 month Oct +--- After stabilization --- + range: in year 2009 month Oct start: 09-Oct-01 - end: 09-Nov-01 - factor: 1 + finish: 09-Nov-01 + +--- 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: <EOF> + +--- Before stabilization --- + range: in year 2009 month Oct day 1 +--- After stabilization --- + range: in year 2009 month Oct day 1 start: 09-Oct-01 - end: 09-Oct-02 - factor: 1 + finish: 09-Oct-02 + +--- 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: <EOF> +--- Before stabilization --- + range: in year 2009 + +--- After stabilization --- + range: in year 2009 start: 09-Jan-01 - end: 10-Jan-01 - factor: 1 + finish: 10-Jan-01 + +--- 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 26862703..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: <EOF> +--- Before stabilization --- + range: in year 2008 month Jun + +--- After stabilization --- + range: in year 2008 month Jun start: 08-Jun-01 - end: 08-Jul-01 - factor: 1 + finish: 08-Jul-01 + +--- Sample dates in range (max. 20) --- + 1: 08-Jun-01 >>>2 === 0 |