GnuCash c935c2f+
Loading...
Searching...
No Matches
gnc-option-date.cpp
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
32
33#define N_(string) string //So that xgettext will find it
34
35enum RelativeDateType
36{
37 ABSOLUTE,
38 LAST,
39 NEXT,
40 START,
41 END
42};
43
44enum RelativeDateOffset
45{
46 NONE,
47 WEEK,
48 MONTH,
49 QUARTER,
50 THREE,
51 SIX,
52 YEAR
53};
54
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 */
73static 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
325static const GncRelativeDate&
326checked_reldate(RelativeDatePeriod per)
327{
328 assert (reldates[static_cast<int>(per)].m_period == per);
329 return reldates[static_cast<int>(per)];
330}
331
332bool
334{
335 if (per == RelativeDatePeriod::ABSOLUTE)
336 return false;
337 auto reldate = checked_reldate(per);
338 return reldate.m_type == RelativeDateType::LAST ||
339 reldate.m_type == RelativeDateType::NEXT;
340}
341
342bool
344{
345 if (per == RelativeDatePeriod::ABSOLUTE)
346 return false;
347 return checked_reldate(per).m_type == RelativeDateType::START;
348}
349
350bool
352{
353 if (per == RelativeDatePeriod::ABSOLUTE)
354 return false;
355 return checked_reldate(per).m_type == RelativeDateType::END;
356}
357
358const char*
360{
361 if (per == RelativeDatePeriod::ABSOLUTE)
362 return nullptr;
363 return checked_reldate(per).m_storage;
364}
365
366const char*
368{
369 if (per == RelativeDatePeriod::ABSOLUTE)
370 return nullptr;
371 return checked_reldate(per).m_display;
372}
373const char*
375{
376 if (per == RelativeDatePeriod::ABSOLUTE)
377 return nullptr;
378 return checked_reldate(per).m_description;
379}
380
383{
384 auto per = std::find_if(reldates.begin(), reldates.end(),
385 [str](auto rel) -> bool
386 {
387 return strcmp(str, rel.m_storage) == 0;
388 });
389 return per != reldates.end() ? per->m_period : RelativeDatePeriod::ABSOLUTE;
390}
391
392static bool
393reldate_is_prev(RelativeDatePeriod per)
394{
395 auto rdate{checked_reldate(per)};
396 return per == RelativeDatePeriod::START_PREV_YEAR ||
397 per == RelativeDatePeriod::END_PREV_YEAR ||
398 per == RelativeDatePeriod::START_PREV_QUARTER ||
399 per == RelativeDatePeriod::END_PREV_QUARTER ||
400 per == RelativeDatePeriod::START_PREV_MONTH ||
401 per == RelativeDatePeriod::END_PREV_MONTH ||
402 rdate.m_type == LAST;
403}
404
405static bool
406reldate_is_next(RelativeDatePeriod per)
407{
408 auto rdate{checked_reldate(per)};
409 return per == RelativeDatePeriod::START_NEXT_YEAR ||
410 per == RelativeDatePeriod::END_NEXT_YEAR ||
411 per == RelativeDatePeriod::START_NEXT_QUARTER ||
412 per == RelativeDatePeriod::END_NEXT_QUARTER ||
413 per == RelativeDatePeriod::START_NEXT_MONTH ||
414 per == RelativeDatePeriod::END_NEXT_MONTH ||
415 rdate.m_type == NEXT;
416}
417
418static RelativeDateOffset
419reldate_offset(RelativeDatePeriod per)
420{
421 return checked_reldate(per).m_offset;
422}
423
424static int
425days_in_month(int month, int year)
426{
427 return gnc_date_get_last_mday(month, year + 1900);
428}
429
430static int
431get_last_day_of_month(struct tm& now)
432{
433 /* Ensure that the month is between 0 and 11*/
434 auto year_delta = (now.tm_mon / 12) + (now.tm_mon < 0 ? -1 : 0);
435 return days_in_month(now.tm_mon - (12 * year_delta), now.tm_year + year_delta);
436}
437
438static void
439set_last_day_in_month(struct tm& now)
440{
441 now.tm_mday = get_last_day_of_month(now);
442}
443
444static bool
445is_last_day_in_month(struct tm& now)
446{
447 return now.tm_mday == get_last_day_of_month(now);
448}
449
450/* Normalize the modified struct tm computed in gnc_relative_date_to_time64
451 * before setting the time and perhaps beginning/end of the month. Using the
452 * gnc_date API would involve multiple conversions to and from struct tm.
453*/
454static void
455normalize_reldate_tm(struct tm& now)
456{
457 auto delta = (now.tm_mon / 12) + (now.tm_mon < 0 ? -1 : 0);
458 now.tm_mon -= 12 * delta;
459 now.tm_year += delta;
460
461 if (now.tm_mday < 1)
462 {
463 do
464 {
465 if (now.tm_mon-- == 0)
466 {
467 now.tm_mon = 11;
468 now.tm_year--;
469 }
470 now.tm_mday += days_in_month(now.tm_mon, now.tm_year);
471 } while (now.tm_mday < 1) ;
472 return;
473 }
474
475 while (now.tm_mday > (delta = days_in_month(now.tm_mon, now.tm_year)))
476 {
477 if (now.tm_mon++ == 11)
478 {
479 now.tm_mon = 0;
480 now.tm_year++;
481 }
482 now.tm_mday -= delta;
483 }
484}
485
486static void
487reldate_set_day_and_time(
488 struct tm& now,
489 RelativeDateType type,
490 bool is_offset_quarter,
491 struct tm& acct_per)
492{
493 if (type == RelativeDateType::START)
494 {
495 now.tm_mday = 1;
496 if (is_offset_quarter)
497 {
498 set_last_day_in_month(now);
499 if (!is_last_day_in_month(acct_per) && now.tm_mday > acct_per.tm_mday)
500 now.tm_mday = acct_per.tm_mday;
501 }
502 gnc_tm_set_day_start(&now);
503 }
504 else if (type == RelativeDateType::END)
505 {
506 set_last_day_in_month(now);
507 if (is_offset_quarter)
508 {
509 if (!is_last_day_in_month(acct_per) && now.tm_mday > acct_per.tm_mday)
510 now.tm_mday = acct_per.tm_mday;
511 --now.tm_mday;
512 }
513 gnc_tm_set_day_end(&now);
514 }
515 // Do nothing for LAST and NEXT.
516};
517
518time64
520{
521 if (period == RelativeDatePeriod::TODAY)
522 return now_t;
523 if (period == RelativeDatePeriod::START_ACCOUNTING_PERIOD)
524 return gnc_accounting_period_fiscal_start();
525 if (period == RelativeDatePeriod::END_ACCOUNTING_PERIOD)
526 return gnc_accounting_period_fiscal_end();
527
528 auto now{static_cast<tm>(GncDateTime(now_t))};
529 struct tm acct_per =
530 static_cast<tm>(GncDateTime(gnc_accounting_period_fiscal_start()));
531 auto offset = reldate_offset(period);
532 bool is_offset_quarter =
533 offset == RelativeDateOffset::QUARTER && acct_per.tm_mday > 1;
534
535 switch(offset)
536 {
537 case RelativeDateOffset::NONE:
538 // Report on today so nothing to do
539 break;
540 case RelativeDateOffset::YEAR:
541 if (reldate_is_prev(period))
542 --now.tm_year;
543 else if (reldate_is_next(period))
544 ++now.tm_year;
546 now.tm_mon = 0;
547 else if (gnc_relative_date_is_ending(period))
548 now.tm_mon = 11;
549 break;
550 case RelativeDateOffset::SIX:
551 if (reldate_is_prev(period))
552 now.tm_mon -= 6;
553 else if (reldate_is_next(period))
554 now.tm_mon += 6;
555 break;
556 case RelativeDateOffset::QUARTER:
557 {
558 auto delta = (12 + now.tm_mon - acct_per.tm_mon) % 3;
559 if (is_offset_quarter)
560 {
561 if (delta == 0 && !is_last_day_in_month(now) &&
562 (is_last_day_in_month(acct_per) ||
563 now.tm_mday < acct_per.tm_mday))
564 delta = 3;
565 if (gnc_relative_date_is_ending(period))
566 --delta;
567 }
568 now.tm_mon -= delta;
569 }
570 [[fallthrough]];
571 case RelativeDateOffset::THREE:
572 if (reldate_is_prev(period))
573 now.tm_mon -= 3;
574 else if (reldate_is_next(period))
575 now.tm_mon += 3;
576 if (gnc_relative_date_is_ending(period))
577 now.tm_mon += 2;
578 break;
579 case RelativeDateOffset::MONTH:
580 if (reldate_is_prev(period))
581 --now.tm_mon;
582 else if (reldate_is_next(period))
583 ++now.tm_mon;
584 break;
585 case RelativeDateOffset::WEEK:
586 if (reldate_is_prev(period))
587 now.tm_mday -= 7;
588 else if (reldate_is_next(period))
589 now.tm_mday += 7;
590 }
591 reldate_set_day_and_time( now,
592 checked_reldate(period).m_type,
593 is_offset_quarter,
594 acct_per);
595 normalize_reldate_tm(now);
596 return static_cast<time64>(GncDateTime(now));
597}
598
599std::ostream&
600operator<<(std::ostream& ostr, RelativeDatePeriod per)
601{
602 ostr << "'reldate . " << gnc_relative_date_display_string(per);
603 return ostr;
604}
GnuCash DateTime class.
General utilities for dealing with accounting periods.
Relative date enumeration and manipulation functions.
Generic api to store and retrieve preferences.
int gnc_date_get_last_mday(int month, int year)
Get the numerical last date of the month.
Definition gnc-date.cpp:413
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
Definition gnc-date.h:87
time64 gnc_relative_date_to_time64(RelativeDatePeriod period, time64 now_t)
Convert a RelativeDatePeriod value to a concrete time64 by applying the value to the current time.
RelativeDatePeriod
Reporting periods relative to the current date.
bool gnc_relative_date_is_ending(RelativeDatePeriod per)
Report whether the relative date represents the end of a date range.
bool gnc_relative_date_is_starting(RelativeDatePeriod per)
Report whether the relative date represents the beginning of a date range.
const char * gnc_relative_date_display_string(RelativeDatePeriod per)
Provide the string representation of a relative date for displaying value to a user.
const char * gnc_relative_date_storage_string(RelativeDatePeriod per)
Provide the string representation of a relative date for persisting the value.
bool gnc_relative_date_is_single(RelativeDatePeriod per)
Report whether the relative date represents a period offset to today's date rather than the beginning...
std::ostream & operator<<(std::ostream &ostr, RelativeDatePeriod per)
Add the display string to the provided std::ostream.
const char * gnc_relative_date_description(RelativeDatePeriod per)
Provide the description of a relative date.
RelativeDatePeriod gnc_relative_date_from_storage_string(const char *str)
Convert a relative date storage string back to a RelativeDatePeriod value.