Implement GncOptionDateValue.

This commit is contained in:
John Ralls 2019-10-29 16:34:44 -07:00
parent 435667e8fe
commit 3dc4bc2377
9 changed files with 381 additions and 19 deletions

View File

@ -23,5 +23,99 @@
//#include "options.h"
#include "gnc-option.hpp"
#include <engine-helpers-guile.h>
#include <gnc-datetime.hpp>
extern "C"
{
#include "gnc-accounting-period.h"
}
static constexpr int days_in_month[12]{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
static void
normalize_month(struct tm& now)
{
if (now.tm_mon < 0)
{
now.tm_mon += 12;
--now.tm_year;
}
else if (now.tm_mon > 11)
{
now.tm_mon -= 12;
++now.tm_year;
}
}
static void
set_day_and_time(struct tm& now, bool starting)
{
if (starting)
{
now.tm_hour = now.tm_min = now.tm_sec = 0;
now.tm_mday = 1;
}
else
{
now.tm_min = now.tm_sec = 59;
now.tm_hour = 23;
now.tm_mday = days_in_month[now.tm_mon];
// Check for Februrary in a leap year
if (int year = now.tm_year + 1900; now.tm_mon == 1 &&
year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))
++now.tm_mday;
}
};
time64
GncOptionDateValue::get_value() const
{
if (m_type == DateType::ABSOLUTE)
return m_date;
if (m_period == RelativeDatePeriod::TODAY)
return static_cast<time64>(GncDateTime());
if (m_period == RelativeDatePeriod::ACCOUNTING_PERIOD)
return m_type == DateType::STARTING ?
gnc_accounting_period_fiscal_start() :
gnc_accounting_period_fiscal_end();
struct tm now{static_cast<tm>(GncDateTime())};
struct tm period{static_cast<tm>(GncDateTime(gnc_accounting_period_fiscal_start()))};
if (m_period == RelativeDatePeriod::CAL_YEAR ||
m_period == RelativeDatePeriod::PREV_YEAR)
{
if (m_period == RelativeDatePeriod::PREV_YEAR)
--now.tm_year;
now.tm_mon = m_type == DateType::STARTING ? 0 : 11;
}
else if (m_period == RelativeDatePeriod::PREV_QUARTER ||
m_period == RelativeDatePeriod::CURRENT_QUARTER)
{
now.tm_mon = now.tm_mon - (now.tm_mon - period.tm_mon) % 3;
if (m_period == RelativeDatePeriod::PREV_QUARTER)
now.tm_mon -= 3;
if (m_type == DateType::ENDING)
now.tm_mon += 2;
}
else if (m_period == RelativeDatePeriod::PREV_MONTH)
--now.tm_mon;
normalize_month(now);
set_day_and_time(now, m_type == DateType::STARTING);
return static_cast<time64>(GncDateTime(now));
}
void
GncOptionDateValue::set_value(DateSetterValue value)
{
auto [type, val] = value;
m_type = type;
if (type == DateType::ABSOLUTE)
{
m_period = RelativeDatePeriod::TODAY;
m_date = static_cast<time64>(val);
return;
}
m_period = static_cast<RelativeDatePeriod>(val);
m_date = 0;
}

View File

@ -31,6 +31,7 @@ extern "C"
#include <gnc-budget.h>
#include <gnc-commodity.h>
}
#include <gnc-datetime.hpp>
#include <gnc-numeric.hpp>
#include <guid.hpp>
#include <libguile.h>
@ -295,7 +296,6 @@ private:
*
*
*/
using GncMultiChoiceOptionEntry = std::tuple<const std::string,
const std::string,
const std::string>;
@ -306,7 +306,7 @@ class GncOptionMultichoiceValue :
{
public:
GncOptionMultichoiceValue(const char* section, const char* name,
const char* key, const char* doc_string,
const char* key, const char* doc_string,
GncMultiChoiceOptionChoices&& choices,
GncOptionUIType ui_type = GncOptionUIType::MULTICHOICE) :
OptionClassifier{section, name, key, doc_string},
@ -378,16 +378,75 @@ private:
GncMultiChoiceOptionChoices m_choices;
};
/** Date options
* A legal date value is a pair of either and a RelativeDatePeriod, the absolute flag and a time64, or for legacy purposes the absolute flag and a timespec.
* The original design allowed custom RelativeDatePeriods, but that facility is unused so we'll go with compiled-in enums.
gnc-date-option-show-time? -- option_data[1]
gnc-date-option-get-subtype -- option_data[0]
gnc-date-option-value-type m_value
gnc-date-option-absolute-time
gnc-date-option-relative-time
*/
enum class DateType
{
ABSOLUTE,
STARTING,
ENDING,
};
enum class RelativeDatePeriod : int64_t
{
TODAY,
THIS_MONTH,
PREV_MONTH,
CURRENT_QUARTER,
PREV_QUARTER,
CAL_YEAR,
PREV_YEAR,
ACCOUNTING_PERIOD
};
using DateSetterValue = std::pair<DateType, int64_t>;
class GncOptionDateValue : public OptionClassifier, public OptionUIItem
{
public:
GncOptionDateValue(const char* section, const char* name,
const char* key, const char* doc_string) :
OptionClassifier{section, name, key, doc_string},
OptionUIItem(GncOptionUIType::DATE),
m_type{DateType::ABSOLUTE}, m_period{RelativeDatePeriod::TODAY},
m_date{static_cast<time64>(GncDateTime())} {}
GncOptionDateValue(const GncOptionDateValue&) = default;
GncOptionDateValue(GncOptionDateValue&&) = default;
GncOptionDateValue& operator=(const GncOptionDateValue&) = default;
GncOptionDateValue& operator=(GncOptionDateValue&&) = default;
time64 get_value() const;
time64 get_default_value() const { return static_cast<time64>(GncDateTime()); }
void set_value(DateSetterValue);
void set_value(time64 time) {
m_type = DateType::ABSOLUTE;
m_period = RelativeDatePeriod::TODAY;
m_date = time;
}
private:
DateType m_type;
RelativeDatePeriod m_period;
time64 m_date;
};
using GncOptionVariant = std::variant<GncOptionValue<std::string>,
GncOptionValue<bool>,
GncOptionValue<int64_t>,
GncOptionValue<QofInstance*>,
GncOptionValue<QofQuery*>,
GncOptionValue<std::vector<GncGUID>>,
GncOptionMultichoiceValue,
GncOptionRangeValue<int>,
GncOptionRangeValue<GncNumeric>,
GncOptionValidatedValue<QofInstance*>>;
GncOptionValue<bool>,
GncOptionValue<int64_t>,
GncOptionValue<QofInstance*>,
GncOptionValue<QofQuery*>,
GncOptionValue<std::vector<GncGUID>>,
GncOptionMultichoiceValue,
GncOptionRangeValue<int>,
GncOptionRangeValue<GncNumeric>,
GncOptionValidatedValue<QofInstance*>,
GncOptionDateValue>;
class GncOption
{
@ -408,7 +467,7 @@ public:
return std::visit([](const auto& option)->ValueType {
if constexpr (std::is_same_v<std::decay_t<decltype(option.get_value())>, std::decay_t<ValueType>>)
return option.get_value();
return ValueType {};
return ValueType {};
}, m_option);
}
@ -417,7 +476,7 @@ public:
return std::visit([](const auto& option)->ValueType {
if constexpr (std::is_same_v<std::decay_t<decltype(option.get_value())>, std::decay_t<ValueType>>)
return option.get_default_value();
return ValueType {};
return ValueType {};
}, m_option);
}
@ -426,7 +485,7 @@ public:
{
std::visit([value](auto& option) {
if constexpr (std::is_same_v<std::decay_t<decltype(option.get_value())>, std::decay_t<ValueType>>)
option.set_value(value);
option.set_value(value);
}, m_option);
}
const std::string& get_section() const

View File

@ -453,3 +453,12 @@ gnc_register_currency_option(const GncOptionDBPtr& db, const char* section,
}};
db->register_option(section, std::move(option));
}
void
gnc_register_date_interval_option(const GncOptionDBPtr& db, const char* section,
const char* name, const char* key,
const char* doc_string)
{
GncOption option{GncOptionDateValue(section, name, key, doc_string)};
db->register_option(section, std::move(option));
}

View File

@ -278,5 +278,7 @@ void gnc_register_dateformat_option(const GncOptionDBPtr& db,
const char* key, const char* doc_string,
std::string value);
void gnc_register_date_interval_option(const GncOptionDBPtr& db,
const char* section, const char* name,
const char* key, const char* doc_string);
#endif //GNC_OPTIONDB_HPP_

View File

@ -121,6 +121,13 @@ inline SCM
%ignore GncOptionMultichoiceValue(GncOptionMultichoiceValue&&);
%ignore GncOptionMultichoiceValue::operator=(const GncOptionMultichoiceValue&);
%ignore GncOptionMultichoiceValue::operator=(GncOptionMultichoiceValue&&);
%ignore GncOptionDateValue(GncOptionDateValue&&);
%ignore GncOptionDateValue::operator=(const GncOptionDateValue&);
%ignore GncOptionDateValue::operator=(GncOptionDateValue&&);
%typemap(typecheck, precedence=SWIG_TYPECHECK_INT64) time64 {
$1 = scm_is_signed_integer($input, INT64_MAX, INT64_MIN);
}
%typemap(in) GncMultiChoiceOptionChoices&& (GncMultiChoiceOptionChoices choices)
{
@ -168,6 +175,7 @@ wrap_unique_ptr(GncOptionDBPtr, GncOptionDB);
%template(set_option_string) set_option<std::string>;
%template(set_option_int) set_option<int>;
%template(set_option_time64) set_option<time64>;
};

View File

@ -43,6 +43,7 @@ set(gtest_gnc_option_INCLUDES
${GUILE_INCLUDE_DIRS})
set(gtest_gnc_option_LIBS
gncmod-app-utils
gncmod-engine
${GLIB2_LDFLAGS}
${GUILE_LDFLAGS}
@ -109,6 +110,7 @@ if (HAVE_SRFI64)
set(swig_gnc_optiondb_LIBS
gncmod-engine
gncmod-app-utils
${GLIB2_LDFLAGS}
${GUILE_LDFLAGS}
)

View File

@ -23,6 +23,11 @@
#include <gtest/gtest.h>
#include <gnc-option.hpp>
extern "C"
{
#include <gnc-date.h>
#include <time.h>
}
TEST(GncOption, test_string_ctor)
{
@ -267,3 +272,164 @@ TEST_F(GncMultichoiceOption, test_permissible_value_stuff)
EXPECT_EQ(std::numeric_limits<std::size_t>::max(),
m_option.permissible_value_index("xyzzy"));
}
class GncOptionDateOptionTest : public ::testing::Test
{
protected:
GncOptionDateOptionTest() :
m_option{GncOptionDateValue{"foo", "bar", "a", "Phony Date Option"}} {}
GncOptionDateValue m_option;
};
using GncDateOption = GncOptionDateOptionTest;
static time64
time64_from_gdate(const GDate* g_date, DayPart when)
{
GncDate date{g_date_get_year(g_date), g_date_get_month(g_date),
g_date_get_day(g_date)};
GncDateTime time{date, when};
return static_cast<time64>(time);
}
TEST_F(GncDateOption, test_set_and_get_absolute)
{
time64 time1{static_cast<time64>(GncDateTime("2019-07-19 15:32:26 +05:00"))};
DateSetterValue value1{DateType::ABSOLUTE, time1};
m_option.set_value(value1);
EXPECT_EQ(time1, m_option.get_value());
}
TEST_F(GncDateOption, test_set_and_get_month_start)
{
GDate month_start;
g_date_set_time_t(&month_start, time(nullptr));
gnc_gdate_set_month_start(&month_start);
time64 time1{time64_from_gdate(&month_start, DayPart::start)};
DateSetterValue value1{DateType::STARTING, static_cast<int64_t>(RelativeDatePeriod::THIS_MONTH)};
m_option.set_value(value1);
EXPECT_EQ(time1, m_option.get_value());
}
TEST_F(GncDateOption, test_set_and_get_month_end)
{
GDate month_end;
g_date_set_time_t(&month_end, time(nullptr));
gnc_gdate_set_month_end(&month_end);
time64 time1{time64_from_gdate(&month_end, DayPart::end)};
DateSetterValue value1{DateType::ENDING, static_cast<int64_t>(RelativeDatePeriod::THIS_MONTH)};
m_option.set_value(value1);
EXPECT_EQ(time1, m_option.get_value());
}
TEST_F(GncDateOption, test_set_and_get_prev_month_start)
{
GDate prev_month_start;
g_date_set_time_t(&prev_month_start, time(nullptr));
gnc_gdate_set_prev_month_start(&prev_month_start);
time64 time1{time64_from_gdate(&prev_month_start, DayPart::start)};
DateSetterValue value1{DateType::STARTING, static_cast<int64_t>(RelativeDatePeriod::PREV_MONTH)};
m_option.set_value(value1);
EXPECT_EQ(time1, m_option.get_value());
}
TEST_F(GncDateOption, test_set_and_get_prev_month_end)
{
GDate prev_month_end;
g_date_set_time_t(&prev_month_end, time(nullptr));
gnc_gdate_set_prev_month_end(&prev_month_end);
time64 time1{time64_from_gdate(&prev_month_end, DayPart::end)};
DateSetterValue value1{DateType::ENDING, static_cast<int64_t>(RelativeDatePeriod::PREV_MONTH)};
m_option.set_value(value1);
EXPECT_EQ(time1, m_option.get_value());
}
TEST_F(GncDateOption, test_set_and_get_quarter_start)
{
GDate quarter_start;
g_date_set_time_t(&quarter_start, time(nullptr));
gnc_gdate_set_quarter_start(&quarter_start);
time64 time1{time64_from_gdate(&quarter_start, DayPart::start)};
DateSetterValue value1{DateType::STARTING, static_cast<int64_t>(RelativeDatePeriod::CURRENT_QUARTER)};
m_option.set_value(value1);
EXPECT_EQ(time1, m_option.get_value());
}
TEST_F(GncDateOption, test_set_and_get_quarter_end)
{
GDate quarter_end;
g_date_set_time_t(&quarter_end, time(nullptr));
gnc_gdate_set_quarter_end(&quarter_end);
time64 time1{time64_from_gdate(&quarter_end, DayPart::end)};
DateSetterValue value1{DateType::ENDING, static_cast<int64_t>(RelativeDatePeriod::CURRENT_QUARTER)};
m_option.set_value(value1);
EXPECT_EQ(time1, m_option.get_value());
}
TEST_F(GncDateOption, test_set_and_get_prev_quarter_start)
{
GDate prev_quarter_start;
g_date_set_time_t(&prev_quarter_start, time(nullptr));
gnc_gdate_set_prev_quarter_start(&prev_quarter_start);
time64 time1{time64_from_gdate(&prev_quarter_start, DayPart::start)};
DateSetterValue value1{DateType::STARTING, static_cast<int64_t>(RelativeDatePeriod::PREV_QUARTER)};
m_option.set_value(value1);
EXPECT_EQ(time1, m_option.get_value());
}
TEST_F(GncDateOption, test_set_and_get_prev_quarter_end)
{
GDate prev_quarter_end;
g_date_set_time_t(&prev_quarter_end, time(nullptr));
gnc_gdate_set_prev_quarter_end(&prev_quarter_end);
time64 time1{time64_from_gdate(&prev_quarter_end, DayPart::end)};
DateSetterValue value1{DateType::ENDING, static_cast<int64_t>(RelativeDatePeriod::PREV_QUARTER)};
m_option.set_value(value1);
EXPECT_EQ(time1, m_option.get_value());
}
TEST_F(GncDateOption, test_set_and_get_year_start)
{
GDate year_start;
g_date_set_time_t(&year_start, time(nullptr));
gnc_gdate_set_year_start(&year_start);
time64 time1{time64_from_gdate(&year_start, DayPart::start)};
DateSetterValue value1{DateType::STARTING, static_cast<int64_t>(RelativeDatePeriod::CAL_YEAR)};
m_option.set_value(value1);
EXPECT_EQ(time1, m_option.get_value());
}
TEST_F(GncDateOption, test_set_and_get_year_end)
{
GDate year_end;
g_date_set_time_t(&year_end, time(nullptr));
gnc_gdate_set_year_end(&year_end);
time64 time1{time64_from_gdate(&year_end, DayPart::end)};
DateSetterValue value1{DateType::ENDING, static_cast<int64_t>(RelativeDatePeriod::CAL_YEAR)};
m_option.set_value(value1);
EXPECT_EQ(time1, m_option.get_value());
}
TEST_F(GncDateOption, test_set_and_get_prev_year_start)
{
GDate prev_year_start;
g_date_set_time_t(&prev_year_start, time(nullptr));
gnc_gdate_set_prev_year_start(&prev_year_start);
time64 time1{time64_from_gdate(&prev_year_start, DayPart::start)};
DateSetterValue value1{DateType::STARTING, static_cast<int64_t>(RelativeDatePeriod::PREV_YEAR)};
m_option.set_value(value1);
EXPECT_EQ(time1, m_option.get_value());
}
TEST_F(GncDateOption, test_set_and_get_prev_year_end)
{
GDate prev_year_end;
g_date_set_time_t(&prev_year_end, time(nullptr));
gnc_gdate_set_prev_year_end(&prev_year_end);
time64 time1{time64_from_gdate(&prev_year_end, DayPart::end)};
DateSetterValue value1{DateType::ENDING, static_cast<int64_t>(RelativeDatePeriod::PREV_YEAR)};
m_option.set_value(value1);
EXPECT_EQ(time1, m_option.get_value());
}

View File

@ -82,6 +82,14 @@ TEST_F(GncOptionDBTest, test_register_multichoice_option)
EXPECT_STREQ("corge", m_db->lookup_string_option("foo", "bar").c_str());
}
TEST_F(GncOptionDBTest, test_register_date_interval_option)
{
gnc_register_date_interval_option(m_db, "foo", "bar", "baz", "Phony Option");
auto time{gnc_dmy2time64(11, 7, 2019)};
ASSERT_TRUE(m_db->set_option("foo", "bar", time));
EXPECT_EQ(time, m_db->find_option("foo", "bar")->get().get_value<time64>());
}
class GncUIType
{
public:

View File

@ -23,10 +23,12 @@
(use-modules (srfi srfi-64))
(use-modules (tests srfi64-extras))
(use-modules (gnucash gnc-module))
(eval-when
(compile load eval expand)
(load-extension "libswig-gnc-optiondb" "scm_init_sw_gnc_optiondb_module"))
(gnc:module-load "gnucash/engine" 0)
(use-modules (sw_gnc_optiondb))
(define (run-test)
@ -34,6 +36,7 @@
(test-begin "test-gnc-optiondb-scheme")
(test-gnc-make-text-option)
(test-gnc-make-multichoice-option)
(test-gnc-make-date-option)
(test-end "test-gnc-optiondb-scheme"))
(define (test-gnc-make-text-option)
@ -79,5 +82,16 @@
(test-equal "corge" (GncOptionDB-lookup-option
(GncOptionDBPtr-get option-db) "foo" "bar")))
(test-end "test-gnc-test-multichoice-option"))
(GncOptionDBPtr-get option-db) "foo" "bar"))
(test-end "test-gnc-test-multichoice-option")))
(define (test-gnc-make-date-option)
(test-begin "test-gnc-test-date-option")
(let* ((option-db (gnc-option-db-new))
(date-opt (gnc-register-date-interval-option option-db "foo" "bar"
"baz" "Phony Option"))
(a-time (gnc-dmy2time64 2019 07 11)))
(test-equal (current-time) (GncOptionDB-lookup-option
(GncOptionDBPtr-get option-db) "foo" "bar"))
(GncOptionDB-set-option-time64 (GncOptionDBPtr-get option-db) "foo" "bar" a-time)
(test-equal a-time (GncOptionDB-lookup-option
(GncOptionDBPtr-get option-db) "foo" "bar"))
(test-end "test-gnc-test-date-option")))