gnucash/libgnucash/engine/gnc-option-date.cpp
Richard Cohen 1cec0cb3f3 Use internal extern "C" { ... } for C++
- removes warnings compiling swig engine
...
[ 10%] Generating swig-engine.cpp
.../libgnucash/engine/engine-helpers.h:31: Warning 313: Unrecognized extern type "C++".
.../libgnucash/engine/gnc-date.h:83: Warning 313: Unrecognized extern type "C++".
.../libgnucash/engine/qofquery.h:90: Warning 302: Identifier 'QofQuery' redefined (ignored),
.../libgnucash/engine/gnc-option.hpp:55: Warning 302: previous definition of 'QofQuery'.
.../libgnucash/engine/gnc-commodity.h:56: Warning 313: Unrecognized extern type "C++".
.../libgnucash/engine/gncBusiness.h:40: Warning 313: Unrecognized extern type "C++".
.../libgnucash/engine/gncEntry.h:37: Warning 313: Unrecognized extern type "C++".
2023-01-23 18:40:01 +00:00

574 lines
17 KiB
C++

/********************************************************************\
* gnc-option-date.cpp -- Relative Dates for options *
* Copyright (C) 2020 John Ralls <jralls@ceridwen.us> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
* Boston, MA 02110-1301, USA gnu@gnu.org *
\********************************************************************/
#include "gnc-option-date.hpp"
#include <array>
#include "gnc-datetime.hpp"
#include <iostream>
#include <cassert>
#include <algorithm>
#include "gnc-accounting-period.h"
#define N_(string) string //So that xgettext will find it
enum RelativeDateType
{
ABSOLUTE,
LAST,
NEXT,
START,
END
};
enum RelativeDateOffset
{
NONE,
WEEK,
MONTH,
QUARTER,
THREE,
SIX,
YEAR
};
struct GncRelativeDate
{
RelativeDatePeriod m_period;
RelativeDateType m_type;
RelativeDateOffset m_offset;
const char* m_storage;
const char* m_display;
const char* m_description;
};
/* The fixed values and strings for date periods. Accessor functions will use
* the RelativeDatePeriod as an index so any changes need to be reflected in the
* RelativeDatePeriod enum class in gnc-option-date.hpp and vice-versa.
*
* The double curly braces are actually correct and required for a std::array
* initializer list.
*/
static const std::array<GncRelativeDate, 31> reldates
{{
{
RelativeDatePeriod::TODAY,
RelativeDateType::LAST,
RelativeDateOffset::NONE,
"today",
N_("Today"),
N_("The current date.")
},
{
RelativeDatePeriod::ONE_WEEK_AGO,
RelativeDateType::LAST,
RelativeDateOffset::WEEK,
"one-week-ago",
N_("One Week Ago"),
N_("One Week Ago.")
},
{
RelativeDatePeriod::ONE_WEEK_AHEAD,
RelativeDateType::NEXT,
RelativeDateOffset::WEEK,
"one-week-ahead",
N_("One Week Ahead"),
N_("One Week Ahead.")
},
{
RelativeDatePeriod::ONE_MONTH_AGO,
RelativeDateType::LAST,
RelativeDateOffset::MONTH,
"one-month-ago",
N_("One Month Ago"),
N_("One Month Ago.")
},
{
RelativeDatePeriod::ONE_MONTH_AHEAD,
RelativeDateType::NEXT,
RelativeDateOffset::MONTH,
"one-month-ahead",
N_("One Month Ahead"),
N_("One Month Ahead.")
},
{
RelativeDatePeriod::THREE_MONTHS_AGO,
RelativeDateType::LAST,
RelativeDateOffset::THREE,
"three-months-ago",
N_("Three Months Ago"),
N_("Three Months Ago.")
},
{
RelativeDatePeriod::THREE_MONTHS_AHEAD,
RelativeDateType::NEXT,
RelativeDateOffset::THREE,
"three-months-ahead",
N_("Three Months Ahead"),
N_("Three Months Ahead.")
},
{
RelativeDatePeriod::SIX_MONTHS_AGO,
RelativeDateType::LAST,
RelativeDateOffset::SIX,
"six-months-ago",
N_("Six Months Ago"),
N_("Six Months Ago.")
},
{
RelativeDatePeriod::SIX_MONTHS_AHEAD,
RelativeDateType::NEXT,
RelativeDateOffset::SIX,
"six-months-ahead",
N_("Six Months Ahead"),
N_("Six Months Ahead.")
},
{
RelativeDatePeriod::ONE_YEAR_AGO,
RelativeDateType::LAST,
RelativeDateOffset::YEAR,
"one-year-ago",
N_("One Year Ago"),
N_("One Year Ago.")
},
{
RelativeDatePeriod::ONE_YEAR_AHEAD,
RelativeDateType::NEXT,
RelativeDateOffset::YEAR,
"one-year-ahead",
N_("One Year Ahead"),
N_("One Year Ahead.")
},
{
RelativeDatePeriod::START_THIS_MONTH,
RelativeDateType::START,
RelativeDateOffset::MONTH,
"start-this-month",
N_("Start of this month"),
N_("First day of the current month.")
},
{
RelativeDatePeriod::END_THIS_MONTH,
RelativeDateType::END,
RelativeDateOffset::MONTH,
"end-this-month",
N_("End of this month"),
N_("Last day of the current month.")
},
{
RelativeDatePeriod::START_PREV_MONTH,
RelativeDateType::START,
RelativeDateOffset::MONTH,
"start-prev-month",
N_("Start of previous month"),
N_("First day of the previous month.")
},
{
RelativeDatePeriod::END_PREV_MONTH,
RelativeDateType::END,
RelativeDateOffset::MONTH,
"end-prev-month",
N_("End of previous month"),
N_("Last day of previous month.")
},
{
RelativeDatePeriod::START_NEXT_MONTH,
RelativeDateType::START,
RelativeDateOffset::MONTH,
"start-next-month",
N_("Start of next month"),
N_("First day of the next month.")
},
{
RelativeDatePeriod::END_NEXT_MONTH,
RelativeDateType::END,
RelativeDateOffset::MONTH,
"end-next-month",
N_("End of next month"),
N_("Last day of next month.")
},
{
RelativeDatePeriod::START_CURRENT_QUARTER,
RelativeDateType::START,
RelativeDateOffset::QUARTER,
"start-current-quarter",
N_("Start of current quarter"),
N_("First day of the current quarterly accounting period.")
},
{
RelativeDatePeriod::END_CURRENT_QUARTER,
RelativeDateType::END,
RelativeDateOffset::QUARTER,
"end-current-quarter",
N_("End of current quarter"),
N_("Last day of the current quarterly accounting period.")
},
{
RelativeDatePeriod::START_PREV_QUARTER,
RelativeDateType::START,
RelativeDateOffset::QUARTER,
"start-prev-quarter",
N_("Start of previous quarter"),
N_("First day of the previous quarterly accounting period.")
},
{
RelativeDatePeriod::END_PREV_QUARTER,
RelativeDateType::END,
RelativeDateOffset::QUARTER,
"end-prev-quarter",
N_("End of previous quarter"),
N_("Last day of previous quarterly accounting period.")
},
{
RelativeDatePeriod::START_NEXT_QUARTER,
RelativeDateType::START,
RelativeDateOffset::QUARTER,
"start-next-quarter",
N_("Start of next quarter"),
N_("First day of the next quarterly accounting period.")
},
{
RelativeDatePeriod::END_NEXT_QUARTER,
RelativeDateType::END,
RelativeDateOffset::QUARTER,
"end-next-quarter",
N_("End of next quarter"),
N_("Last day of next quarterly accounting period.")
},
{
RelativeDatePeriod::START_CAL_YEAR,
RelativeDateType::START,
RelativeDateOffset::YEAR,
"start-cal-year",
N_("Start of this year"),
N_("First day of the current calendar year.")
},
{
RelativeDatePeriod::END_CAL_YEAR,
RelativeDateType::END,
RelativeDateOffset::YEAR,
"end-cal-year",
N_("End of this year"),
N_("Last day of the current calendar year.")
},
{
RelativeDatePeriod::START_PREV_YEAR,
RelativeDateType::START,
RelativeDateOffset::YEAR,
"start-prev-year",
N_("Start of previous year"),
N_("First day of the previous calendar year.")
},
{
RelativeDatePeriod::END_PREV_YEAR,
RelativeDateType::END,
RelativeDateOffset::YEAR,
"end-prev-year",
N_("End of previous year"),
N_("Last day of the previous calendar year.")
},
{
RelativeDatePeriod::START_NEXT_YEAR,
RelativeDateType::START,
RelativeDateOffset::YEAR,
"start-next-year",
N_("Start of next year"),
N_("First day of the next calendar year.")
},
{
RelativeDatePeriod::END_NEXT_YEAR,
RelativeDateType::END,
RelativeDateOffset::YEAR,
"end-next-year",
N_("End of next year"),
N_("Last day of the next calendar year.")
},
{
RelativeDatePeriod::START_ACCOUNTING_PERIOD,
RelativeDateType::START,
RelativeDateOffset::YEAR,
"start-prev-fin-year",
N_("Start of accounting period"),
N_("First day of the accounting period, as set in the global preferences.")
},
{
RelativeDatePeriod::END_ACCOUNTING_PERIOD,
RelativeDateType::END,
RelativeDateOffset::YEAR,
"end-prev-fin-year",
N_("End of accounting period"),
N_("Last day of the accounting period, as set in the global preferences.")
}
}};
static const GncRelativeDate&
checked_reldate(RelativeDatePeriod per)
{
assert (reldates[static_cast<int>(per)].m_period == per);
return reldates[static_cast<int>(per)];
}
bool
gnc_relative_date_is_single(RelativeDatePeriod per)
{
if (per == RelativeDatePeriod::ABSOLUTE)
return false;
auto reldate = checked_reldate(per);
return reldate.m_type == RelativeDateType::LAST ||
reldate.m_type == RelativeDateType::NEXT;
}
bool
gnc_relative_date_is_starting(RelativeDatePeriod per)
{
if (per == RelativeDatePeriod::ABSOLUTE)
return false;
return checked_reldate(per).m_type == RelativeDateType::START;
}
bool
gnc_relative_date_is_ending(RelativeDatePeriod per)
{
if (per == RelativeDatePeriod::ABSOLUTE)
return false;
return checked_reldate(per).m_type == RelativeDateType::END;
}
const char*
gnc_relative_date_storage_string(RelativeDatePeriod per)
{
if (per == RelativeDatePeriod::ABSOLUTE)
return nullptr;
return checked_reldate(per).m_storage;
}
const char*
gnc_relative_date_display_string(RelativeDatePeriod per)
{
if (per == RelativeDatePeriod::ABSOLUTE)
return nullptr;
return checked_reldate(per).m_display;
}
const char*
gnc_relative_date_description(RelativeDatePeriod per)
{
if (per == RelativeDatePeriod::ABSOLUTE)
return nullptr;
return checked_reldate(per).m_description;
}
RelativeDatePeriod
gnc_relative_date_from_storage_string(const char* str)
{
auto per = std::find_if(reldates.begin(), reldates.end(),
[str](auto rel) -> bool
{
return strcmp(str, rel.m_storage) == 0;
});
return per != reldates.end() ? per->m_period : RelativeDatePeriod::ABSOLUTE;
}
static bool
reldate_is_prev(RelativeDatePeriod per)
{
auto rdate{checked_reldate(per)};
return per == RelativeDatePeriod::START_PREV_YEAR ||
per == RelativeDatePeriod::END_PREV_YEAR ||
per == RelativeDatePeriod::START_PREV_QUARTER ||
per == RelativeDatePeriod::END_PREV_QUARTER ||
per == RelativeDatePeriod::START_PREV_MONTH ||
per == RelativeDatePeriod::END_PREV_MONTH ||
rdate.m_type == LAST;
}
static bool
reldate_is_next(RelativeDatePeriod per)
{
auto rdate{checked_reldate(per)};
return per == RelativeDatePeriod::START_NEXT_YEAR ||
per == RelativeDatePeriod::END_NEXT_YEAR ||
per == RelativeDatePeriod::START_NEXT_QUARTER ||
per == RelativeDatePeriod::END_NEXT_QUARTER ||
per == RelativeDatePeriod::START_NEXT_MONTH ||
per == RelativeDatePeriod::END_NEXT_MONTH ||
rdate.m_type == NEXT;
}
static RelativeDateOffset
reldate_offset(RelativeDatePeriod per)
{
return checked_reldate(per).m_offset;
}
static constexpr int days_in_month[12]{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
/* Normalize the modified struct tm computed in gnc_relative_date_to_time64
* before setting the time and perhaps beginning/end of the month. Using the
* gnc_date API would involve multiple conversions to and from struct tm.
*/
static void
normalize_reldate_tm(struct tm& now)
{
auto factor{abs(now.tm_mon) / 12};
now.tm_mon /= factor > 0 ? factor : 1;
now.tm_year += now.tm_mon < 0 ? -factor: factor;
auto days = [](auto month, int year)
{
auto mon{month % 12 + (month < 0 ? 12 : 0)};
auto num_days{days_in_month[mon]};
//Leap year check.
if (mon == 1 && year % 4 == 0 && !(year % 100 == 0 && (year + 1900) % 400 != 0))
++num_days;
return num_days;
};
while (now.tm_mday < 1)
now.tm_mday += days(--now.tm_mon, now.tm_year);
while (now.tm_mday > days(now.tm_mon, now.tm_year))
now.tm_mday -= days(now.tm_mon++, now.tm_year);
while (now.tm_mon < 0)
{
now.tm_mon += 12;
--now.tm_year;
}
while (now.tm_mon > 11)
{
now.tm_mon -= 12;
++now.tm_year;
}
/* This would happen only if we moved from Feb 29 in a leap year while
* adjusting the months so we don't need to worry about adjusting the year
* again.
*/
if (now.tm_mday > days_in_month[now.tm_mon])
now.tm_mday -= days_in_month[now.tm_mon++];
}
static void
reldate_set_day_and_time(struct tm& now, RelativeDateType type)
{
if (type == RelativeDateType::START)
{
gnc_tm_set_day_start(&now);
now.tm_mday = 1;
}
else if (type == RelativeDateType::END)
{
/* Ensure that the month is between 0 and 12*/
auto year_delta = now.tm_mon / 12 + now.tm_mon < 0 ? -1 : 0;
auto month = now.tm_mon - 12 * year_delta;
auto year = now.tm_year + year_delta + 1900;
now.tm_mday = gnc_date_get_last_mday(month, year);
gnc_tm_set_day_end(&now);
}
// Do nothing for LAST and NEXT.
};
time64
gnc_relative_date_to_time64(RelativeDatePeriod period)
{
if (period == RelativeDatePeriod::TODAY)
return static_cast<time64>(GncDateTime());
if (period == RelativeDatePeriod::START_ACCOUNTING_PERIOD)
return gnc_accounting_period_fiscal_start();
if (period == RelativeDatePeriod::END_ACCOUNTING_PERIOD)
return gnc_accounting_period_fiscal_end();
GncDateTime now_t;
if (period == RelativeDatePeriod::TODAY)
return static_cast<time64>(now_t);
auto now{static_cast<tm>(now_t)};
auto acct_per{static_cast<tm>(GncDateTime(gnc_accounting_period_fiscal_start()))};
if (acct_per.tm_mon == now.tm_mon && acct_per.tm_mday == now.tm_mday)
{
//No set accounting period, use the calendar year
acct_per.tm_mon = 0;
acct_per.tm_mday = 0;
}
switch(reldate_offset(period))
{
case RelativeDateOffset::NONE:
// Report on today so nothing to do
break;
case RelativeDateOffset::YEAR:
if (reldate_is_prev(period))
--now.tm_year;
else if (reldate_is_next(period))
++now.tm_year;
if (gnc_relative_date_is_starting(period))
now.tm_mon = 0;
else if (gnc_relative_date_is_ending(period))
now.tm_mon = 11;
break;
case RelativeDateOffset::SIX:
if (reldate_is_prev(period))
now.tm_mon -= 6;
else if (reldate_is_next(period))
now.tm_mon += 6;
break;
case RelativeDateOffset::QUARTER:
{
auto delta = (now.tm_mon > acct_per.tm_mon ?
now.tm_mon - acct_per.tm_mon :
acct_per.tm_mon - now.tm_mon) % 3;
now.tm_mon = now.tm_mon - delta;
}
[[fallthrough]];
case RelativeDateOffset::THREE:
if (reldate_is_prev(period))
now.tm_mon -= 3;
else if (reldate_is_next(period))
now.tm_mon += 3;
if (gnc_relative_date_is_ending(period))
now.tm_mon += 2;
break;
case RelativeDateOffset::MONTH:
if (reldate_is_prev(period))
--now.tm_mon;
else if (reldate_is_next(period))
++now.tm_mon;
break;
case RelativeDateOffset::WEEK:
if (reldate_is_prev(period))
now.tm_mday -= 7;
else if (reldate_is_next(period))
now.tm_mday += 7;
}
reldate_set_day_and_time(now, checked_reldate(period).m_type);
normalize_reldate_tm(now);
return static_cast<time64>(GncDateTime(now));
}
std::ostream&
operator<<(std::ostream& ostr, RelativeDatePeriod per)
{
ostr << "'reldate . " << gnc_relative_date_display_string(per);
return ostr;
}