GnuCash  5.6-150-g038405b370+
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 
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 
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 checked_reldate(RelativeDatePeriod per)
327 {
328  assert (reldates[static_cast<int>(per)].m_period == per);
329  return reldates[static_cast<int>(per)];
330 }
331 
332 bool
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 
342 bool
344 {
345  if (per == RelativeDatePeriod::ABSOLUTE)
346  return false;
347  return checked_reldate(per).m_type == RelativeDateType::START;
348 }
349 
350 bool
352 {
353  if (per == RelativeDatePeriod::ABSOLUTE)
354  return false;
355  return checked_reldate(per).m_type == RelativeDateType::END;
356 }
357 
358 const char*
360 {
361  if (per == RelativeDatePeriod::ABSOLUTE)
362  return nullptr;
363  return checked_reldate(per).m_storage;
364 }
365 
366 const char*
368 {
369  if (per == RelativeDatePeriod::ABSOLUTE)
370  return nullptr;
371  return checked_reldate(per).m_display;
372 }
373 const 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 
392 static bool
393 reldate_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 
405 static bool
406 reldate_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 
418 static RelativeDateOffset
419 reldate_offset(RelativeDatePeriod per)
420 {
421  return checked_reldate(per).m_offset;
422 }
423 
424 static int
425 days_in_month(int month, int year)
426 {
427  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 normalize_reldate_tm(struct tm& now)
436 {
437  auto delta = (now.tm_mon / 12) + (now.tm_mon < 0 ? -1 : 0);
438  now.tm_mon -= 12 * delta;
439  now.tm_year += delta;
440 
441  if (now.tm_mday < 1)
442  {
443  do
444  {
445  if (now.tm_mon-- == 0)
446  {
447  now.tm_mon = 11;
448  now.tm_year--;
449  }
450  now.tm_mday += days_in_month(now.tm_mon, now.tm_year);
451  } while (now.tm_mday < 1) ;
452  return;
453  }
454 
455  while (now.tm_mday > (delta = days_in_month(now.tm_mon, now.tm_year)))
456  {
457  if (now.tm_mon++ == 11)
458  {
459  now.tm_mon = 0;
460  now.tm_year++;
461  }
462  now.tm_mday -= delta;
463  }
464 }
465 
466 static void
467 reldate_set_day_and_time(struct tm& now, RelativeDateType type)
468 {
469  if (type == RelativeDateType::START)
470  {
471  gnc_tm_set_day_start(&now);
472  now.tm_mday = 1;
473  }
474  else if (type == RelativeDateType::END)
475  {
476  /* Ensure that the month is between 0 and 11*/
477  auto year_delta = (now.tm_mon / 12) + (now.tm_mon < 0 ? -1 : 0);
478  auto month = now.tm_mon - (12 * year_delta);
479  auto year = now.tm_year + year_delta;
480  now.tm_mday = days_in_month(month, year);
481  gnc_tm_set_day_end(&now);
482  }
483  // Do nothing for LAST and NEXT.
484 };
485 
486 time64
488 {
489  if (period == RelativeDatePeriod::TODAY)
490  return static_cast<time64>(GncDateTime());
491  if (period == RelativeDatePeriod::START_ACCOUNTING_PERIOD)
492  return gnc_accounting_period_fiscal_start();
493  if (period == RelativeDatePeriod::END_ACCOUNTING_PERIOD)
494  return gnc_accounting_period_fiscal_end();
495 
496  GncDateTime now_t;
497  if (period == RelativeDatePeriod::TODAY)
498  return static_cast<time64>(now_t);
499  auto now{static_cast<tm>(now_t)};
500  struct tm acct_per{};
501  if (gnc_prefs_get_bool (GNC_PREFS_GROUP_ACCT_SUMMARY,
502  GNC_PREF_START_CHOICE_ABS))
503  acct_per = static_cast<tm>(GncDateTime(gnc_accounting_period_fiscal_start()));
504 
505  switch(reldate_offset(period))
506  {
507  case RelativeDateOffset::NONE:
508 // Report on today so nothing to do
509  break;
510  case RelativeDateOffset::YEAR:
511  if (reldate_is_prev(period))
512  --now.tm_year;
513  else if (reldate_is_next(period))
514  ++now.tm_year;
515  if (gnc_relative_date_is_starting(period))
516  now.tm_mon = 0;
517  else if (gnc_relative_date_is_ending(period))
518  now.tm_mon = 11;
519  break;
520  case RelativeDateOffset::SIX:
521  if (reldate_is_prev(period))
522  now.tm_mon -= 6;
523  else if (reldate_is_next(period))
524  now.tm_mon += 6;
525  break;
526  case RelativeDateOffset::QUARTER:
527  {
528  auto delta = (now.tm_mon >= acct_per.tm_mon ?
529  ( now.tm_mon - acct_per.tm_mon) % 3 :
530  3 - ((acct_per.tm_mon - now.tm_mon) % 3));
531  now.tm_mon = now.tm_mon - delta;
532  }
533  [[fallthrough]];
534  case RelativeDateOffset::THREE:
535  if (reldate_is_prev(period))
536  now.tm_mon -= 3;
537  else if (reldate_is_next(period))
538  now.tm_mon += 3;
539  if (gnc_relative_date_is_ending(period))
540  now.tm_mon += 2;
541  break;
542  case RelativeDateOffset::MONTH:
543  if (reldate_is_prev(period))
544  --now.tm_mon;
545  else if (reldate_is_next(period))
546  ++now.tm_mon;
547  break;
548  case RelativeDateOffset::WEEK:
549  if (reldate_is_prev(period))
550  now.tm_mday -= 7;
551  else if (reldate_is_next(period))
552  now.tm_mday += 7;
553  }
554  reldate_set_day_and_time(now, checked_reldate(period).m_type);
555  normalize_reldate_tm(now);
556  return static_cast<time64>(GncDateTime(now));
557 }
558 
559 std::ostream&
560 operator<<(std::ostream& ostr, RelativeDatePeriod per)
561 {
562  ostr << "'reldate . " << gnc_relative_date_display_string(per);
563  return ostr;
564 }
std::ostream & operator<<(std::ostream &ostr, RelativeDatePeriod per)
Add the display string to the provided std::ostream.
GnuCash DateTime class.
RelativeDatePeriod gnc_relative_date_from_storage_string(const char *str)
Convert a relative date storage string back to a RelativeDatePeriod value.
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_description(RelativeDatePeriod per)
Provide the description of a relative date.
int gnc_date_get_last_mday(int month, int year)
Get the numerical last date of the month.
Definition: gnc-date.cpp:412
time64 gnc_relative_date_to_time64(RelativeDatePeriod period)
Convert a RelativeDatePeriod value to a concrete time64 by applying the value to the current time...
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_single(RelativeDatePeriod per)
Report whether the relative date represents a period offset to today&#39;s date rather than the beginning...
RelativeDatePeriod
Reporting periods relative to the current date.
General utilities for dealing with accounting periods.
bool gnc_relative_date_is_starting(RelativeDatePeriod per)
Report whether the relative date represents the beginning of a date range.
Generic api to store and retrieve preferences.
gboolean gnc_prefs_get_bool(const gchar *group, const gchar *pref_name)
Get a boolean value from the preferences backend.
const char * gnc_relative_date_storage_string(RelativeDatePeriod per)
Provide the string representation of a relative date for persisting the value.
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
Definition: gnc-date.h:87
Relative date enumeration and manipulation functions.