Branch data Line data Source code
1 : : /********************************************************************\
2 : : * gnc-option-date.cpp -- Relative Dates for options *
3 : : * Copyright (C) 2020 John Ralls <jralls@ceridwen.us> *
4 : : * *
5 : : * This program is free software; you can redistribute it and/or *
6 : : * modify it under the terms of the GNU General Public License as *
7 : : * published by the Free Software Foundation; either version 2 of *
8 : : * the License, or (at your option) any later version. *
9 : : * *
10 : : * This program is distributed in the hope that it will be useful, *
11 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 : : * GNU General Public License for more details. *
14 : : * *
15 : : * You should have received a copy of the GNU General Public License*
16 : : * along with this program; if not, contact: *
17 : : * *
18 : : * Free Software Foundation Voice: +1-617-542-5942 *
19 : : * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
20 : : * Boston, MA 02110-1301, USA gnu@gnu.org *
21 : : \********************************************************************/
22 : :
23 : : #include "gnc-option-date.hpp"
24 : : #include <array>
25 : : #include "gnc-datetime.hpp"
26 : : #include <gnc-prefs.h>
27 : : #include <iostream>
28 : : #include <cassert>
29 : : #include <algorithm>
30 : :
31 : : #include "gnc-accounting-period.h"
32 : :
33 : : #define N_(string) string //So that xgettext will find it
34 : :
35 : : enum RelativeDateType
36 : : {
37 : : ABSOLUTE,
38 : : LAST,
39 : : NEXT,
40 : : START,
41 : : END
42 : : };
43 : :
44 : : enum RelativeDateOffset
45 : : {
46 : : NONE,
47 : : WEEK,
48 : : MONTH,
49 : : QUARTER,
50 : : THREE,
51 : : SIX,
52 : : YEAR
53 : : };
54 : :
55 : : struct GncRelativeDate
56 : : {
57 : : RelativeDatePeriod m_period;
58 : : RelativeDateType m_type;
59 : : RelativeDateOffset m_offset;
60 : : const char* m_storage;
61 : : const char* m_display;
62 : : const char* m_description;
63 : : };
64 : :
65 : :
66 : : /* The fixed values and strings for date periods. Accessor functions will use
67 : : * the RelativeDatePeriod as an index so any changes need to be reflected in the
68 : : * RelativeDatePeriod enum class in gnc-option-date.hpp and vice-versa.
69 : : *
70 : : * The double curly braces are actually correct and required for a std::array
71 : : * initializer list.
72 : : */
73 : : static const std::array<GncRelativeDate, 31> reldates
74 : : {{
75 : : {
76 : : RelativeDatePeriod::TODAY,
77 : : RelativeDateType::LAST,
78 : : RelativeDateOffset::NONE,
79 : : "today",
80 : : N_("Today"),
81 : : N_("The current date.")
82 : : },
83 : : {
84 : : RelativeDatePeriod::ONE_WEEK_AGO,
85 : : RelativeDateType::LAST,
86 : : RelativeDateOffset::WEEK,
87 : : "one-week-ago",
88 : : N_("One Week Ago"),
89 : : N_("One Week Ago.")
90 : : },
91 : : {
92 : : RelativeDatePeriod::ONE_WEEK_AHEAD,
93 : : RelativeDateType::NEXT,
94 : : RelativeDateOffset::WEEK,
95 : : "one-week-ahead",
96 : : N_("One Week Ahead"),
97 : : N_("One Week Ahead.")
98 : : },
99 : : {
100 : : RelativeDatePeriod::ONE_MONTH_AGO,
101 : : RelativeDateType::LAST,
102 : : RelativeDateOffset::MONTH,
103 : : "one-month-ago",
104 : : N_("One Month Ago"),
105 : : N_("One Month Ago.")
106 : : },
107 : : {
108 : : RelativeDatePeriod::ONE_MONTH_AHEAD,
109 : : RelativeDateType::NEXT,
110 : : RelativeDateOffset::MONTH,
111 : : "one-month-ahead",
112 : : N_("One Month Ahead"),
113 : : N_("One Month Ahead.")
114 : : },
115 : : {
116 : : RelativeDatePeriod::THREE_MONTHS_AGO,
117 : : RelativeDateType::LAST,
118 : : RelativeDateOffset::THREE,
119 : : "three-months-ago",
120 : : N_("Three Months Ago"),
121 : : N_("Three Months Ago.")
122 : : },
123 : : {
124 : : RelativeDatePeriod::THREE_MONTHS_AHEAD,
125 : : RelativeDateType::NEXT,
126 : : RelativeDateOffset::THREE,
127 : : "three-months-ahead",
128 : : N_("Three Months Ahead"),
129 : : N_("Three Months Ahead.")
130 : : },
131 : : {
132 : : RelativeDatePeriod::SIX_MONTHS_AGO,
133 : : RelativeDateType::LAST,
134 : : RelativeDateOffset::SIX,
135 : : "six-months-ago",
136 : : N_("Six Months Ago"),
137 : : N_("Six Months Ago.")
138 : : },
139 : : {
140 : : RelativeDatePeriod::SIX_MONTHS_AHEAD,
141 : : RelativeDateType::NEXT,
142 : : RelativeDateOffset::SIX,
143 : : "six-months-ahead",
144 : : N_("Six Months Ahead"),
145 : : N_("Six Months Ahead.")
146 : : },
147 : : {
148 : : RelativeDatePeriod::ONE_YEAR_AGO,
149 : : RelativeDateType::LAST,
150 : : RelativeDateOffset::YEAR,
151 : : "one-year-ago",
152 : : N_("One Year Ago"),
153 : : N_("One Year Ago.")
154 : : },
155 : : {
156 : : RelativeDatePeriod::ONE_YEAR_AHEAD,
157 : : RelativeDateType::NEXT,
158 : : RelativeDateOffset::YEAR,
159 : : "one-year-ahead",
160 : : N_("One Year Ahead"),
161 : : N_("One Year Ahead.")
162 : : },
163 : : {
164 : : RelativeDatePeriod::START_THIS_MONTH,
165 : : RelativeDateType::START,
166 : : RelativeDateOffset::MONTH,
167 : : "start-this-month",
168 : : N_("Start of this month"),
169 : : N_("First day of the current month.")
170 : : },
171 : : {
172 : : RelativeDatePeriod::END_THIS_MONTH,
173 : : RelativeDateType::END,
174 : : RelativeDateOffset::MONTH,
175 : : "end-this-month",
176 : : N_("End of this month"),
177 : : N_("Last day of the current month.")
178 : : },
179 : : {
180 : : RelativeDatePeriod::START_PREV_MONTH,
181 : : RelativeDateType::START,
182 : : RelativeDateOffset::MONTH,
183 : : "start-prev-month",
184 : : N_("Start of previous month"),
185 : : N_("First day of the previous month.")
186 : : },
187 : : {
188 : : RelativeDatePeriod::END_PREV_MONTH,
189 : : RelativeDateType::END,
190 : : RelativeDateOffset::MONTH,
191 : : "end-prev-month",
192 : : N_("End of previous month"),
193 : : N_("Last day of previous month.")
194 : : },
195 : : {
196 : : RelativeDatePeriod::START_NEXT_MONTH,
197 : : RelativeDateType::START,
198 : : RelativeDateOffset::MONTH,
199 : : "start-next-month",
200 : : N_("Start of next month"),
201 : : N_("First day of the next month.")
202 : : },
203 : : {
204 : : RelativeDatePeriod::END_NEXT_MONTH,
205 : : RelativeDateType::END,
206 : : RelativeDateOffset::MONTH,
207 : : "end-next-month",
208 : : N_("End of next month"),
209 : : N_("Last day of next month.")
210 : : },
211 : : {
212 : : RelativeDatePeriod::START_CURRENT_QUARTER,
213 : : RelativeDateType::START,
214 : : RelativeDateOffset::QUARTER,
215 : : "start-current-quarter",
216 : : N_("Start of current quarter"),
217 : : N_("First day of the current quarterly accounting period.")
218 : : },
219 : : {
220 : : RelativeDatePeriod::END_CURRENT_QUARTER,
221 : : RelativeDateType::END,
222 : : RelativeDateOffset::QUARTER,
223 : : "end-current-quarter",
224 : : N_("End of current quarter"),
225 : : N_("Last day of the current quarterly accounting period.")
226 : : },
227 : : {
228 : : RelativeDatePeriod::START_PREV_QUARTER,
229 : : RelativeDateType::START,
230 : : RelativeDateOffset::QUARTER,
231 : : "start-prev-quarter",
232 : : N_("Start of previous quarter"),
233 : : N_("First day of the previous quarterly accounting period.")
234 : : },
235 : : {
236 : : RelativeDatePeriod::END_PREV_QUARTER,
237 : : RelativeDateType::END,
238 : : RelativeDateOffset::QUARTER,
239 : : "end-prev-quarter",
240 : : N_("End of previous quarter"),
241 : : N_("Last day of previous quarterly accounting period.")
242 : : },
243 : : {
244 : : RelativeDatePeriod::START_NEXT_QUARTER,
245 : : RelativeDateType::START,
246 : : RelativeDateOffset::QUARTER,
247 : : "start-next-quarter",
248 : : N_("Start of next quarter"),
249 : : N_("First day of the next quarterly accounting period.")
250 : : },
251 : : {
252 : : RelativeDatePeriod::END_NEXT_QUARTER,
253 : : RelativeDateType::END,
254 : : RelativeDateOffset::QUARTER,
255 : : "end-next-quarter",
256 : : N_("End of next quarter"),
257 : : N_("Last day of next quarterly accounting period.")
258 : : },
259 : : {
260 : : RelativeDatePeriod::START_CAL_YEAR,
261 : : RelativeDateType::START,
262 : : RelativeDateOffset::YEAR,
263 : : "start-cal-year",
264 : : N_("Start of this year"),
265 : : N_("First day of the current calendar year.")
266 : : },
267 : : {
268 : : RelativeDatePeriod::END_CAL_YEAR,
269 : : RelativeDateType::END,
270 : : RelativeDateOffset::YEAR,
271 : : "end-cal-year",
272 : : N_("End of this year"),
273 : : N_("Last day of the current calendar year.")
274 : : },
275 : : {
276 : : RelativeDatePeriod::START_PREV_YEAR,
277 : : RelativeDateType::START,
278 : : RelativeDateOffset::YEAR,
279 : : "start-prev-year",
280 : : N_("Start of previous year"),
281 : : N_("First day of the previous calendar year.")
282 : : },
283 : : {
284 : : RelativeDatePeriod::END_PREV_YEAR,
285 : : RelativeDateType::END,
286 : : RelativeDateOffset::YEAR,
287 : : "end-prev-year",
288 : : N_("End of previous year"),
289 : : N_("Last day of the previous calendar year.")
290 : : },
291 : : {
292 : : RelativeDatePeriod::START_NEXT_YEAR,
293 : : RelativeDateType::START,
294 : : RelativeDateOffset::YEAR,
295 : : "start-next-year",
296 : : N_("Start of next year"),
297 : : N_("First day of the next calendar year.")
298 : : },
299 : : {
300 : : RelativeDatePeriod::END_NEXT_YEAR,
301 : : RelativeDateType::END,
302 : : RelativeDateOffset::YEAR,
303 : : "end-next-year",
304 : : N_("End of next year"),
305 : : N_("Last day of the next calendar year.")
306 : : },
307 : : {
308 : : RelativeDatePeriod::START_ACCOUNTING_PERIOD,
309 : : RelativeDateType::START,
310 : : RelativeDateOffset::YEAR,
311 : : "start-prev-fin-year",
312 : : N_("Start of accounting period"),
313 : : N_("First day of the accounting period, as set in the global preferences.")
314 : : },
315 : : {
316 : : RelativeDatePeriod::END_ACCOUNTING_PERIOD,
317 : : RelativeDateType::END,
318 : : RelativeDateOffset::YEAR,
319 : : "end-prev-fin-year",
320 : : N_("End of accounting period"),
321 : : N_("Last day of the accounting period, as set in the global preferences.")
322 : : }
323 : : }};
324 : :
325 : : static const GncRelativeDate&
326 : 816 : checked_reldate(RelativeDatePeriod per)
327 : : {
328 : 816 : assert (reldates[static_cast<int>(per)].m_period == per);
329 : 816 : return reldates[static_cast<int>(per)];
330 : : }
331 : :
332 : : bool
333 : 7 : gnc_relative_date_is_single(RelativeDatePeriod per)
334 : : {
335 : 7 : if (per == RelativeDatePeriod::ABSOLUTE)
336 : 1 : return false;
337 : 6 : auto reldate = checked_reldate(per);
338 : 11 : return reldate.m_type == RelativeDateType::LAST ||
339 : 11 : reldate.m_type == RelativeDateType::NEXT;
340 : : }
341 : :
342 : : bool
343 : 126 : gnc_relative_date_is_starting(RelativeDatePeriod per)
344 : : {
345 : 126 : if (per == RelativeDatePeriod::ABSOLUTE)
346 : 1 : return false;
347 : 125 : return checked_reldate(per).m_type == RelativeDateType::START;
348 : : }
349 : :
350 : : bool
351 : 95 : gnc_relative_date_is_ending(RelativeDatePeriod per)
352 : : {
353 : 95 : if (per == RelativeDatePeriod::ABSOLUTE)
354 : 1 : return false;
355 : 94 : return checked_reldate(per).m_type == RelativeDateType::END;
356 : : }
357 : :
358 : : const char*
359 : 19 : gnc_relative_date_storage_string(RelativeDatePeriod per)
360 : : {
361 : 19 : if (per == RelativeDatePeriod::ABSOLUTE)
362 : 1 : return nullptr;
363 : 18 : return checked_reldate(per).m_storage;
364 : : }
365 : :
366 : : const char*
367 : 2 : gnc_relative_date_display_string(RelativeDatePeriod per)
368 : : {
369 : 2 : if (per == RelativeDatePeriod::ABSOLUTE)
370 : 1 : return nullptr;
371 : 1 : return checked_reldate(per).m_display;
372 : : }
373 : : const char*
374 : 2 : gnc_relative_date_description(RelativeDatePeriod per)
375 : : {
376 : 2 : if (per == RelativeDatePeriod::ABSOLUTE)
377 : 1 : return nullptr;
378 : 1 : return checked_reldate(per).m_description;
379 : : }
380 : :
381 : : RelativeDatePeriod
382 : 15 : gnc_relative_date_from_storage_string(const char* str)
383 : : {
384 : 15 : auto per = std::find_if(reldates.begin(), reldates.end(),
385 : 288 : [str](auto rel) -> bool
386 : : {
387 : 288 : return strcmp(str, rel.m_storage) == 0;
388 : : });
389 : 15 : return per != reldates.end() ? per->m_period : RelativeDatePeriod::ABSOLUTE;
390 : : }
391 : :
392 : : static bool
393 : 153 : reldate_is_prev(RelativeDatePeriod per)
394 : : {
395 : 153 : auto rdate{checked_reldate(per)};
396 : 151 : return per == RelativeDatePeriod::START_PREV_YEAR ||
397 : 129 : per == RelativeDatePeriod::END_PREV_YEAR ||
398 : 121 : per == RelativeDatePeriod::START_PREV_QUARTER ||
399 : 116 : per == RelativeDatePeriod::END_PREV_QUARTER ||
400 : 114 : per == RelativeDatePeriod::START_PREV_MONTH ||
401 : 304 : per == RelativeDatePeriod::END_PREV_MONTH ||
402 : 265 : rdate.m_type == LAST;
403 : : }
404 : :
405 : : static bool
406 : 112 : reldate_is_next(RelativeDatePeriod per)
407 : : {
408 : 112 : auto rdate{checked_reldate(per)};
409 : 112 : return per == RelativeDatePeriod::START_NEXT_YEAR ||
410 : 112 : per == RelativeDatePeriod::END_NEXT_YEAR ||
411 : 112 : per == RelativeDatePeriod::START_NEXT_QUARTER ||
412 : 112 : per == RelativeDatePeriod::END_NEXT_QUARTER ||
413 : 112 : per == RelativeDatePeriod::START_NEXT_MONTH ||
414 : 224 : per == RelativeDatePeriod::END_NEXT_MONTH ||
415 : 224 : rdate.m_type == NEXT;
416 : : }
417 : :
418 : : static RelativeDateOffset
419 : 153 : reldate_offset(RelativeDatePeriod per)
420 : : {
421 : 153 : return checked_reldate(per).m_offset;
422 : : }
423 : :
424 : : static int
425 : 239 : days_in_month(int month, int year)
426 : : {
427 : 239 : return gnc_date_get_last_mday(month, year + 1900);
428 : : }
429 : :
430 : : /* Normalize the modified struct tm computed in gnc_relative_date_to_time64
431 : : * before setting the time and perhaps beginning/end of the month. Using the
432 : : * gnc_date API would involve multiple conversions to and from struct tm.
433 : : */
434 : : static void
435 : 153 : normalize_reldate_tm(struct tm& now)
436 : : {
437 : 153 : auto delta = (now.tm_mon / 12) + (now.tm_mon < 0 ? -1 : 0);
438 : 153 : now.tm_mon -= 12 * delta;
439 : 153 : now.tm_year += delta;
440 : :
441 : 153 : if (now.tm_mday < 1)
442 : : {
443 : : do
444 : : {
445 : 0 : if (now.tm_mon-- == 0)
446 : : {
447 : 0 : now.tm_mon = 11;
448 : 0 : now.tm_year--;
449 : : }
450 : 0 : now.tm_mday += days_in_month(now.tm_mon, now.tm_year);
451 : 0 : } while (now.tm_mday < 1) ;
452 : 0 : return;
453 : : }
454 : :
455 : 153 : while (now.tm_mday > (delta = days_in_month(now.tm_mon, now.tm_year)))
456 : : {
457 : 0 : if (now.tm_mon++ == 11)
458 : : {
459 : 0 : now.tm_mon = 0;
460 : 0 : now.tm_year++;
461 : : }
462 : 0 : now.tm_mday -= delta;
463 : : }
464 : : }
465 : :
466 : : static void
467 : 153 : reldate_set_day_and_time(struct tm& now, RelativeDateType type)
468 : : {
469 : 153 : if (type == RelativeDateType::START)
470 : : {
471 : 67 : gnc_tm_set_day_start(&now);
472 : 67 : now.tm_mday = 1;
473 : : }
474 : 86 : else if (type == RelativeDateType::END)
475 : : {
476 : : /* Ensure that the month is between 0 and 11*/
477 : 86 : auto year_delta = (now.tm_mon / 12) + (now.tm_mon < 0 ? -1 : 0);
478 : 86 : auto month = now.tm_mon - (12 * year_delta);
479 : 86 : auto year = now.tm_year + year_delta;
480 : 86 : now.tm_mday = days_in_month(month, year);
481 : 86 : gnc_tm_set_day_end(&now);
482 : : }
483 : : // Do nothing for LAST and NEXT.
484 : 153 : };
485 : :
486 : : time64
487 : 590 : gnc_relative_date_to_time64(RelativeDatePeriod period)
488 : : {
489 : 590 : if (period == RelativeDatePeriod::TODAY)
490 : 51 : return static_cast<time64>(GncDateTime());
491 : 539 : if (period == RelativeDatePeriod::START_ACCOUNTING_PERIOD)
492 : 175 : return gnc_accounting_period_fiscal_start();
493 : 364 : if (period == RelativeDatePeriod::END_ACCOUNTING_PERIOD)
494 : 211 : return gnc_accounting_period_fiscal_end();
495 : :
496 : 153 : GncDateTime now_t;
497 : 153 : if (period == RelativeDatePeriod::TODAY)
498 : 0 : return static_cast<time64>(now_t);
499 : 153 : auto now{static_cast<tm>(now_t)};
500 : 153 : struct tm acct_per{};
501 : 153 : if (gnc_prefs_get_bool (GNC_PREFS_GROUP_ACCT_SUMMARY,
502 : : GNC_PREF_START_CHOICE_ABS))
503 : 0 : acct_per = static_cast<tm>(GncDateTime(gnc_accounting_period_fiscal_start()));
504 : :
505 : 153 : switch(reldate_offset(period))
506 : : {
507 : 0 : case RelativeDateOffset::NONE:
508 : : // Report on today so nothing to do
509 : 0 : break;
510 : 119 : case RelativeDateOffset::YEAR:
511 : 119 : if (reldate_is_prev(period))
512 : 24 : --now.tm_year;
513 : 95 : else if (reldate_is_next(period))
514 : 0 : ++now.tm_year;
515 : 119 : if (gnc_relative_date_is_starting(period))
516 : 48 : now.tm_mon = 0;
517 : 71 : else if (gnc_relative_date_is_ending(period))
518 : 71 : now.tm_mon = 11;
519 : 119 : break;
520 : 0 : case RelativeDateOffset::SIX:
521 : 0 : if (reldate_is_prev(period))
522 : 0 : now.tm_mon -= 6;
523 : 0 : else if (reldate_is_next(period))
524 : 0 : now.tm_mon += 6;
525 : 0 : break;
526 : 17 : case RelativeDateOffset::QUARTER:
527 : : {
528 : 17 : auto delta = (now.tm_mon >= acct_per.tm_mon ?
529 : 17 : ( now.tm_mon - acct_per.tm_mon) % 3 :
530 : 0 : (3 - acct_per.tm_mon - now.tm_mon) % 3);
531 : 17 : now.tm_mon = now.tm_mon - delta;
532 : : }
533 : : [[fallthrough]];
534 : 17 : case RelativeDateOffset::THREE:
535 : 17 : if (reldate_is_prev(period))
536 : 13 : now.tm_mon -= 3;
537 : 4 : else if (reldate_is_next(period))
538 : 0 : now.tm_mon += 3;
539 : 17 : if (gnc_relative_date_is_ending(period))
540 : 7 : now.tm_mon += 2;
541 : 17 : break;
542 : 17 : case RelativeDateOffset::MONTH:
543 : 17 : if (reldate_is_prev(period))
544 : 4 : --now.tm_mon;
545 : 13 : else if (reldate_is_next(period))
546 : 0 : ++now.tm_mon;
547 : 17 : break;
548 : 0 : case RelativeDateOffset::WEEK:
549 : 0 : if (reldate_is_prev(period))
550 : 0 : now.tm_mday -= 7;
551 : 0 : else if (reldate_is_next(period))
552 : 0 : now.tm_mday += 7;
553 : : }
554 : 153 : reldate_set_day_and_time(now, checked_reldate(period).m_type);
555 : 153 : normalize_reldate_tm(now);
556 : 153 : return static_cast<time64>(GncDateTime(now));
557 : 153 : }
558 : :
559 : : std::ostream&
560 : 0 : operator<<(std::ostream& ostr, RelativeDatePeriod per)
561 : : {
562 : 0 : ostr << "'reldate . " << gnc_relative_date_display_string(per);
563 : 0 : return ostr;
564 : : }
|