diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/commodity.cc | 2 | ||||
-rw-r--r-- | src/filters.cc | 105 | ||||
-rw-r--r-- | src/filters.h | 48 | ||||
-rw-r--r-- | src/precmd.cc | 53 | ||||
-rw-r--r-- | src/report.h | 12 | ||||
-rw-r--r-- | src/textual.cc | 2 | ||||
-rw-r--r-- | src/times.cc | 440 | ||||
-rw-r--r-- | src/times.h | 115 | ||||
-rw-r--r-- | src/token.cc | 7 | ||||
-rw-r--r-- | src/xact.h | 6 |
10 files changed, 523 insertions, 267 deletions
diff --git a/src/commodity.cc b/src/commodity.cc index 22c92862..3e4d1160 100644 --- a/src/commodity.cc +++ b/src/commodity.cc @@ -936,7 +936,7 @@ namespace { details.print(name, comm.parent().keep_base); DEBUG("amounts.commodities", "make_qualified_name for " - << comm.qualified_symbol << std::endl << details); + << *comm.qualified_symbol << std::endl << details); DEBUG("amounts.commodities", "qualified_name is " << name.str()); return name.str(); diff --git a/src/filters.cc b/src/filters.cc index d0e345ae..14b18db1 100644 --- a/src/filters.cc +++ b/src/filters.cc @@ -483,41 +483,41 @@ void changed_value_posts::operator()(post_t& post) last_post = &post; } -void subtotal_posts::report_subtotal(const char * spec_fmt, - const date_t& start, - const date_t& finish) +void subtotal_posts::report_subtotal(const char * spec_fmt, + const optional<date_interval_t>& interval) { if (component_posts.empty()) return; - date_t range_start = start; - date_t range_finish = finish; + 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 (! is_valid(range_start) || date < range_start) + if (! range_start || date < *range_start) range_start = date; - if (! is_valid(range_finish) || date > range_finish) + if (! range_finish || date > *range_finish) range_finish = date; } component_posts.clear(); std::ostringstream out_date; if (spec_fmt) { - out_date << format_date(range_finish, string(spec_fmt)); + out_date << format_date(*range_finish, string(spec_fmt)); } else if (date_format) { string fmt = "- "; fmt += *date_format; - out_date << format_date(range_finish, string(fmt)); + out_date << format_date(*range_finish, string(fmt)); } else { - out_date << format_date(range_finish, std::string("- ") + output_date_format); + out_date << format_date(*range_finish, std::string("- ") + output_date_format); } xact_temps.push_back(xact_t()); xact_t& xact = xact_temps.back(); xact.payee = out_date.str(); - xact._date = range_start; + xact._date = *range_start; foreach (values_map::value_type& pair, values) handle_value(pair.second.value, pair.second.account, &xact, post_temps, @@ -556,13 +556,13 @@ void subtotal_posts::operator()(post_t& post) post.reported_account()->xdata().add_flags(ACCOUNT_EXT_HAS_UNB_VIRTUALS); } -void interval_posts::report_subtotal(const date_t& finish) +void interval_posts::report_subtotal(const date_interval_t& interval) { if (last_post && interval) { if (exact_periods) subtotal_posts::report_subtotal(); else - subtotal_posts::report_subtotal(NULL, interval.begin, finish); + subtotal_posts::report_subtotal(NULL, interval); } last_post = NULL; @@ -572,35 +572,22 @@ void interval_posts::operator()(post_t& post) { date_t date = post.date(); - if ((is_valid(interval.begin) && date < interval.begin) || - (is_valid(interval.end) && date >= interval.end)) + if (! interval.find_period(post.date())) return; - if (interval) { - if (! is_valid(interval.begin)) - interval.set_start(date); - start = interval.begin; - - date_t quant = interval.increment(interval.begin); - if (date >= quant) { - if (last_post) - report_subtotal(quant - gregorian::days(1)); - - date_t temp; - while (date >= (temp = interval.increment(quant))) { - if (quant == temp) - break; - interval.begin = quant; - quant = temp; + if (interval.duration) { + if (last_interval && interval != last_interval) { + report_subtotal(last_interval); - if (generate_empty_posts) { + if (generate_empty_posts) { + for (++last_interval; interval != last_interval; ++last_interval) { // Generate a null posting, so the intervening periods can be // seen when -E is used, or if the calculated amount ends up being // non-zero xact_temps.push_back(xact_t()); xact_t& null_xact = xact_temps.back(); null_xact.add_flags(ITEM_TEMP); - null_xact._date = quant - gregorian::days(1); + null_xact._date = last_interval.inclusive_end(); post_temps.push_back(post_t(&empty_account)); post_t& null_post = post_temps.back(); @@ -611,10 +598,14 @@ void interval_posts::operator()(post_t& post) last_post = &null_post; subtotal_posts::operator()(null_post); - report_subtotal(quant - gregorian::days(1)); + report_subtotal(last_interval); } + assert(interval == last_interval); + } else { + last_interval = interval; } - start = interval.begin = quant; + } else { + last_interval = interval; } subtotal_posts::operator()(post); } else { @@ -745,7 +736,7 @@ void generate_posts::add_period_xacts(period_xacts_list& period_xacts) add_post(xact->period, *post); } -void generate_posts::add_post(const interval_t& period, post_t& post) +void generate_posts::add_post(const date_interval_t& period, post_t& post) { pending_posts.push_back(pending_posts_pair(period, &post)); } @@ -759,14 +750,16 @@ void budget_posts::report_budget_items(const date_t& date) do { reported = false; foreach (pending_posts_list::value_type& pair, pending_posts) { - date_t& begin = pair.first.begin; - if (! is_valid(begin)) { - pair.first.set_start(date); - begin = pair.first.begin; + optional<date_t> begin = pair.first.start; + if (! begin) { + if (! pair.first.find_period(date)) + throw_(std::runtime_error, _()"Something odd has happened"); + begin = pair.first.start; } + assert(begin); - if (begin < date && - (! is_valid(pair.first.end) || begin < pair.first.end)) { + if (*begin < date && + (! pair.first.end || *begin < *pair.first.end)) { post_t& post = *pair.second; DEBUG("ledger.walk.budget", "Reporting budget for " @@ -784,7 +777,8 @@ void budget_posts::report_budget_items(const date_t& date) temp.amount.in_place_negate(); xact.add_post(&temp); - begin = pair.first.increment(begin); + ++pair.first; + begin = *pair.first.start; item_handler<post_t>::operator()(temp); @@ -823,17 +817,18 @@ void budget_posts::operator()(post_t& post) } } -void forecast_posts::add_post(const interval_t& period, post_t& post) +void forecast_posts::add_post(const date_interval_t& period, post_t& post) { generate_posts::add_post(period, post); - interval_t& i = pending_posts.back().first; - if (! is_valid(i.begin)) { - i.set_start(CURRENT_DATE()); - i.begin = i.increment(i.begin); + date_interval_t& i = pending_posts.back().first; + if (! i.start) { + if (! i.find_period(CURRENT_DATE())) + throw_(std::runtime_error, _("Something odd has happened")); + ++i; } else { - while (i.begin < CURRENT_DATE()) - i.begin = i.increment(i.begin); + while (*i.start < CURRENT_DATE()) + ++i; } } @@ -847,12 +842,12 @@ void forecast_posts::flush() for (pending_posts_list::iterator i = ++pending_posts.begin(); i != pending_posts.end(); i++) - if ((*i).first.begin < (*least).first.begin) + if (*(*i).first.start < *(*least).first.start) least = i; - date_t& begin = (*least).first.begin; + date_t& begin = *(*least).first.start; - if (is_valid((*least).first.end) && begin >= (*least).first.end) { + if ((*least).first.end && begin >= *(*least).first.end) { pending_posts.erase(least); passed.remove((*least).second); continue; @@ -871,7 +866,9 @@ void forecast_posts::flush() temp.add_flags(ITEM_TEMP); xact.add_post(&temp); - date_t next = (*least).first.increment(begin); + date_t next = *(*least).first.next; + ++(*least).first; + if (next < begin || (is_valid(last) && (next - last).days() > 365 * 5)) break; begin = next; diff --git a/src/filters.h b/src/filters.h index d3968c51..14b97c23 100644 --- a/src/filters.h +++ b/src/filters.h @@ -539,9 +539,8 @@ public: clear_xacts_posts(xact_temps); } - void report_subtotal(const char * spec_fmt = NULL, - const date_t& start = date_t(), - const date_t& finish = date_t()); + void report_subtotal(const char * spec_fmt = NULL, + const optional<date_interval_t>& interval = none); virtual void flush() { if (values.size() > 0) @@ -558,22 +557,22 @@ public: */ class interval_posts : public subtotal_posts { - interval_t interval; - post_t * last_post; - account_t empty_account; - bool exact_periods; - bool generate_empty_posts; - date_t start; + date_interval_t interval; + date_interval_t last_interval; + post_t * last_post; + account_t empty_account; + bool exact_periods; + bool generate_empty_posts; interval_posts(); public: - interval_posts(post_handler_ptr _handler, - expr_t& amount_expr, - const interval_t& _interval, - bool _exact_periods = false, - bool _generate_empty_posts = false) + interval_posts(post_handler_ptr _handler, + expr_t& amount_expr, + const date_interval_t& _interval, + bool _exact_periods = false, + bool _generate_empty_posts = false) : subtotal_posts(_handler, amount_expr), interval(_interval), last_post(NULL), empty_account(NULL, _("<None>")), exact_periods(_exact_periods), @@ -585,19 +584,20 @@ public: TRACE_DTOR(interval_posts); } - void report_subtotal(const date_t& finish); + void report_subtotal(const date_interval_t& interval); virtual void flush() { - if (last_post && interval) - report_subtotal(interval.increment(interval.begin) - gregorian::days(1)); - subtotal_posts::flush(); + if (last_post && interval.duration) { + if (interval.is_valid()) + report_subtotal(interval); + subtotal_posts::flush(); + } } virtual void operator()(post_t& post); }; class posts_as_equity : public subtotal_posts { - interval_t interval; post_t * last_post; account_t equity_account; account_t * balance_account; @@ -726,11 +726,11 @@ class generate_posts : public item_handler<post_t> generate_posts(); protected: - typedef std::pair<interval_t, post_t *> pending_posts_pair; - typedef std::list<pending_posts_pair> pending_posts_list; + typedef std::pair<date_interval_t, post_t *> pending_posts_pair; + typedef std::list<pending_posts_pair> pending_posts_list; pending_posts_list pending_posts; - std::list<xact_t> xact_temps; + std::list<xact_t> xact_temps; std::list<post_t> post_temps; public: @@ -746,7 +746,7 @@ public: void add_period_xacts(period_xacts_list& period_xacts); - virtual void add_post(const interval_t& period, post_t& post); + virtual void add_post(const date_interval_t& period, post_t& post); }; /** @@ -801,7 +801,7 @@ class forecast_posts : public generate_posts TRACE_DTOR(forecast_posts); } - virtual void add_post(const interval_t& period, post_t& post); + virtual void add_post(const date_interval_t& period, post_t& post); virtual void flush(); }; diff --git a/src/precmd.cc b/src/precmd.cc index ef04cb79..1160cc64 100644 --- a/src/precmd.cc +++ b/src/precmd.cc @@ -160,25 +160,54 @@ value_t period_command(call_scope_t& args) report_t& report(find_scope<report_t>(args)); std::ostream& out(report.output_stream); - interval_t interval(arg); + date_interval_t interval(arg); + + 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; - if (! is_valid(interval.begin)) { - out << _("Time period has no beginning.") << std::endl; - } else { - out << _("begin: ") << format_date(interval.begin) << std::endl; - out << _(" end: ") << format_date(interval.end) << std::endl; out << std::endl; - date_t date = interval.first(); - - for (int i = 0; i < 20; i++) { + for (int i = 0; i < 20 && interval; i++, ++interval) { out << std::right; out.width(2); - out << i << "): " << format_date(date) << std::endl; + out << i << "): " << format_date(*interval.start); + if (interval.end_of_duration) + out << " -- " << format_date(*interval.inclusive_end()); + out << std::endl; - date = interval.increment(date); - if (is_valid(interval.end) && date >= interval.end) + if (! interval.skip_duration) break; } } diff --git a/src/report.h b/src/report.h index 55c37b22..5eb2a706 100644 --- a/src/report.h +++ b/src/report.h @@ -240,14 +240,14 @@ public: }); OPTION_(report_t, begin_, DO_(args) { // -b - interval_t interval(args[0].to_string()); - if (! is_valid(interval.begin)) + date_interval_t interval(args[0].to_string()); + if (! interval.start) throw_(std::invalid_argument, _("Could not determine beginning of period '%1'") << args[0].to_string()); string predicate = - "date>=[" + to_iso_extended_string(interval.begin) + "]"; + "date>=[" + to_iso_extended_string(*interval.start) + "]"; parent->HANDLER(limit_).on(predicate); }); @@ -355,14 +355,14 @@ public: OPTION(report_t, empty); // -E OPTION_(report_t, end_, DO_(args) { // -e - interval_t interval(args[0].to_string()); - if (! is_valid(interval.begin)) + date_interval_t interval(args[0].to_string()); + if (! interval.start) throw_(std::invalid_argument, _("Could not determine end of period '%1'") << args[0].to_string()); string predicate = - "date<[" + to_iso_extended_string(interval.begin) + "]"; + "date<[" + to_iso_extended_string(*interval.start) + "]"; parent->HANDLER(limit_).on(predicate); #if 0 terminus = interval.begin; diff --git a/src/textual.cc b/src/textual.cc index 8f7388a2..0c92f7bb 100644 --- a/src/textual.cc +++ b/src/textual.cc @@ -592,8 +592,6 @@ void instance_t::period_xact_directive(char * line) try { std::auto_ptr<period_xact_t> pe(new period_xact_t(skip_ws(line + 1))); - if (! pe->period) - throw_(parse_error, _("Parsing time period '%1'") << line); reveal_context = false; diff --git a/src/times.cc b/src/times.cc index d83b6baa..4bf5241b 100644 --- a/src/times.cc +++ b/src/times.cc @@ -107,25 +107,25 @@ namespace { } } -int string_to_day_of_week(const std::string& str) +date_time::weekdays string_to_day_of_week(const std::string& str) { if (str == _("sun") || str == _("sunday") || str == "0") - return 0; + return gregorian::Sunday; else if (str == _("mon") || str == _("monday") || str == "1") - return 1; + return gregorian::Monday; else if (str == _("tue") || str == _("tuesday") || str == "2") - return 2; + return gregorian::Tuesday; else if (str == _("wed") || str == _("wednesday") || str == "3") - return 3; + return gregorian::Wednesday; else if (str == _("thu") || str == _("thursday") || str == "4") - return 4; + return gregorian::Thursday; else if (str == _("fri") || str == _("friday") || str == "5") - return 5; + return gregorian::Friday; else if (str == _("sat") || str == _("saturday") || str == "6") - return 6; + return gregorian::Saturday; assert(false); - return -1; + return gregorian::Sunday; } datetime_t parse_datetime(const char * str, int) @@ -145,50 +145,260 @@ date_t parse_date(const char * str, int current_year) return gregorian::date_from_tm(when); } -date_t interval_t::first(const optional<date_t>& moment) +date_t date_interval_t::add_duration(const date_t& date, + const duration_t& duration) { - if (! is_valid(begin)) { - // Find an efficient starting point for the upcoming while loop. We want - // a date early enough that the range will be correct, but late enough - // that we don't spend hundreds of thousands of loops skipping through - // time. - assert(moment); - - if (months > 0 || years > 0) { - begin = date_t(moment->year(), gregorian::Jan, 1); - } else { - begin = date_t(*moment - gregorian::days(400)); + if (duration.type() == typeid(gregorian::days)) + return date + boost::get<gregorian::days>(duration); + else if (duration.type() == typeid(gregorian::weeks)) + return date + boost::get<gregorian::weeks>(duration); + else if (duration.type() == typeid(gregorian::months)) + return date + boost::get<gregorian::months>(duration); + else + assert(duration.type() == typeid(gregorian::years)); + return date + boost::get<gregorian::years>(duration); +} + +date_t date_interval_t::subtract_duration(const date_t& date, + const duration_t& duration) +{ + if (duration.type() == typeid(gregorian::days)) + return date - boost::get<gregorian::days>(duration); + else if (duration.type() == typeid(gregorian::weeks)) + return date - boost::get<gregorian::weeks>(duration); + else if (duration.type() == typeid(gregorian::months)) + return date - boost::get<gregorian::months>(duration); + else + assert(duration.type() == typeid(gregorian::years)); + return date - boost::get<gregorian::years>(duration); +} + +std::ostream& operator<<(std::ostream& out, + const date_interval_t::duration_t& duration) +{ + if (duration.type() == typeid(gregorian::days)) + out << boost::get<gregorian::days>(duration).days() + << " day(s)"; + else if (duration.type() == typeid(gregorian::weeks)) + out << (boost::get<gregorian::weeks>(duration).days() / 7) + << " week(s)"; + else if (duration.type() == typeid(gregorian::months)) + out << boost::get<gregorian::months>(duration).number_of_months() + << " month(s)"; + else { + assert(duration.type() == typeid(gregorian::years)); + out << boost::get<gregorian::years>(duration).number_of_years() + << " year(s)"; + } + return out; +} + +void date_interval_t::resolve_end() +{ + if (start && ! end_of_duration) { + end_of_duration = add_duration(*start, *duration); + DEBUG("times.interval", + "stabilize: end_of_duration = " << *end_of_duration); + } + + if (end && *end_of_duration > *end) { + end_of_duration = end; + DEBUG("times.interval", + "stabilize: end_of_duration reset to end: " << *end_of_duration); + } + + if (! skip_duration) { + skip_duration = duration; + DEBUG("times.interval", + "stabilize: skip_duration set to: " << *skip_duration); + } + + if (start && ! next) { + next = add_duration(*start, *skip_duration); + DEBUG("times.interval", + "stabilize: next set to: " << *next); + } +} - // jww (2009-02-21): Add support for starting a week on any day - if (weekly) { // move it to a Sunday - while (begin.day_of_week() != start_of_week) - begin += gregorian::days(1); +void date_interval_t::stabilize(const optional<date_t>& date) +{ +#if defined(DEBUG_ON) + if (date) + DEBUG("times.interval", "stabilize: with date = " << *date); +#endif + + if (date && ! aligned) { + DEBUG("times.interval", "stabilize: date passed, but not aligned"); + if (duration) { + DEBUG("times.interval", + "stabilize: aligning with a duration: " << *duration); + + // The interval object has not been seeded with a start date yet, so + // find the nearest period before on on date which fits, if possible. + // + // Find an efficient starting point for the upcoming while loop. We + // want a date early enough that the range will be correct, but late + // enough that we don't spend hundreds of thousands of loops skipping + // through time. + optional<date_t> initial_start = start; + optional<date_t> initial_end = end; + +#if defined(DEBUG_ON) + if (initial_start) + DEBUG("times.interval", + "stabilize: initial_start = " << *initial_start); + if (initial_end) + DEBUG("times.interval", + "stabilize: initial_end = " << *initial_end); +#endif + + date_t when = start ? *start : *date; + + if (duration->type() == typeid(gregorian::months) || + duration->type() == typeid(gregorian::years)) { + DEBUG("times.interval", "stabilize: monthly or yearly duration"); + + start = date_t(when.year(), gregorian::Jan, 1); + } else { + DEBUG("times.interval", "stabilize: daily or weekly duration"); + + start = date_t(when - gregorian::days(400)); + + if (duration->type() == typeid(gregorian::weeks)) { + // Move it to a Sunday + while (start->day_of_week() != start_of_week) + *start += gregorian::days(1); + } + } + + DEBUG("times.interval", + "stabilize: beginning start date = " << *start); + + while (*start < *date) { + date_interval_t next_interval(*this); + ++next_interval; + + if (next_interval.start && *next_interval.start < *date) { + *this = next_interval; + } else { + end_of_duration = none; + next = none; + break; + } + } + + DEBUG("times.interval", "stabilize: final start date = " << *start); + + if (initial_start && (! start || *start < *initial_start)) { + resolve_end(); + + start = initial_start; + DEBUG("times.interval", "stabilize: start reset to initial start"); + } + if (initial_end && (! end || *end > *initial_end)) { + end = initial_end; + DEBUG("times.interval", "stabilize: end reset to initial end"); } } + aligned = true; + } + + // If there is no duration, then if we've reached here the date falls + // between begin and end. + if (! duration) { + DEBUG("times.interval", "stabilize: there was no duration given"); + + if (! start && ! end) + throw_(date_error, + _("Invalid date interval: neither start, nor end, nor duration")); + } else { + resolve_end(); + } +} + +bool date_interval_t::find_period(const date_t& date) +{ + stabilize(date); + + if (end && date > *end) { + DEBUG("times.interval", + "false: date [" << date << "] > end [" << *end << "]"); + return false; } - date_t quant(begin); + if (! start) { + throw_(std::runtime_error, _("Date interval is improperly initialized")); + } + else if (date < *start) { + DEBUG("times.interval", + "false: date [" << date << "] < start [" << *start << "]"); + return false; + } + + if (end_of_duration && date < *end_of_duration) { + DEBUG("times.interval", + "true: date [" << date << "] < end_of_duration [" + << *end_of_duration << "]"); + return true; + } + + // If we've reached here, it means the date does not fall into the current + // interval, so we must seek another interval that does match -- unless we + // pass by date in so doing, which means we shouldn't alter the current + // period of the interval at all. + + date_t scan = *start; + date_t end_of_scan = *end_of_duration; - if (moment && *moment >= quant) { - date_t temp; - while (*moment >= (temp = increment(quant))) { - if (quant == temp) - break; - quant = temp; + DEBUG("times.interval", "date = " << date); + DEBUG("times.interval", "scan = " << scan); + DEBUG("times.interval", "end_of_scan = " << end_of_scan); + + while (date >= scan && (! end || scan < *end)) { + if (date < end_of_scan) { + start = scan; + end_of_duration = end_of_scan; + next = none; + + DEBUG("times.interval", "true: start = " << *start); + DEBUG("times.interval", "true: end_of_duration = " << *end_of_duration); + + return true; } + + scan = add_duration(scan, *skip_duration); + end_of_scan = add_duration(scan, *duration); } - return quant; + + return false; } -date_t interval_t::increment(const date_t& moment) const +date_interval_t& date_interval_t::operator++() { - date_t future(moment); + if (! start) + throw_(date_error, _("Cannot increment an unstarted date interval")); + + stabilize(); + + if (! skip_duration || ! duration) + throw_(date_error, + _("Cannot increment a date interval without a duration")); + + assert(next); + + if (end && *next >= *end) { + start = none; + } else { + start = *next; + + end_of_duration = add_duration(*start, *duration); + } + + next = none; - if (years) future += gregorian::years(years); - if (months) future += gregorian::months(months); - if (days) future += gregorian::days(days); + resolve_end(); - return future; + return *this; } namespace { @@ -230,9 +440,15 @@ namespace { if (begin) { *begin = gregorian::date_from_tm(when); - if (end) - *end = interval_t(saw_day ? 1 : 0, saw_mon ? 1 : 0, - saw_year ? 1 : 0).increment(*begin); + + if (end) { + if (saw_year) + *end = *begin + gregorian::years(1); + else if (saw_mon) + *end = *begin + gregorian::months(1); + else if (saw_day) + *end = *begin + gregorian::days(1); + } } else if (end) { *end = gregorian::date_from_tm(when); @@ -245,14 +461,14 @@ namespace { word[i] = static_cast<char>(std::tolower(word[i])); } - void parse_date_words(std::istream& in, string& word, - date_t * begin, date_t * end) + 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; - bool mon_spec = false; - char buf[32]; - if (word == _("this") || word == _("last") || word == _("next")) { type = word; if (! in.eof()) @@ -263,59 +479,48 @@ namespace { type = _("this"); } - if (word == _("month")) { - time_t now = to_time_t(CURRENT_TIME()); - std::strftime(buf, 31, "%B", localtime(&now)); - word = buf; - mon_spec = true; + date_t start = CURRENT_DATE(); + date_t end; + bool parse_specifier = false; + + date_interval_t::duration_t duration; + + assert(look_for_start || look_for_end); + + if (word == _("year")) { + duration = gregorian::years(1); + start = gregorian::date(start.year(), 1, 1); } - else if (word == _("year")) { - int year = CURRENT_DATE().year(); - std::sprintf(buf, "%04d", year); - word = buf; + else if (word == _("month")) { + duration = gregorian::months(1); + start = gregorian::date(start.year(), start.month(), 1); } - else if (word == _("today")) { - if (begin) - *begin = CURRENT_DATE(); - if (end) { - *end = CURRENT_DATE(); - *end += gregorian::days(1); - } - return; + else if (word == _("today") || word == _("day")) { + duration = gregorian::days(1); } + else { + parse_specifier = true; + } + end = date_interval_t::add_duration(start, duration); - parse_inclusion_specifier(word, begin, end); + if (parse_specifier) + parse_inclusion_specifier(word, &start, &end); if (type == _("last")) { - if (mon_spec) { - if (begin) - *begin = interval_t(0, -1, 0).increment(*begin); - if (end) - *end = interval_t(0, -1, 0).increment(*end); - } else { - if (begin) - *begin = interval_t(0, 0, -1).increment(*begin); - if (end) - *end = interval_t(0, 0, -1).increment(*end); - } + start = date_interval_t::subtract_duration(start, duration); + end = date_interval_t::subtract_duration(end, duration); } else if (type == _("next")) { - if (mon_spec) { - if (begin) - *begin = interval_t(0, 1, 0).increment(*begin); - if (end) - *end = interval_t(0, 1, 0).increment(*end); - } else { - if (begin) - *begin = interval_t(0, 0, 1).increment(*begin); - if (end) - *end = interval_t(0, 0, 1).increment(*end); - } + start = date_interval_t::add_duration(start, duration); + end = date_interval_t::add_duration(end, duration); } + + if (look_for_start) interval.start = start; + if (look_for_end) interval.end = end; } } -void interval_t::parse(std::istream& in) +void date_interval_t::parse(std::istream& in) { string word; @@ -327,65 +532,62 @@ void interval_t::parse(std::istream& in) int quantity = lexical_cast<int>(word); read_lower_word(in, word); if (word == _("days")) - days = quantity; - else if (word == _("weeks")) { - days = 7 * quantity; - weekly = true; - } + duration = gregorian::days(quantity); + else if (word == _("weeks")) + duration = gregorian::weeks(quantity); else if (word == _("months")) - months = quantity; + duration = gregorian::months(quantity); else if (word == _("quarters")) - months = 3 * quantity; + duration = gregorian::months(3 * quantity); else if (word == _("years")) - years = quantity; + duration = gregorian::years(quantity); } else if (word == _("day")) - days = 1; - else if (word == _("week")) { - days = 7; - weekly = true; - } + duration = gregorian::days(1); + else if (word == _("week")) + duration = gregorian::weeks(1); else if (word == _("month")) - months = 1; + duration = gregorian::months(1); else if (word == _("quarter")) - months = 3; + duration = gregorian::months(3); else if (word == _("year")) - years = 1; + duration = gregorian::years(1); } else if (word == _("daily")) - days = 1; - else if (word == _("weekly")) { - days = 7; - weekly = true; - } + duration = gregorian::days(1); + else if (word == _("weekly")) + duration = gregorian::weeks(1); else if (word == _("biweekly")) - days = 14; + duration = gregorian::weeks(2); else if (word == _("monthly")) - months = 1; + duration = gregorian::months(1); else if (word == _("bimonthly")) - months = 2; + duration = gregorian::months(2); else if (word == _("quarterly")) - months = 3; + duration = gregorian::months(3); else if (word == _("yearly")) - years = 1; + duration = gregorian::years(1); else if (word == _("this") || word == _("last") || word == _("next") || word == _("today")) { - parse_date_words(in, word, &begin, &end); + parse_date_words(in, word, *this); } else if (word == _("in")) { read_lower_word(in, word); - parse_date_words(in, word, &begin, &end); + parse_date_words(in, word, *this); } else if (word == _("from") || word == _("since")) { read_lower_word(in, word); - parse_date_words(in, word, &begin, NULL); + parse_date_words(in, word, *this, true, false); } else if (word == _("to") || word == _("until")) { read_lower_word(in, word); - parse_date_words(in, word, NULL, &end); + parse_date_words(in, word, *this, false, true); } else { - parse_inclusion_specifier(word, &begin, &end); + date_t b, e; + parse_inclusion_specifier(word, &b, &e); + start = b; + end = e; } } } diff --git a/src/times.h b/src/times.h index b83c65b5..9750bbfb 100644 --- a/src/times.h +++ b/src/times.h @@ -60,6 +60,7 @@ 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) { return ! moment.is_not_a_date(); @@ -75,7 +76,7 @@ inline bool is_valid(const date_t& moment) { extern int start_of_week; extern optional<std::string> input_date_format; -int string_to_day_of_week(const std::string& str); +date_time::weekdays string_to_day_of_week(const std::string& str); datetime_t parse_datetime(const char * str, int current_year = -1); @@ -123,65 +124,91 @@ inline std::string format_date(const date_t& when, return buf; } -/** - * @brief Brief - * - * Long. - */ -struct interval_t +class date_interval_t : public equality_comparable<date_interval_t> { - int years; - int months; - int days; - bool weekly; - date_t begin; - date_t end; - - interval_t(int _days = 0, - int _months = 0, - int _years = 0, - bool _weekly = false, - const date_t& _begin = date_t(), - const date_t& _end = date_t()) - : years(_years), months(_months), days(_days), - weekly(_weekly), begin(_begin), end(_end) { - TRACE_CTOR(interval_t, - "int, int, int, bool, const date_t&, const date_t&"); +public: + typedef variant<gregorian::days, + gregorian::weeks, + gregorian::months, + gregorian::years> duration_t; + + 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) { + TRACE_CTOR(date_interval_t, ""); } - interval_t(const interval_t& other) - : years(other.years), - months(other.months), - days(other.days), - weekly(other.weekly), - begin(other.begin), + date_interval_t(const string& str) : aligned(false), factor(1) { + TRACE_CTOR(date_interval_t, "const string&"); + parse(str); + } + date_interval_t(const date_interval_t& other) + : start(other.start), + 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) { - TRACE_CTOR(interval_t, "copy"); + TRACE_CTOR(date_interval_t, "copy"); } - interval_t(const string& desc) - : years(0), months(0), days(0), weekly(false), begin(), end() { - TRACE_CTOR(interval_t, "const string&"); - std::istringstream stream(desc); - parse(stream); + ~date_interval_t() throw() { + TRACE_DTOR(date_interval_t); } - ~interval_t() throw() { - TRACE_DTOR(interval_t); + bool operator==(const date_interval_t& other) const { + return (start == other.start && + (! start || *start == *other.start)); } operator bool() const { - return years != 0 || months != 0 || days != 0; + return is_valid(); } - void set_start(const date_t& moment) { - begin = first(moment); + void parse(std::istream& in); + + void parse(const string& str) { + std::istringstream in(str); + parse(in); } - date_t first(const optional<date_t>& moment = none); - date_t increment(const date_t&) const; + void resolve_end(); + void stabilize(const optional<date_t>& date = none); - void parse(std::istream& in); + bool is_valid() const { + return start; + } + + /** Find the current or next period containing date. Returns true if the + date_interval_t object has been altered to reflect the interval + containing date, or false if no such period can be found. */ + bool find_period(const date_t& date); + + optional<date_t> inclusive_end() const { + if (end_of_duration) + return *end_of_duration - gregorian::days(1); + else + return none; + } + + date_interval_t& operator++(); }; +std::ostream& operator<<(std::ostream& out, + const date_interval_t::duration_t& duration); + } // namespace ledger #endif // _TIMES_H diff --git a/src/token.cc b/src/token.cc index b7c19ceb..fde6a0e3 100644 --- a/src/token.cc +++ b/src/token.cc @@ -205,9 +205,12 @@ void expr_t::token_t::next(std::istream& in, const uint_least8_t pflags) in.get(c); length++; - interval_t timespan(buf); + date_interval_t timespan(buf); + if (! timespan) + throw_(parse_error, + _("Date specifier does not refer to a starting date")); kind = VALUE; - value = timespan.first(); + value = *timespan.start; break; } @@ -186,8 +186,8 @@ struct auto_xact_finalizer_t : public xact_finalizer_t class period_xact_t : public xact_base_t { public: - interval_t period; - string period_string; + date_interval_t period; + string period_string; period_xact_t() { TRACE_CTOR(period_xact_t, ""); @@ -206,7 +206,7 @@ class period_xact_t : public xact_base_t } virtual bool valid() const { - return period; + return period.is_valid(); } }; |