1 /*
2 * Copyright (c) 2003-2019, John Wiegley. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * - Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * - Neither the name of New Artisans LLC nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 #include <system.hh>
33
34 #include "times.h"
35
36 #if defined(_WIN32) || defined(__CYGWIN__)
37 #include "strptime.h"
38 #endif
39
40 namespace ledger {
41
42 optional<datetime_t> epoch;
43
44 date_time::weekdays start_of_week = gregorian::Sunday;
45
46 namespace {
47 template <typename T, typename InputFacetType, typename OutputFacetType>
48 class temporal_io_t : public noncopyable
49 {
50 string fmt_str;
51
52 public:
53 date_traits_t traits;
54 bool input;
55
temporal_io_t(const char * _fmt_str,bool _input)56 temporal_io_t(const char * _fmt_str, bool _input)
57 : fmt_str(_fmt_str),
58 traits(icontains(fmt_str, "%F") || icontains(fmt_str, "%y"),
59 icontains(fmt_str, "%F") || icontains(fmt_str, "%m") || icontains(fmt_str, "%b"),
60 icontains(fmt_str, "%F") || icontains(fmt_str, "%d")),
61 input(_input) {
62 }
63
set_format(const char * fmt)64 void set_format(const char * fmt) {
65 fmt_str = fmt;
66 traits = date_traits_t(icontains(fmt_str, "%F") || icontains(fmt_str, "%y"),
67 icontains(fmt_str, "%F") ||
68 icontains(fmt_str, "%m") || icontains(fmt_str, "%b"),
69 icontains(fmt_str, "%F") || icontains(fmt_str, "%d"));
70 }
71
parse(const char *)72 T parse(const char *) {}
73
format(const T & when)74 std::string format(const T& when) {
75 std::tm data(to_tm(when));
76 char buf[128];
77 std::strftime(buf, 127, fmt_str.c_str(), &data);
78 return buf;
79 }
80 };
81
82 template <>
83 datetime_t temporal_io_t<datetime_t, posix_time::time_input_facet,
84 posix_time::time_facet>
parse(const char * str)85 ::parse(const char * str)
86 {
87 std::tm data;
88 std::memset(&data, 0, sizeof(std::tm));
89 if (strptime(str, fmt_str.c_str(), &data))
90 return posix_time::ptime_from_tm(data);
91 else
92 return datetime_t();
93 }
94
95 template <>
96 date_t temporal_io_t<date_t, gregorian::date_input_facet,
97 gregorian::date_facet>
parse(const char * str)98 ::parse(const char * str)
99 {
100 std::tm data;
101 std::memset(&data, 0, sizeof(std::tm));
102 data.tm_year = CURRENT_DATE().year() - 1900;
103 data.tm_mday = 1; // some formats have no day
104 if (strptime(str, fmt_str.c_str(), &data))
105 return gregorian::date_from_tm(data);
106 else
107 return date_t();
108 }
109
110 typedef temporal_io_t<datetime_t, posix_time::time_input_facet,
111 posix_time::time_facet> datetime_io_t;
112 typedef temporal_io_t<date_t, gregorian::date_input_facet,
113 gregorian::date_facet> date_io_t;
114
115 shared_ptr<datetime_io_t> input_datetime_io;
116 shared_ptr<datetime_io_t> timelog_datetime_io;
117 shared_ptr<datetime_io_t> written_datetime_io;
118 shared_ptr<date_io_t> written_date_io;
119 shared_ptr<datetime_io_t> printed_datetime_io;
120 shared_ptr<date_io_t> printed_date_io;
121
122 std::deque<shared_ptr<date_io_t> > readers;
123
124 bool convert_separators_to_slashes = true;
125
parse_date_mask_routine(const char * date_str,date_io_t & io,date_traits_t * traits=NULL)126 date_t parse_date_mask_routine(const char * date_str, date_io_t& io,
127 date_traits_t * traits = NULL)
128 {
129 if (std::strlen(date_str) > 127) {
130 throw_(date_error, _f("Invalid date: %1%") % date_str);
131 }
132
133 char buf[128];
134 std::strcpy(buf, date_str);
135
136 if (convert_separators_to_slashes) {
137 for (char * p = buf; *p; p++)
138 if (*p == '.' || *p == '-')
139 *p = '/';
140 }
141
142 date_t when = io.parse(buf);
143
144 if (! when.is_not_a_date()) {
145 DEBUG("times.parse", "Passed date string: " << date_str);
146 DEBUG("times.parse", "Parsed date string: " << buf);
147 DEBUG("times.parse", "Parsed result is: " << when);
148 DEBUG("times.parse", "Formatted result is: " << io.format(when));
149
150 string when_str = io.format(when);
151
152 const char * p = when_str.c_str();
153 const char * q = buf;
154 for (; *p && *q; p++, q++) {
155 if (*p != *q && *p == '0') p++;
156 if (! *p || *p != *q) break;
157 }
158 if (*p != '\0' || *q != '\0')
159 throw_(date_error, _f("Invalid date: %1%") % date_str);
160
161 if (traits)
162 *traits = io.traits;
163
164 if (! io.traits.has_year) {
165 when = date_t(CURRENT_DATE().year(), when.month(), when.day());
166
167 if (when.month() > CURRENT_DATE().month())
168 when -= gregorian::years(1);
169 }
170 }
171 return when;
172 }
173
parse_date_mask(const char * date_str,date_traits_t * traits=NULL)174 date_t parse_date_mask(const char * date_str, date_traits_t * traits = NULL)
175 {
176 foreach (shared_ptr<date_io_t>& reader, readers) {
177 date_t when = parse_date_mask_routine(date_str, *reader.get(), traits);
178 if (! when.is_not_a_date())
179 return when;
180 }
181
182 throw_(date_error, _f("Invalid date: %1%") % date_str);
183 return date_t();
184 }
185 }
186
string_to_day_of_week(const std::string & str)187 optional<date_time::weekdays> string_to_day_of_week(const std::string& str)
188 {
189 if (str == _("sun") || str == _("sunday") || str == "0")
190 return gregorian::Sunday;
191 else if (str == _("mon") || str == _("monday") || str == "1")
192 return gregorian::Monday;
193 else if (str == _("tue") || str == _("tuesday") || str == "2")
194 return gregorian::Tuesday;
195 else if (str == _("wed") || str == _("wednesday") || str == "3")
196 return gregorian::Wednesday;
197 else if (str == _("thu") || str == _("thursday") || str == "4")
198 return gregorian::Thursday;
199 else if (str == _("fri") || str == _("friday") || str == "5")
200 return gregorian::Friday;
201 else if (str == _("sat") || str == _("saturday") || str == "6")
202 return gregorian::Saturday;
203 else
204 return none;
205 }
206
207 optional<date_time::months_of_year>
string_to_month_of_year(const std::string & str)208 string_to_month_of_year(const std::string& str)
209 {
210 if (str == _("jan") || str == _("january") || str == "0")
211 return gregorian::Jan;
212 else if (str == _("feb") || str == _("february") || str == "1")
213 return gregorian::Feb;
214 else if (str == _("mar") || str == _("march") || str == "2")
215 return gregorian::Mar;
216 else if (str == _("apr") || str == _("april") || str == "3")
217 return gregorian::Apr;
218 else if (str == _("may") || str == _("may") || str == "4")
219 return gregorian::May;
220 else if (str == _("jun") || str == _("june") || str == "5")
221 return gregorian::Jun;
222 else if (str == _("jul") || str == _("july") || str == "6")
223 return gregorian::Jul;
224 else if (str == _("aug") || str == _("august") || str == "7")
225 return gregorian::Aug;
226 else if (str == _("sep") || str == _("september") || str == "8")
227 return gregorian::Sep;
228 else if (str == _("oct") || str == _("october") || str == "9")
229 return gregorian::Oct;
230 else if (str == _("nov") || str == _("november") || str == "10")
231 return gregorian::Nov;
232 else if (str == _("dec") || str == _("december") || str == "11")
233 return gregorian::Dec;
234 else
235 return none;
236 }
237
parse_datetime(const char * str)238 datetime_t parse_datetime(const char * str)
239 {
240 if (std::strlen(str) > 127) {
241 throw_(date_error, _f("Invalid date: %1%") % str);
242 }
243
244 char buf[128];
245 std::strcpy(buf, str);
246
247 for (char * p = buf; *p; p++)
248 if (*p == '.' || *p == '-')
249 *p = '/';
250
251 datetime_t when = input_datetime_io->parse(buf);
252 if (when.is_not_a_date_time()) {
253 when = timelog_datetime_io->parse(buf);
254 if (when.is_not_a_date_time()) {
255 throw_(date_error, _f("Invalid date/time: %1%") % str);
256 }
257 }
258 return when;
259 }
260
parse_date(const char * str)261 date_t parse_date(const char * str)
262 {
263 return parse_date_mask(str);
264 }
265
begin() const266 date_t date_specifier_t::begin() const
267 {
268 year_type the_year = year ? *year : year_type(CURRENT_DATE().year());
269 month_type the_month = month ? *month : date_t::month_type(1);
270 day_type the_day = day ? *day : date_t::day_type(1);
271
272 #if !NO_ASSERTS
273 if (day)
274 assert(! wday);
275 else if (wday)
276 assert(! day);
277 #endif
278
279 // jww (2009-11-16): Handle wday. If a month is set, find the most recent
280 // wday in that month; if the year is set, then in that year.
281
282 return gregorian::date(static_cast<date_t::year_type>(the_year),
283 static_cast<date_t::month_type>(the_month),
284 static_cast<date_t::day_type>(the_day));
285 }
286
end() const287 date_t date_specifier_t::end() const
288 {
289 if (day || wday)
290 return begin() + gregorian::days(1);
291 else if (month)
292 return begin() + gregorian::months(1);
293 else if (year)
294 return begin() + gregorian::years(1);
295 else {
296 assert(false);
297 return date_t();
298 }
299 }
300
operator <<(std::ostream & out,const date_duration_t & duration)301 std::ostream& operator<<(std::ostream& out,
302 const date_duration_t& duration)
303 {
304 if (duration.quantum == date_duration_t::DAYS)
305 out << duration.length << " day(s)";
306 else if (duration.quantum == date_duration_t::WEEKS)
307 out << duration.length << " week(s)";
308 else if (duration.quantum == date_duration_t::MONTHS)
309 out << duration.length << " month(s)";
310 else if (duration.quantum == date_duration_t::QUARTERS)
311 out << duration.length << " quarter(s)";
312 else {
313 assert(duration.quantum == date_duration_t::YEARS);
314 out << duration.length << " year(s)";
315 }
316 return out;
317 }
318
319 class date_parser_t
320 {
321 friend void show_period_tokens(std::ostream& out, const string& arg);
322
323 class lexer_t
324 {
325 friend class date_parser_t;
326
327 string::const_iterator begin;
328 string::const_iterator end;
329
330 public:
331 struct token_t
332 {
333 enum kind_t {
334 UNKNOWN,
335
336 TOK_DATE,
337 TOK_INT,
338 TOK_SLASH,
339 TOK_DASH,
340 TOK_DOT,
341
342 TOK_A_MONTH,
343 TOK_A_WDAY,
344
345 TOK_AGO,
346 TOK_HENCE,
347 TOK_SINCE,
348 TOK_UNTIL,
349 TOK_IN,
350 TOK_THIS,
351 TOK_NEXT,
352 TOK_LAST,
353 TOK_EVERY,
354
355 TOK_TODAY,
356 TOK_TOMORROW,
357 TOK_YESTERDAY,
358
359 TOK_YEAR,
360 TOK_QUARTER,
361 TOK_MONTH,
362 TOK_WEEK,
363 TOK_DAY,
364
365 TOK_YEARLY,
366 TOK_QUARTERLY,
367 TOK_BIMONTHLY,
368 TOK_MONTHLY,
369 TOK_BIWEEKLY,
370 TOK_WEEKLY,
371 TOK_DAILY,
372
373 TOK_YEARS,
374 TOK_QUARTERS,
375 TOK_MONTHS,
376 TOK_WEEKS,
377 TOK_DAYS,
378
379 END_REACHED
380
381 } kind;
382
383 typedef variant<unsigned short,
384 string,
385 date_specifier_t::year_type,
386 date_time::months_of_year,
387 date_time::weekdays,
388 date_specifier_t> content_t;
389
390 optional<content_t> value;
391
token_tledger::date_parser_t::lexer_t::token_t392 explicit token_t(kind_t _kind = UNKNOWN,
393 const optional<content_t>& _value =
394 content_t(empty_string))
395 : kind(_kind), value(_value) {
396 TRACE_CTOR(date_parser_t::lexer_t::token_t, "");
397 }
token_tledger::date_parser_t::lexer_t::token_t398 token_t(const token_t& tok)
399 : kind(tok.kind), value(tok.value) {
400 TRACE_CTOR(date_parser_t::lexer_t::token_t, "copy");
401 }
~token_tledger::date_parser_t::lexer_t::token_t402 ~token_t() throw() {
403 TRACE_DTOR(date_parser_t::lexer_t::token_t);
404 }
405
operator =ledger::date_parser_t::lexer_t::token_t406 token_t& operator=(const token_t& tok) {
407 if (this != &tok) {
408 kind = tok.kind;
409 value = tok.value;
410 }
411 return *this;
412 }
413
operator boolledger::date_parser_t::lexer_t::token_t414 operator bool() const {
415 return kind != END_REACHED;
416 }
417
to_stringledger::date_parser_t::lexer_t::token_t418 string to_string() const {
419 std::ostringstream out;
420
421 switch (kind) {
422 case UNKNOWN:
423 out << boost::get<string>(*value);
424 break;
425 case TOK_DATE:
426 return boost::get<date_specifier_t>(*value).to_string();
427 case TOK_INT:
428 out << boost::get<unsigned short>(*value);
429 break;
430 case TOK_SLASH: return "/";
431 case TOK_DASH: return "-";
432 case TOK_DOT: return ".";
433 case TOK_A_MONTH:
434 out << date_specifier_t::month_type
435 (boost::get<date_time::months_of_year>(*value));
436 break;
437 case TOK_A_WDAY:
438 out << date_specifier_t::day_of_week_type
439 (boost::get<date_time::weekdays>(*value));
440 break;
441 case TOK_AGO: return "ago";
442 case TOK_HENCE: return "hence";
443 case TOK_SINCE: return "since";
444 case TOK_UNTIL: return "until";
445 case TOK_IN: return "in";
446 case TOK_THIS: return "this";
447 case TOK_NEXT: return "next";
448 case TOK_LAST: return "last";
449 case TOK_EVERY: return "every";
450 case TOK_TODAY: return "today";
451 case TOK_TOMORROW: return "tomorrow";
452 case TOK_YESTERDAY: return "yesterday";
453 case TOK_YEAR: return "year";
454 case TOK_QUARTER: return "quarter";
455 case TOK_MONTH: return "month";
456 case TOK_WEEK: return "week";
457 case TOK_DAY: return "day";
458 case TOK_YEARLY: return "yearly";
459 case TOK_QUARTERLY: return "quarterly";
460 case TOK_BIMONTHLY: return "bimonthly";
461 case TOK_MONTHLY: return "monthly";
462 case TOK_BIWEEKLY: return "biweekly";
463 case TOK_WEEKLY: return "weekly";
464 case TOK_DAILY: return "daily";
465 case TOK_YEARS: return "years";
466 case TOK_QUARTERS: return "quarters";
467 case TOK_MONTHS: return "months";
468 case TOK_WEEKS: return "weeks";
469 case TOK_DAYS: return "days";
470 case END_REACHED: return "<EOF>";
471 }
472
473 return out.str();
474 }
475
dumpledger::date_parser_t::lexer_t::token_t476 void dump(std::ostream& out) const {
477 switch (kind) {
478 case UNKNOWN: out << "UNKNOWN"; break;
479 case TOK_DATE: out << "TOK_DATE"; break;
480 case TOK_INT: out << "TOK_INT"; break;
481 case TOK_SLASH: out << "TOK_SLASH"; break;
482 case TOK_DASH: out << "TOK_DASH"; break;
483 case TOK_DOT: out << "TOK_DOT"; break;
484 case TOK_A_MONTH: out << "TOK_A_MONTH"; break;
485 case TOK_A_WDAY: out << "TOK_A_WDAY"; break;
486 case TOK_AGO: out << "TOK_AGO"; break;
487 case TOK_HENCE: out << "TOK_HENCE"; break;
488 case TOK_SINCE: out << "TOK_SINCE"; break;
489 case TOK_UNTIL: out << "TOK_UNTIL"; break;
490 case TOK_IN: out << "TOK_IN"; break;
491 case TOK_THIS: out << "TOK_THIS"; break;
492 case TOK_NEXT: out << "TOK_NEXT"; break;
493 case TOK_LAST: out << "TOK_LAST"; break;
494 case TOK_EVERY: out << "TOK_EVERY"; break;
495 case TOK_TODAY: out << "TOK_TODAY"; break;
496 case TOK_TOMORROW: out << "TOK_TOMORROW"; break;
497 case TOK_YESTERDAY: out << "TOK_YESTERDAY"; break;
498 case TOK_YEAR: out << "TOK_YEAR"; break;
499 case TOK_QUARTER: out << "TOK_QUARTER"; break;
500 case TOK_MONTH: out << "TOK_MONTH"; break;
501 case TOK_WEEK: out << "TOK_WEEK"; break;
502 case TOK_DAY: out << "TOK_DAY"; break;
503 case TOK_YEARLY: out << "TOK_YEARLY"; break;
504 case TOK_QUARTERLY: out << "TOK_QUARTERLY"; break;
505 case TOK_BIMONTHLY: out << "TOK_BIMONTHLY"; break;
506 case TOK_MONTHLY: out << "TOK_MONTHLY"; break;
507 case TOK_BIWEEKLY: out << "TOK_BIWEEKLY"; break;
508 case TOK_WEEKLY: out << "TOK_WEEKLY"; break;
509 case TOK_DAILY: out << "TOK_DAILY"; break;
510 case TOK_YEARS: out << "TOK_YEARS"; break;
511 case TOK_QUARTERS: out << "TOK_QUARTERS"; break;
512 case TOK_MONTHS: out << "TOK_MONTHS"; break;
513 case TOK_WEEKS: out << "TOK_WEEKS"; break;
514 case TOK_DAYS: out << "TOK_DAYS"; break;
515 case END_REACHED: out << "END_REACHED"; break;
516 }
517 }
518
519 void unexpected();
520 static void expected(char wanted, char c = '\0');
521 };
522
523 token_t token_cache;
524
lexer_t(string::const_iterator _begin,string::const_iterator _end)525 lexer_t(string::const_iterator _begin,
526 string::const_iterator _end)
527 : begin(_begin), end(_end)
528 {
529 TRACE_CTOR(date_parser_t::lexer_t, "");
530 }
lexer_t(const lexer_t & other)531 lexer_t(const lexer_t& other)
532 : begin(other.begin), end(other.end),
533 token_cache(other.token_cache)
534 {
535 TRACE_CTOR(date_parser_t::lexer_t, "copy");
536 }
~lexer_t()537 ~lexer_t() throw() {
538 TRACE_DTOR(date_parser_t::lexer_t);
539 }
540
541 token_t next_token();
push_token(token_t tok)542 void push_token(token_t tok) {
543 assert(token_cache.kind == token_t::UNKNOWN);
544 token_cache = tok;
545 }
peek_token()546 token_t peek_token() {
547 if (token_cache.kind == token_t::UNKNOWN)
548 token_cache = next_token();
549 return token_cache;
550 }
551 };
552
553 string arg;
554 lexer_t lexer;
555
556 public:
date_parser_t(const string & _arg)557 date_parser_t(const string& _arg)
558 : arg(_arg), lexer(arg.begin(), arg.end()) {
559 TRACE_CTOR(date_parser_t, "");
560 }
date_parser_t(const date_parser_t & parser)561 date_parser_t(const date_parser_t& parser)
562 : arg(parser.arg), lexer(parser.lexer) {
563 TRACE_CTOR(date_parser_t, "copy");
564 }
~date_parser_t()565 ~date_parser_t() throw() {
566 TRACE_DTOR(date_parser_t);
567 }
568
569 date_interval_t parse();
570
571 private:
572 void determine_when(lexer_t::token_t& tok, date_specifier_t& specifier);
573 };
574
determine_when(date_parser_t::lexer_t::token_t & tok,date_specifier_t & specifier)575 void date_parser_t::determine_when(date_parser_t::lexer_t::token_t& tok,
576 date_specifier_t& specifier)
577 {
578 date_t today = CURRENT_DATE();
579
580 switch (tok.kind) {
581 case lexer_t::token_t::TOK_DATE:
582 specifier = boost::get<date_specifier_t>(*tok.value);
583 break;
584
585 case lexer_t::token_t::TOK_INT: {
586 unsigned short amount = boost::get<unsigned short>(*tok.value);
587 int8_t adjust = 0;
588
589 tok = lexer.peek_token();
590 lexer_t::token_t::kind_t kind = tok.kind;
591 switch (kind) {
592 case lexer_t::token_t::TOK_YEAR:
593 case lexer_t::token_t::TOK_YEARS:
594 case lexer_t::token_t::TOK_QUARTER:
595 case lexer_t::token_t::TOK_QUARTERS:
596 case lexer_t::token_t::TOK_MONTH:
597 case lexer_t::token_t::TOK_MONTHS:
598 case lexer_t::token_t::TOK_WEEK:
599 case lexer_t::token_t::TOK_WEEKS:
600 case lexer_t::token_t::TOK_DAY:
601 case lexer_t::token_t::TOK_DAYS:
602 lexer.next_token();
603 tok = lexer.next_token();
604 switch (tok.kind) {
605 case lexer_t::token_t::TOK_AGO:
606 adjust = -1;
607 break;
608 case lexer_t::token_t::TOK_HENCE:
609 adjust = 1;
610 break;
611 default:
612 tok.unexpected();
613 break;
614 }
615 break;
616 default:
617 break;
618 }
619
620 date_t when(today);
621
622 switch (kind) {
623 case lexer_t::token_t::TOK_YEAR:
624 case lexer_t::token_t::TOK_YEARS:
625 when += gregorian::years(amount * adjust);
626 break;
627 case lexer_t::token_t::TOK_QUARTER:
628 case lexer_t::token_t::TOK_QUARTERS:
629 when += gregorian::months(amount * 3 * adjust);
630 break;
631 case lexer_t::token_t::TOK_MONTH:
632 case lexer_t::token_t::TOK_MONTHS:
633 when += gregorian::months(amount * adjust);
634 break;
635 case lexer_t::token_t::TOK_WEEK:
636 case lexer_t::token_t::TOK_WEEKS:
637 when += gregorian::weeks(amount * adjust);
638 break;
639 case lexer_t::token_t::TOK_DAY:
640 case lexer_t::token_t::TOK_DAYS:
641 when += gregorian::days(amount * adjust);
642 break;
643 default:
644 if (amount > 31) {
645 specifier.year = date_specifier_t::year_type(amount);
646 } else {
647 specifier.day = date_specifier_t::day_type(amount);
648 }
649 break;
650 }
651
652 if (adjust)
653 specifier = date_specifier_t(when);
654 break;
655 }
656
657 case lexer_t::token_t::TOK_THIS:
658 case lexer_t::token_t::TOK_NEXT:
659 case lexer_t::token_t::TOK_LAST: {
660 int8_t adjust = 0;
661 if (tok.kind == lexer_t::token_t::TOK_NEXT)
662 adjust = 1;
663 else if (tok.kind == lexer_t::token_t::TOK_LAST)
664 adjust = -1;
665
666 tok = lexer.next_token();
667 switch (tok.kind) {
668 case lexer_t::token_t::TOK_A_MONTH: {
669 date_t temp(today.year(),
670 boost::get<date_time::months_of_year>(*tok.value), 1);
671 temp += gregorian::years(adjust);
672 specifier =
673 date_specifier_t(static_cast<date_specifier_t::year_type>(temp.year()),
674 temp.month());
675 break;
676 }
677
678 case lexer_t::token_t::TOK_A_WDAY: {
679 date_t temp =
680 date_duration_t::find_nearest(today, date_duration_t::WEEKS);
681 while (temp.day_of_week() !=
682 boost::get<date_time::months_of_year>(*tok.value))
683 temp += gregorian::days(1);
684 temp += gregorian::days(7 * adjust);
685 specifier = date_specifier_t(temp);
686 break;
687 }
688
689 case lexer_t::token_t::TOK_YEAR: {
690 date_t temp(today);
691 temp += gregorian::years(adjust);
692 specifier =
693 date_specifier_t(static_cast<date_specifier_t::year_type>(temp.year()));
694 break;
695 }
696
697 case lexer_t::token_t::TOK_QUARTER: {
698 date_t base =
699 date_duration_t::find_nearest(today, date_duration_t::QUARTERS);
700 date_t temp;
701 if (adjust < 0) {
702 temp = base + gregorian::months(3 * adjust);
703 }
704 else if (adjust == 0) {
705 temp = base + gregorian::months(3);
706 }
707 else if (adjust > 0) {
708 base += gregorian::months(3 * adjust);
709 temp = base + gregorian::months(3 * adjust);
710 }
711 specifier = date_specifier_t(adjust < 0 ? temp : base);
712 break;
713 }
714
715 case lexer_t::token_t::TOK_WEEK: {
716 date_t base =
717 date_duration_t::find_nearest(today, date_duration_t::WEEKS);
718 date_t temp;
719 if (adjust < 0) {
720 temp = base + gregorian::days(7 * adjust);
721 }
722 else if (adjust == 0) {
723 temp = base + gregorian::days(7);
724 }
725 else if (adjust > 0) {
726 base += gregorian::days(7 * adjust);
727 temp = base + gregorian::days(7 * adjust);
728 }
729 specifier = date_specifier_t(adjust < 0 ? temp : base);
730 break;
731 }
732
733 case lexer_t::token_t::TOK_DAY: {
734 date_t temp(today);
735 temp += gregorian::days(adjust);
736 specifier = date_specifier_t(temp);
737 break;
738 }
739
740 default:
741 case lexer_t::token_t::TOK_MONTH: {
742 date_t temp(today);
743 temp += gregorian::months(adjust);
744 specifier =
745 date_specifier_t(static_cast<date_specifier_t::year_type>(temp.year()),
746 temp.month());
747 break;
748 }
749 }
750 break;
751 }
752
753 case lexer_t::token_t::TOK_A_MONTH:
754 specifier.month =
755 date_specifier_t::month_type
756 (boost::get<date_time::months_of_year>(*tok.value));
757 tok = lexer.peek_token();
758 switch (tok.kind) {
759 case lexer_t::token_t::TOK_INT:
760 specifier.year = boost::get<date_specifier_t::year_type>(*tok.value);
761 break;
762 case lexer_t::token_t::END_REACHED:
763 break;
764 default:
765 break;
766 }
767 break;
768 case lexer_t::token_t::TOK_A_WDAY:
769 specifier.wday =
770 date_specifier_t::day_of_week_type
771 (boost::get<date_time::weekdays>(*tok.value));
772 break;
773
774 case lexer_t::token_t::TOK_TODAY:
775 specifier = date_specifier_t(today);
776 break;
777 case lexer_t::token_t::TOK_TOMORROW:
778 specifier = date_specifier_t(today + gregorian::days(1));
779 break;
780 case lexer_t::token_t::TOK_YESTERDAY:
781 specifier = date_specifier_t(today - gregorian::days(1));
782 break;
783
784 default:
785 tok.unexpected();
786 break;
787 }
788 }
789
parse()790 date_interval_t date_parser_t::parse()
791 {
792 optional<date_specifier_t> since_specifier;
793 optional<date_specifier_t> until_specifier;
794 optional<date_specifier_t> inclusion_specifier;
795
796 date_interval_t period;
797 date_t today = CURRENT_DATE();
798 bool end_inclusive = false;
799
800 for (lexer_t::token_t tok = lexer.next_token();
801 tok.kind != lexer_t::token_t::END_REACHED;
802 tok = lexer.next_token()) {
803 switch (tok.kind) {
804 case lexer_t::token_t::TOK_DATE:
805 if (! inclusion_specifier)
806 inclusion_specifier = date_specifier_t();
807 determine_when(tok, *inclusion_specifier);
808 break;
809
810 case lexer_t::token_t::TOK_INT:
811 if (! inclusion_specifier)
812 inclusion_specifier = date_specifier_t();
813 determine_when(tok, *inclusion_specifier);
814 break;
815
816 case lexer_t::token_t::TOK_A_MONTH:
817 if (! inclusion_specifier)
818 inclusion_specifier = date_specifier_t();
819 determine_when(tok, *inclusion_specifier);
820 break;
821
822 case lexer_t::token_t::TOK_A_WDAY:
823 if (! inclusion_specifier)
824 inclusion_specifier = date_specifier_t();
825 determine_when(tok, *inclusion_specifier);
826 break;
827
828 case lexer_t::token_t::TOK_DASH:
829 if (inclusion_specifier) {
830 since_specifier = inclusion_specifier;
831 until_specifier = date_specifier_t();
832 inclusion_specifier = none;
833
834 tok = lexer.next_token();
835 determine_when(tok, *until_specifier);
836
837 // The dash operator is special: it has an _inclusive_ end.
838 end_inclusive = true;
839 } else {
840 tok.unexpected();
841 }
842 break;
843
844 case lexer_t::token_t::TOK_SINCE:
845 if (since_specifier) {
846 tok.unexpected();
847 } else {
848 since_specifier = date_specifier_t();
849 tok = lexer.next_token();
850 determine_when(tok, *since_specifier);
851 }
852 break;
853
854 case lexer_t::token_t::TOK_UNTIL:
855 if (until_specifier) {
856 tok.unexpected();
857 } else {
858 until_specifier = date_specifier_t();
859 tok = lexer.next_token();
860 determine_when(tok, *until_specifier);
861 }
862 break;
863
864 case lexer_t::token_t::TOK_IN:
865 if (inclusion_specifier) {
866 tok.unexpected();
867 } else {
868 inclusion_specifier = date_specifier_t();
869 tok = lexer.next_token();
870 determine_when(tok, *inclusion_specifier);
871 }
872 break;
873
874 case lexer_t::token_t::TOK_THIS:
875 case lexer_t::token_t::TOK_NEXT:
876 case lexer_t::token_t::TOK_LAST: {
877 int8_t adjust = 0;
878 if (tok.kind == lexer_t::token_t::TOK_NEXT)
879 adjust = 1;
880 else if (tok.kind == lexer_t::token_t::TOK_LAST)
881 adjust = -1;
882
883 tok = lexer.next_token();
884 switch (tok.kind) {
885 case lexer_t::token_t::TOK_INT: {
886 unsigned short amount = boost::get<unsigned short>(*tok.value);
887
888 date_t base(today);
889 date_t end(today);
890
891 if (! adjust)
892 adjust = 1;
893
894 tok = lexer.next_token();
895 switch (tok.kind) {
896 case lexer_t::token_t::TOK_YEARS:
897 base += gregorian::years(amount * adjust);
898 break;
899 case lexer_t::token_t::TOK_QUARTERS:
900 base += gregorian::months(amount * adjust * 3);
901 break;
902 case lexer_t::token_t::TOK_MONTHS:
903 base += gregorian::months(amount * adjust);
904 break;
905 case lexer_t::token_t::TOK_WEEKS:
906 base += gregorian::weeks(amount * adjust);
907 break;
908 case lexer_t::token_t::TOK_DAYS:
909 base += gregorian::days(amount * adjust);
910 break;
911 default:
912 tok.unexpected();
913 break;
914 }
915
916 if (adjust >= 0) {
917 date_t temp = base;
918 base = end;
919 end = temp;
920 }
921
922 since_specifier = date_specifier_t(base);
923 until_specifier = date_specifier_t(end);
924 break;
925 }
926
927 case lexer_t::token_t::TOK_A_MONTH: {
928 inclusion_specifier = date_specifier_t();
929 determine_when(tok, *inclusion_specifier);
930
931 date_t temp(today.year(), *inclusion_specifier->month, 1);
932 temp += gregorian::years(adjust);
933 inclusion_specifier =
934 date_specifier_t(static_cast<date_specifier_t::year_type>(temp.year()),
935 temp.month());
936 break;
937 }
938
939 case lexer_t::token_t::TOK_A_WDAY: {
940 inclusion_specifier = date_specifier_t();
941 determine_when(tok, *inclusion_specifier);
942
943 date_t temp =
944 date_duration_t::find_nearest(today, date_duration_t::WEEKS);
945 while (temp.day_of_week() != inclusion_specifier->wday)
946 temp += gregorian::days(1);
947 temp += gregorian::days(7 * adjust);
948 inclusion_specifier = date_specifier_t(temp);
949 break;
950 }
951
952 case lexer_t::token_t::TOK_YEAR: {
953 date_t temp(today);
954 temp += gregorian::years(adjust);
955 inclusion_specifier =
956 date_specifier_t(static_cast<date_specifier_t::year_type>(temp.year()));
957 break;
958 }
959
960 case lexer_t::token_t::TOK_QUARTER: {
961 date_t base =
962 date_duration_t::find_nearest(today, date_duration_t::QUARTERS);
963 date_t temp;
964 if (adjust < 0) {
965 temp = base + gregorian::months(3 * adjust);
966 }
967 else if (adjust == 0) {
968 temp = base + gregorian::months(3);
969 }
970 else if (adjust > 0) {
971 base += gregorian::months(3 * adjust);
972 temp = base + gregorian::months(3 * adjust);
973 }
974 since_specifier = date_specifier_t(adjust < 0 ? temp : base);
975 until_specifier = date_specifier_t(adjust < 0 ? base : temp);
976 break;
977 }
978
979 case lexer_t::token_t::TOK_WEEK: {
980 date_t base =
981 date_duration_t::find_nearest(today, date_duration_t::WEEKS);
982 date_t temp;
983 if (adjust < 0) {
984 temp = base + gregorian::days(7 * adjust);
985 }
986 else if (adjust == 0) {
987 temp = base + gregorian::days(7);
988 }
989 else if (adjust > 0) {
990 base += gregorian::days(7 * adjust);
991 temp = base + gregorian::days(7 * adjust);
992 }
993 since_specifier = date_specifier_t(adjust < 0 ? temp : base);
994 until_specifier = date_specifier_t(adjust < 0 ? base : temp);
995 break;
996 }
997
998 case lexer_t::token_t::TOK_DAY: {
999 date_t temp(today);
1000 temp += gregorian::days(adjust);
1001 inclusion_specifier = date_specifier_t(temp);
1002 break;
1003 }
1004
1005 default:
1006 case lexer_t::token_t::TOK_MONTH: {
1007 date_t temp(today);
1008 temp += gregorian::months(adjust);
1009 inclusion_specifier =
1010 date_specifier_t(static_cast<date_specifier_t::year_type>(temp.year()),
1011 temp.month());
1012 break;
1013 }
1014 }
1015 break;
1016 }
1017
1018 case lexer_t::token_t::TOK_TODAY:
1019 inclusion_specifier = date_specifier_t(today);
1020 break;
1021 case lexer_t::token_t::TOK_TOMORROW:
1022 inclusion_specifier = date_specifier_t(today + gregorian::days(1));
1023 break;
1024 case lexer_t::token_t::TOK_YESTERDAY:
1025 inclusion_specifier = date_specifier_t(today - gregorian::days(1));
1026 break;
1027
1028 case lexer_t::token_t::TOK_EVERY:
1029 tok = lexer.next_token();
1030 if (tok.kind == lexer_t::token_t::TOK_INT) {
1031 int quantity = boost::get<unsigned short>(*tok.value);
1032 tok = lexer.next_token();
1033 switch (tok.kind) {
1034 case lexer_t::token_t::TOK_YEARS:
1035 period.duration = date_duration_t(date_duration_t::YEARS, quantity);
1036 break;
1037 case lexer_t::token_t::TOK_QUARTERS:
1038 period.duration = date_duration_t(date_duration_t::QUARTERS, quantity);
1039 break;
1040 case lexer_t::token_t::TOK_MONTHS:
1041 period.duration = date_duration_t(date_duration_t::MONTHS, quantity);
1042 break;
1043 case lexer_t::token_t::TOK_WEEKS:
1044 period.duration = date_duration_t(date_duration_t::WEEKS, quantity);
1045 break;
1046 case lexer_t::token_t::TOK_DAYS:
1047 period.duration = date_duration_t(date_duration_t::DAYS, quantity);
1048 break;
1049 default:
1050 tok.unexpected();
1051 break;
1052 }
1053 } else {
1054 switch (tok.kind) {
1055 case lexer_t::token_t::TOK_YEAR:
1056 period.duration = date_duration_t(date_duration_t::YEARS, 1);
1057 break;
1058 case lexer_t::token_t::TOK_QUARTER:
1059 period.duration = date_duration_t(date_duration_t::QUARTERS, 1);
1060 break;
1061 case lexer_t::token_t::TOK_MONTH:
1062 period.duration = date_duration_t(date_duration_t::MONTHS, 1);
1063 break;
1064 case lexer_t::token_t::TOK_WEEK:
1065 period.duration = date_duration_t(date_duration_t::WEEKS, 1);
1066 break;
1067 case lexer_t::token_t::TOK_DAY:
1068 period.duration = date_duration_t(date_duration_t::DAYS, 1);
1069 break;
1070 default:
1071 tok.unexpected();
1072 break;
1073 }
1074 }
1075 break;
1076
1077 case lexer_t::token_t::TOK_YEARLY:
1078 period.duration = date_duration_t(date_duration_t::YEARS, 1);
1079 break;
1080 case lexer_t::token_t::TOK_QUARTERLY:
1081 period.duration = date_duration_t(date_duration_t::QUARTERS, 1);
1082 break;
1083 case lexer_t::token_t::TOK_BIMONTHLY:
1084 period.duration = date_duration_t(date_duration_t::MONTHS, 2);
1085 break;
1086 case lexer_t::token_t::TOK_MONTHLY:
1087 period.duration = date_duration_t(date_duration_t::MONTHS, 1);
1088 break;
1089 case lexer_t::token_t::TOK_BIWEEKLY:
1090 period.duration = date_duration_t(date_duration_t::WEEKS, 2);
1091 break;
1092 case lexer_t::token_t::TOK_WEEKLY:
1093 period.duration = date_duration_t(date_duration_t::WEEKS, 1);
1094 break;
1095 case lexer_t::token_t::TOK_DAILY:
1096 period.duration = date_duration_t(date_duration_t::DAYS, 1);
1097 break;
1098
1099 default:
1100 tok.unexpected();
1101 break;
1102 }
1103 }
1104
1105 #if 0
1106 if (! period.duration && inclusion_specifier)
1107 period.duration = inclusion_specifier->implied_duration();
1108 #endif
1109
1110 if (since_specifier || until_specifier) {
1111 date_range_t range(since_specifier, until_specifier);
1112 range.end_inclusive = end_inclusive;
1113
1114 period.range = date_specifier_or_range_t(range);
1115 }
1116 else if (inclusion_specifier) {
1117 period.range = date_specifier_or_range_t(*inclusion_specifier);
1118 }
1119 else {
1120 /* otherwise, it's something like "monthly", with no date reference */
1121 }
1122
1123 return period;
1124 }
1125
parse(const string & str)1126 void date_interval_t::parse(const string& str)
1127 {
1128 date_parser_t parser(str);
1129 *this = parser.parse();
1130 }
1131
resolve_end()1132 void date_interval_t::resolve_end()
1133 {
1134 if (start && ! end_of_duration) {
1135 end_of_duration = duration->add(*start);
1136 DEBUG("times.interval",
1137 "stabilize: end_of_duration = " << *end_of_duration);
1138 }
1139
1140 if (finish && *end_of_duration > *finish) {
1141 end_of_duration = finish;
1142 DEBUG("times.interval",
1143 "stabilize: end_of_duration reset to end: " << *end_of_duration);
1144 }
1145
1146 if (start && ! next) {
1147 next = end_of_duration;
1148 DEBUG("times.interval", "stabilize: next set to: " << *next);
1149 }
1150 }
1151
find_nearest(const date_t & date,skip_quantum_t skip)1152 date_t date_duration_t::find_nearest(const date_t& date, skip_quantum_t skip)
1153 {
1154 date_t result;
1155
1156 switch (skip) {
1157 case date_duration_t::YEARS:
1158 result = date_t(date.year(), gregorian::Jan, 1);
1159 break;
1160 case date_duration_t::QUARTERS:
1161 result = date_t(date.year(), date.month(), 1);
1162 while (result.month() != gregorian::Jan &&
1163 result.month() != gregorian::Apr &&
1164 result.month() != gregorian::Jul &&
1165 result.month() != gregorian::Oct)
1166 result -= gregorian::months(1);
1167 break;
1168 case date_duration_t::MONTHS:
1169 result = date_t(date.year(), date.month(), 1);
1170 break;
1171 case date_duration_t::WEEKS:
1172 result = date;
1173 while (result.day_of_week() != start_of_week)
1174 result -= gregorian::days(1);
1175 break;
1176 case date_duration_t::DAYS:
1177 result = date;
1178 break;
1179 }
1180 return result;
1181 }
1182
stabilize(const optional<date_t> & date)1183 void date_interval_t::stabilize(const optional<date_t>& date)
1184 {
1185 #if DEBUG_ON
1186 if (date)
1187 DEBUG("times.interval", "stabilize: with date = " << *date);
1188 #endif
1189
1190 if (date && ! aligned) {
1191 DEBUG("times.interval", "stabilize: date passed, but not aligned");
1192 if (duration) {
1193 DEBUG("times.interval",
1194 "stabilize: aligning with a duration: " << *duration);
1195
1196 // The interval object has not been seeded with a start date yet, so
1197 // find the nearest period before or on date which fits, if possible.
1198 //
1199 // Find an efficient starting point for the upcoming while loop. We
1200 // want a date early enough that the range will be correct, but late
1201 // enough that we don't spend hundreds of thousands of loops skipping
1202 // through time.
1203 optional<date_t> initial_start = start ? start : begin();
1204 optional<date_t> initial_finish = finish ? finish : end();
1205
1206 #if DEBUG_ON
1207 if (initial_start)
1208 DEBUG("times.interval",
1209 "stabilize: initial_start = " << *initial_start);
1210 if (initial_finish)
1211 DEBUG("times.interval",
1212 "stabilize: initial_finish = " << *initial_finish);
1213 #endif
1214
1215 date_t when = start ? *start : *date;
1216 switch (duration->quantum) {
1217 case date_duration_t::MONTHS:
1218 case date_duration_t::QUARTERS:
1219 case date_duration_t::YEARS:
1220 // These start on most recent period start quantum before when
1221 DEBUG("times.interval",
1222 "stabilize: monthly, quarterly or yearly duration");
1223 start = date_duration_t::find_nearest(when, duration->quantum);
1224 break;
1225 case date_duration_t::WEEKS:
1226 // Weeks start on the beginning of week prior to 400 remainder period length
1227 // Either the first quanta of the period or the last quanta of the period seems more sensible
1228 // implies period is never less than 400 days not too unreasonable
1229 DEBUG("times.interval", "stabilize: weekly duration");
1230 {
1231 int period = duration->length * 7;
1232 start = date_duration_t::find_nearest(
1233 when - gregorian::days(period + 400 % period), duration->quantum);
1234 }
1235 break;
1236 default:
1237 // multiples of days have a quanta of 1 day so should not have the start date adjusted to a quanta
1238 DEBUG("times.interval",
1239 "stabilize: daily duration - stable by definition");
1240 start = when;
1241 break;
1242 }
1243
1244 DEBUG("times.interval", "stabilize: beginning start date = " << *start);
1245
1246 while (*start < *date) {
1247 date_interval_t next_interval(*this);
1248 ++next_interval;
1249
1250 if (next_interval.start && *next_interval.start <= *date) {
1251 *this = next_interval;
1252 } else {
1253 end_of_duration = none;
1254 next = none;
1255 break;
1256 }
1257 }
1258
1259 DEBUG("times.interval", "stabilize: proposed start date = " << *start);
1260
1261 if (initial_start && (! start || *start < *initial_start)) {
1262 // Using the discovered start, find the end of the period
1263 resolve_end();
1264
1265 start = initial_start;
1266 DEBUG("times.interval", "stabilize: start reset to initial start");
1267 }
1268 if (initial_finish && (! finish || *finish > *initial_finish)) {
1269 finish = initial_finish;
1270 DEBUG("times.interval", "stabilize: finish reset to initial finish");
1271 }
1272
1273 #if DEBUG_ON
1274 if (start)
1275 DEBUG("times.interval", "stabilize: final start = " << *start);
1276 if (finish)
1277 DEBUG("times.interval", "stabilize: final finish = " << *finish);
1278 #endif
1279 }
1280 else if (range) {
1281 start = range->begin();
1282 finish = range->end();
1283 }
1284 aligned = true;
1285 }
1286
1287 // If there is no duration, then if we've reached here the date falls
1288 // between start and finish.
1289 if (! duration) {
1290 DEBUG("times.interval", "stabilize: there was no duration given");
1291
1292 if (! start && ! finish)
1293 throw_(date_error,
1294 _("Invalid date interval: neither start, nor finish, nor duration"));
1295 } else {
1296 resolve_end();
1297 }
1298 }
1299
find_period(const date_t & date,const bool allow_shift)1300 bool date_interval_t::find_period(const date_t& date,
1301 const bool allow_shift)
1302 {
1303 stabilize(date);
1304
1305 if (finish && date > *finish) {
1306 DEBUG("times.interval",
1307 "false: date [" << date << "] > finish [" << *finish << "]");
1308 return false;
1309 }
1310
1311 if (! start) {
1312 throw_(std::runtime_error, _("Date interval is improperly initialized"));
1313 }
1314 else if (date < *start) {
1315 DEBUG("times.interval",
1316 "false: date [" << date << "] < start [" << *start << "]");
1317 return false;
1318 }
1319
1320 if (end_of_duration) {
1321 if (date < *end_of_duration) {
1322 DEBUG("times.interval",
1323 "true: date [" << date << "] < end_of_duration ["
1324 << *end_of_duration << "]");
1325 return true;
1326 }
1327 } else {
1328 DEBUG("times.interval", "false: there is no end_of_duration");
1329 return false;
1330 }
1331
1332 // If we've reached here, it means the date does not fall into the current
1333 // interval, so we must seek another interval that does match -- unless we
1334 // pass by date in so doing, which means we shouldn't alter the current
1335 // period of the interval at all.
1336
1337 date_t scan = *start;
1338 date_t end_of_scan = *end_of_duration;
1339
1340 DEBUG("times.interval", "date = " << date);
1341 DEBUG("times.interval", "scan = " << scan);
1342 DEBUG("times.interval", "end_of_scan = " << end_of_scan);
1343 #if DEBUG_ON
1344 if (finish)
1345 DEBUG("times.interval", "finish = " << *finish);
1346 else
1347 DEBUG("times.interval", "finish is not set");
1348 #endif
1349
1350 while (date >= scan && (! finish || scan < *finish)) {
1351 if (date < end_of_scan) {
1352 start = scan;
1353 end_of_duration = end_of_scan;
1354 next = none;
1355
1356 DEBUG("times.interval", "true: start = " << *start);
1357 DEBUG("times.interval", "true: end_of_duration = " << *end_of_duration);
1358
1359 resolve_end();
1360
1361 return true;
1362 }
1363 else if (! allow_shift) {
1364 break;
1365 }
1366
1367 scan = duration->add(scan);
1368 end_of_scan = duration->add(scan);
1369
1370 DEBUG("times.interval", "scan = " << scan);
1371 DEBUG("times.interval", "end_of_scan = " << end_of_scan);
1372 }
1373
1374 DEBUG("times.interval", "false: failed scan");
1375
1376 return false;
1377 }
1378
operator ++()1379 date_interval_t& date_interval_t::operator++()
1380 {
1381 if (! start)
1382 throw_(date_error, _("Cannot increment an unstarted date interval"));
1383
1384 stabilize();
1385
1386 if (! duration)
1387 throw_(date_error,
1388 _("Cannot increment a date interval without a duration"));
1389
1390 assert(next);
1391
1392 if (finish && *next >= *finish) {
1393 start = none;
1394 } else {
1395 start = *next;
1396 end_of_duration = duration->add(*start);
1397 }
1398 next = none;
1399
1400 resolve_end();
1401
1402 return *this;
1403 }
1404
dump(std::ostream & out)1405 void date_interval_t::dump(std::ostream& out)
1406 {
1407 out << _("--- Before stabilization ---") << std::endl;
1408
1409 if (range)
1410 out << _(" range: ") << range->to_string() << std::endl;
1411 if (start)
1412 out << _(" start: ") << format_date(*start) << std::endl;
1413 if (finish)
1414 out << _(" finish: ") << format_date(*finish) << std::endl;
1415
1416 if (duration)
1417 out << _("duration: ") << duration->to_string() << std::endl;
1418
1419 optional<date_t> when(begin());
1420 if (! when)
1421 when = CURRENT_DATE();
1422
1423 stabilize(when);
1424
1425 out << std::endl
1426 << _("--- After stabilization ---") << std::endl;
1427
1428 if (range)
1429 out << _(" range: ") << range->to_string() << std::endl;
1430 if (start)
1431 out << _(" start: ") << format_date(*start) << std::endl;
1432 if (finish)
1433 out << _(" finish: ") << format_date(*finish) << std::endl;
1434
1435 if (duration)
1436 out << _("duration: ") << duration->to_string() << std::endl;
1437
1438 out << std::endl
1439 << _("--- Sample dates in range (max. 20) ---") << std::endl;
1440
1441 date_t last_date;
1442
1443 for (int i = 0; i < 20 && *this; ++i, ++*this) {
1444 out << std::right;
1445 out.width(2);
1446
1447 if (! last_date.is_not_a_date() && last_date == *start)
1448 break;
1449
1450 out << (i + 1) << ": " << format_date(*start);
1451 if (duration)
1452 out << " -- " << format_date(*inclusive_end());
1453 out << std::endl;
1454
1455 if (! duration)
1456 break;
1457
1458 last_date = *start;
1459 }
1460 }
1461
next_token()1462 date_parser_t::lexer_t::token_t date_parser_t::lexer_t::next_token()
1463 {
1464 if (token_cache.kind != token_t::UNKNOWN) {
1465 token_t tok = token_cache;
1466 token_cache = token_t();
1467 return tok;
1468 }
1469
1470 while (begin != end && std::isspace(*begin))
1471 begin++;
1472
1473 if (begin == end)
1474 return token_t(token_t::END_REACHED);
1475
1476 switch (*begin) {
1477 case '/': ++begin; return token_t(token_t::TOK_SLASH);
1478 case '-': ++begin; return token_t(token_t::TOK_DASH);
1479 case '.': ++begin; return token_t(token_t::TOK_DOT);
1480 default: break;
1481 }
1482
1483 string::const_iterator start = begin;
1484
1485 // If the first character is a digit, try parsing the whole argument as a
1486 // date using the typical date formats. This allows not only dates like
1487 // "2009/08/01", but also dates that fit the user's --input-date-format,
1488 // assuming their format fits in one argument and begins with a digit.
1489 if (std::isdigit(*begin)) {
1490 string::const_iterator i = begin;
1491 for (i = begin; i != end && ! std::isspace(*i); i++) {}
1492 assert(i != begin);
1493
1494 string possible_date(start, i);
1495
1496 try {
1497 date_traits_t traits;
1498 date_t when = parse_date_mask(possible_date.c_str(), &traits);
1499 if (! when.is_not_a_date()) {
1500 begin = i;
1501 return token_t(token_t::TOK_DATE,
1502 token_t::content_t(date_specifier_t(when, traits)));
1503 }
1504 }
1505 catch (date_error&) {
1506 if (contains(possible_date, "/") ||
1507 contains(possible_date, "-") ||
1508 contains(possible_date, "."))
1509 throw;
1510 }
1511 }
1512
1513 start = begin;
1514
1515 string term;
1516 bool alnum = std::isalnum(*begin);
1517 for (; (begin != end && ! std::isspace(*begin) &&
1518 ((alnum && static_cast<bool>(std::isalnum(*begin))) ||
1519 (! alnum && ! static_cast<bool>(std::isalnum(*begin))))); begin++)
1520 term.push_back(*begin);
1521
1522 if (! term.empty()) {
1523 if (std::isdigit(term[0])) {
1524 return token_t(token_t::TOK_INT,
1525 token_t::content_t(lexical_cast<unsigned short>(term)));
1526 }
1527 else if (std::isalpha(term[0])) {
1528 to_lower(term);
1529
1530 if (optional<date_time::months_of_year> month =
1531 string_to_month_of_year(term)) {
1532 return token_t(token_t::TOK_A_MONTH, token_t::content_t(*month));
1533 }
1534 else if (optional<date_time::weekdays> wday =
1535 string_to_day_of_week(term)) {
1536 return token_t(token_t::TOK_A_WDAY, token_t::content_t(*wday));
1537 }
1538 else if (term == _("ago"))
1539 return token_t(token_t::TOK_AGO);
1540 else if (term == _("hence"))
1541 return token_t(token_t::TOK_HENCE);
1542 else if (term == _("from") || term == _("since"))
1543 return token_t(token_t::TOK_SINCE);
1544 else if (term == _("to") || term == _("until"))
1545 return token_t(token_t::TOK_UNTIL);
1546 else if (term == _("in"))
1547 return token_t(token_t::TOK_IN);
1548 else if (term == _("this"))
1549 return token_t(token_t::TOK_THIS);
1550 else if (term == _("next"))
1551 return token_t(token_t::TOK_NEXT);
1552 else if (term == _("last"))
1553 return token_t(token_t::TOK_LAST);
1554 else if (term == _("every"))
1555 return token_t(token_t::TOK_EVERY);
1556 else if (term == _("today"))
1557 return token_t(token_t::TOK_TODAY);
1558 else if (term == _("tomorrow"))
1559 return token_t(token_t::TOK_TOMORROW);
1560 else if (term == _("yesterday"))
1561 return token_t(token_t::TOK_YESTERDAY);
1562 else if (term == _("year"))
1563 return token_t(token_t::TOK_YEAR);
1564 else if (term == _("quarter"))
1565 return token_t(token_t::TOK_QUARTER);
1566 else if (term == _("month"))
1567 return token_t(token_t::TOK_MONTH);
1568 else if (term == _("week"))
1569 return token_t(token_t::TOK_WEEK);
1570 else if (term == _("day"))
1571 return token_t(token_t::TOK_DAY);
1572 else if (term == _("yearly"))
1573 return token_t(token_t::TOK_YEARLY);
1574 else if (term == _("quarterly"))
1575 return token_t(token_t::TOK_QUARTERLY);
1576 else if (term == _("bimonthly"))
1577 return token_t(token_t::TOK_BIMONTHLY);
1578 else if (term == _("monthly"))
1579 return token_t(token_t::TOK_MONTHLY);
1580 else if (term == _("biweekly"))
1581 return token_t(token_t::TOK_BIWEEKLY);
1582 else if (term == _("weekly"))
1583 return token_t(token_t::TOK_WEEKLY);
1584 else if (term == _("daily"))
1585 return token_t(token_t::TOK_DAILY);
1586 else if (term == _("years"))
1587 return token_t(token_t::TOK_YEARS);
1588 else if (term == _("quarters"))
1589 return token_t(token_t::TOK_QUARTERS);
1590 else if (term == _("months"))
1591 return token_t(token_t::TOK_MONTHS);
1592 else if (term == _("weeks"))
1593 return token_t(token_t::TOK_WEEKS);
1594 else if (term == _("days"))
1595 return token_t(token_t::TOK_DAYS);
1596 }
1597 else {
1598 token_t::expected('\0', term[0]);
1599 begin = ++start;
1600 }
1601 } else {
1602 token_t::expected('\0', *begin);
1603 }
1604
1605 return token_t(token_t::UNKNOWN, token_t::content_t(term));
1606 }
1607
unexpected()1608 void date_parser_t::lexer_t::token_t::unexpected()
1609 {
1610 switch (kind) {
1611 case END_REACHED:
1612 kind = UNKNOWN;
1613 throw_(date_error, _("Unexpected end of expression"));
1614 default: {
1615 string desc = to_string();
1616 kind = UNKNOWN;
1617 throw_(date_error, _f("Unexpected date period token '%1%'") % desc);
1618 }
1619 }
1620 }
1621
expected(char wanted,char c)1622 void date_parser_t::lexer_t::token_t::expected(char wanted, char c)
1623 {
1624 if (c == '\0' || c == -1) {
1625 if (wanted == '\0' || wanted == -1)
1626 throw_(date_error, _("Unexpected end"));
1627 else
1628 throw_(date_error, _f("Missing '%1%'") % wanted);
1629 } else {
1630 if (wanted == '\0' || wanted == -1)
1631 throw_(date_error, _f("Invalid char '%1%'") % c);
1632 else
1633 throw_(date_error, _f("Invalid char '%1%' (wanted '%2%')") % c % wanted);
1634 }
1635 }
1636
1637 namespace {
1638 typedef std::map<std::string, datetime_io_t *> datetime_io_map;
1639 typedef std::map<std::string, date_io_t *> date_io_map;
1640
1641 datetime_io_map temp_datetime_io;
1642 date_io_map temp_date_io;
1643 }
1644
format_datetime(const datetime_t & when,const format_type_t format_type,const optional<const char * > & format)1645 std::string format_datetime(const datetime_t& when,
1646 const format_type_t format_type,
1647 const optional<const char *>& format)
1648 {
1649 if (format_type == FMT_WRITTEN) {
1650 return written_datetime_io->format(when);
1651 }
1652 else if (format_type == FMT_CUSTOM && format) {
1653 datetime_io_map::iterator i = temp_datetime_io.find(*format);
1654 if (i != temp_datetime_io.end()) {
1655 return (*i).second->format(when);
1656 } else {
1657 datetime_io_t * formatter = new datetime_io_t(*format, false);
1658 temp_datetime_io.insert(datetime_io_map::value_type(*format, formatter));
1659 return formatter->format(when);
1660 }
1661 }
1662 else if (format_type == FMT_PRINTED) {
1663 return printed_datetime_io->format(when);
1664 }
1665 else {
1666 assert(false);
1667 return empty_string;
1668 }
1669 }
1670
format_date(const date_t & when,const format_type_t format_type,const optional<const char * > & format)1671 std::string format_date(const date_t& when,
1672 const format_type_t format_type,
1673 const optional<const char *>& format)
1674 {
1675 if (format_type == FMT_WRITTEN) {
1676 return written_date_io->format(when);
1677 }
1678 else if (format_type == FMT_CUSTOM && format) {
1679 date_io_map::iterator i = temp_date_io.find(*format);
1680 if (i != temp_date_io.end()) {
1681 return (*i).second->format(when);
1682 } else {
1683 date_io_t * formatter = new date_io_t(*format, false);
1684 temp_date_io.insert(date_io_map::value_type(*format, formatter));
1685 return formatter->format(when);
1686 }
1687 }
1688 else if (format_type == FMT_PRINTED) {
1689 return printed_date_io->format(when);
1690 }
1691 else {
1692 assert(false);
1693 return empty_string;
1694 }
1695 }
1696
1697 namespace {
1698 bool is_initialized = false;
1699 }
1700
set_datetime_format(const char * format)1701 void set_datetime_format(const char * format)
1702 {
1703 written_datetime_io->set_format(format);
1704 printed_datetime_io->set_format(format);
1705 }
1706
set_date_format(const char * format)1707 void set_date_format(const char * format)
1708 {
1709 written_date_io->set_format(format);
1710 printed_date_io->set_format(format);
1711 }
1712
set_input_date_format(const char * format)1713 void set_input_date_format(const char * format)
1714 {
1715 readers.push_front(shared_ptr<date_io_t>(new date_io_t(format, true)));
1716 convert_separators_to_slashes = false;
1717 }
1718
times_initialize()1719 void times_initialize()
1720 {
1721 if (! is_initialized) {
1722 input_datetime_io.reset(new datetime_io_t("%Y/%m/%d %H:%M:%S", true));
1723 timelog_datetime_io.reset(new datetime_io_t("%m/%d/%Y %H:%M:%S", true));
1724
1725 written_datetime_io.reset(new datetime_io_t("%Y/%m/%d %H:%M:%S", false));
1726 written_date_io.reset(new date_io_t("%Y/%m/%d", false));
1727
1728 printed_datetime_io.reset(new datetime_io_t("%y-%b-%d %H:%M:%S", false));
1729 printed_date_io.reset(new date_io_t("%y-%b-%d", false));
1730
1731 readers.push_back(shared_ptr<date_io_t>(new date_io_t("%m/%d", true)));
1732 readers.push_back(shared_ptr<date_io_t>(new date_io_t("%Y/%m/%d", true)));
1733 readers.push_back(shared_ptr<date_io_t>(new date_io_t("%Y/%m", true)));
1734 readers.push_back(shared_ptr<date_io_t>(new date_io_t("%y/%m/%d", true)));
1735 readers.push_back(shared_ptr<date_io_t>(new date_io_t("%Y-%m-%d", true)));
1736
1737 is_initialized = true;
1738 }
1739 }
1740
times_shutdown()1741 void times_shutdown()
1742 {
1743 if (is_initialized) {
1744 input_datetime_io.reset();
1745 timelog_datetime_io.reset();
1746 written_datetime_io.reset();
1747 written_date_io.reset();
1748 printed_datetime_io.reset();
1749 printed_date_io.reset();
1750
1751 readers.clear();
1752
1753 foreach (datetime_io_map::value_type& pair, temp_datetime_io)
1754 checked_delete(pair.second);
1755 temp_datetime_io.clear();
1756
1757 foreach (date_io_map::value_type& pair, temp_date_io)
1758 checked_delete(pair.second);
1759 temp_date_io.clear();
1760
1761 is_initialized = false;
1762 }
1763 }
1764
show_period_tokens(std::ostream & out,const string & arg)1765 void show_period_tokens(std::ostream& out, const string& arg)
1766 {
1767 date_parser_t::lexer_t lexer(arg.begin(), arg.end());
1768
1769 out << _("--- Period expression tokens ---") << std::endl;
1770
1771 date_parser_t::lexer_t::token_t token;
1772 do {
1773 token = lexer.next_token();
1774 token.dump(out);
1775 out << ": " << token.to_string() << std::endl;
1776 }
1777 while (token.kind != date_parser_t::lexer_t::token_t::END_REACHED);
1778 }
1779
1780 } // namespace ledger
1781