summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Wiegley <johnw@newartisans.com>2009-11-19 03:37:16 -0500
committerJohn Wiegley <johnw@newartisans.com>2009-11-19 03:37:16 -0500
commitcc9110a43a1e2d006de8d24a84bbfb2c6918cf33 (patch)
tree4230ae03ebea8e2cbc0b6e1de821730e4819af47
parent6b557f810e1c6eb2dcd8a24eeeacfcf9cc8210e3 (diff)
parent63fee4c83775f79364199ea547dbc7e068b0abc8 (diff)
downloadfork-ledger-cc9110a43a1e2d006de8d24a84bbfb2c6918cf33.tar.gz
fork-ledger-cc9110a43a1e2d006de8d24a84bbfb2c6918cf33.tar.bz2
fork-ledger-cc9110a43a1e2d006de8d24a84bbfb2c6918cf33.zip
Merge branch 'next'
-rw-r--r--src/account.h7
-rw-r--r--src/archive.cc2
-rw-r--r--src/chain.cc36
-rw-r--r--src/chain.h2
-rw-r--r--src/commodity.cc3
-rw-r--r--src/filters.cc146
-rw-r--r--src/filters.h37
-rw-r--r--src/precmd.cc77
-rw-r--r--src/query.cc4
-rw-r--r--src/query.h72
-rw-r--r--src/report.cc44
-rw-r--r--src/report.h34
-rw-r--r--src/times.cc1209
-rw-r--r--src/times.h519
-rw-r--r--src/token.cc5
-rw-r--r--src/utils.h5
-rw-r--r--src/value.cc36
-rw-r--r--test/baseline/opt-unrealized.test20
-rw-r--r--test/regress/7F3650FD.test77
-rw-r--r--test/regress/BBFA1759.test16
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