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