gnucash stable: Multiple changes pushed
John Ralls
jralls at code.gnucash.org
Sun Mar 23 19:28:28 EDT 2025
Updated via https://github.com/Gnucash/gnucash/commit/ad92ae41 (commit)
via https://github.com/Gnucash/gnucash/commit/b5b2221e (commit)
via https://github.com/Gnucash/gnucash/commit/cf369fb2 (commit)
via https://github.com/Gnucash/gnucash/commit/4e4dcf17 (commit)
from https://github.com/Gnucash/gnucash/commit/2f3aa62a (commit)
commit ad92ae41f2e04a0581359fed5587056c3be39fff
Merge: 2f3aa62a6b b5b2221e72
Author: John Ralls <jralls at ceridwen.us>
Date: Sun Mar 23 16:28:02 2025 -0700
Merge Sherlock's 'non-aligned-periods' into stable
commit b5b2221e72cb9c1509f8c4ca6ee238b4301f597e
Author: John Ralls <jralls at ceridwen.us>
Date: Sun Mar 23 12:11:51 2025 -0700
Test gnc-option-date variable fy start days.
Leverages the FakePrefsBackend from the previous commit to change the
fiscal year for testing.
Makes now_t a defaulted parameter of gnc_relative_date_to_time64 so
that we can inject different reference dates for testing.
diff --git a/bindings/guile/gnc-optiondb.i b/bindings/guile/gnc-optiondb.i
index 5d2cf7e0e7..29a7a9591a 100644
--- a/bindings/guile/gnc-optiondb.i
+++ b/bindings/guile/gnc-optiondb.i
@@ -591,6 +591,10 @@ gnc_option_test_book_destroy(QofBook* book)
$1 = rdp;
}
+%typecheck(SWIG_TYPECHECK_INTEGER) RelativeDatePeriod {
+ $1 = scm_is_integer($input) || scm_is_symbol($input) ? 1 : 0;
+}
+
%typemap(in) RelativeDatePeriodVec& (RelativeDatePeriodVec period_set)
{
for (SCM node = $input; !scm_is_null (node); node = scm_cdr (node))
diff --git a/libgnucash/engine/gnc-option-date.cpp b/libgnucash/engine/gnc-option-date.cpp
index a370681b3a..d5c0e104c0 100644
--- a/libgnucash/engine/gnc-option-date.cpp
+++ b/libgnucash/engine/gnc-option-date.cpp
@@ -516,7 +516,7 @@ reldate_set_day_and_time(
};
time64
-gnc_relative_date_to_time64(RelativeDatePeriod period)
+gnc_relative_date_to_time64(RelativeDatePeriod period, time64 now_t)
{
if (period == RelativeDatePeriod::TODAY)
return static_cast<time64>(GncDateTime());
@@ -525,10 +525,9 @@ gnc_relative_date_to_time64(RelativeDatePeriod period)
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)};
+ return now_t;
+ auto now{static_cast<tm>(GncDateTime(now_t))};
struct tm acct_per =
static_cast<tm>(GncDateTime(gnc_accounting_period_fiscal_start()));
auto offset = reldate_offset(period);
diff --git a/libgnucash/engine/gnc-option-date.hpp b/libgnucash/engine/gnc-option-date.hpp
index 98dc0fac07..52dfbbbed1 100644
--- a/libgnucash/engine/gnc-option-date.hpp
+++ b/libgnucash/engine/gnc-option-date.hpp
@@ -32,7 +32,7 @@
#define GNC_OPTION_DATE_HPP_
#include "gnc-date.h"
-
+#include "gnc-datetime.hpp"
#include <vector>
#include <iostream>
/**
@@ -167,9 +167,11 @@ RelativeDatePeriod gnc_relative_date_from_storage_string(const char*);
* both in the current time zone.
*
* @param period The relative date period to use to calculate the concrete date.
+ * @param now_t Optional "now" date. Primarily for testing.
* @return a time64.
*/
-time64 gnc_relative_date_to_time64(RelativeDatePeriod);
+time64 gnc_relative_date_to_time64(RelativeDatePeriod,
+ time64 now_t = static_cast<time64>(GncDateTime()));
/**
* Add the display string to the provided std::ostream.
diff --git a/libgnucash/engine/test/gtest-gnc-option.cpp b/libgnucash/engine/test/gtest-gnc-option.cpp
index b1d1779c45..2a8dd380f4 100644
--- a/libgnucash/engine/test/gtest-gnc-option.cpp
+++ b/libgnucash/engine/test/gtest-gnc-option.cpp
@@ -950,6 +950,8 @@ class FakePrefsBackend
int m_endabs = 0;
int m_startperiod = GNC_ACCOUNTING_PERIOD_CYEAR;
int m_endperiod = GNC_ACCOUNTING_PERIOD_CYEAR;
+ time64 m_startdate = 0;
+ time64 m_enddate = 0;
public:
int get_bool([[maybe_unused]] const char* group, const char* setting)
{
@@ -984,6 +986,23 @@ public:
if (strcmp (setting, GNC_PREF_END_PERIOD) == 0)
m_endperiod = value;
}
+
+ time64 get_time64([[maybe_unused]] const char* group, const char* setting)
+ {
+ if (strcmp (setting, GNC_PREF_START_DATE) == 0)
+ return m_startdate;
+ if (strcmp (setting, GNC_PREF_END_DATE) == 0)
+ return m_enddate;
+ return -1;
+ }
+
+ void set_time64([[maybe_unused]] const char* group, const char* setting, time64 value)
+ {
+ if (strcmp (setting, GNC_PREF_START_DATE) == 0)
+ m_startdate = value;
+ if (strcmp (setting, GNC_PREF_END_DATE) == 0)
+ m_enddate = value;
+ }
};
FakePrefsBackend fpb;
@@ -1010,6 +1029,18 @@ static int set_int(const char* group, const char* setting, int value)
return 1;
}
+static GVariant* get_value(const char* group, const char* setting)
+{
+ return g_variant_new("x", fpb.get_time64(group, setting));
+}
+
+static int set_value(const char* group, const char* setting, GVariant *value)
+{
+ fpb.set_int(group, setting, g_variant_get_int64(value));
+ g_variant_unref(value);
+ return 1;
+}
+
class GncOptionDateTest : public ::testing::Test
{
protected:
@@ -1020,11 +1051,13 @@ protected:
prefsbackend->set_bool = set_bool;
prefsbackend->get_int = get_int;
prefsbackend->set_int = set_int;
+ prefsbackend->get_value = get_value;
+ prefsbackend->set_value = set_value;
}
~GncOptionDateTest()
{
g_free(prefsbackend);
- gnc_clear_current_session();
+ prefsbackend = nullptr;
}
};
@@ -1099,6 +1132,98 @@ TEST_F(GncOptionDateTest, test_gnc_relative_date_to_time64)
gnc_relative_date_to_time64(RelativeDatePeriod::END_PREV_YEAR));
}
+static void
+set_fy(int year, int month, int day)
+{
+ auto start{static_cast<time64>(GncDateTime(GncDate(year, month, day), DayPart::start))};
+ fpb.set_time64(GNC_PREFS_GROUP_ACCT_SUMMARY, GNC_PREF_START_DATE, start);
+}
+
+static time64
+time64_from_ymd(int year, int month, int day)
+{
+ return static_cast<time64>(GncDateTime(GncDate(year, month, day)));
+}
+
+static time64
+time64_from_ymd_start(int year, int month, int day)
+{
+ return static_cast<time64>(GncDateTime(GncDate(year, month, day), DayPart::start));
+}
+
+static time64
+time64_from_ymd_end(int year, int month, int day)
+{
+ return static_cast<time64>(GncDateTime(GncDate(year, month, day), DayPart::end));
+}
+
+TEST_F(GncOptionDateTest, test_fiscal_quarter_variation)
+{
+ fpb.set_bool(GNC_PREFS_GROUP_ACCT_SUMMARY, GNC_PREF_START_CHOICE_ABS, TRUE);
+ fpb.set_bool(GNC_PREFS_GROUP_ACCT_SUMMARY, GNC_PREF_END_CHOICE_ABS, TRUE);
+
+ auto this_year{GncDate().year_month_day().year};
+ auto last_year = this_year - 1;
+ set_fy(last_year, 7, 1);
+ auto at_date{time64_from_ymd(this_year, 1, 1)};
+ EXPECT_EQ(time64_from_ymd_start(this_year, 1, 1),
+ gnc_relative_date_to_time64(RelativeDatePeriod::START_CURRENT_QUARTER, at_date));
+ EXPECT_EQ(time64_from_ymd_end(this_year, 3, 31),
+ gnc_relative_date_to_time64(RelativeDatePeriod::END_CURRENT_QUARTER, at_date));
+ EXPECT_EQ(time64_from_ymd_start(last_year, 10, 1),
+ gnc_relative_date_to_time64(RelativeDatePeriod::START_PREV_QUARTER, at_date));
+ EXPECT_EQ(time64_from_ymd_end(last_year, 12, 31),
+ gnc_relative_date_to_time64(RelativeDatePeriod::END_PREV_QUARTER, at_date));
+ EXPECT_EQ(time64_from_ymd_start(this_year, 4, 1),
+ gnc_relative_date_to_time64(RelativeDatePeriod::START_NEXT_QUARTER, at_date));
+ EXPECT_EQ(time64_from_ymd_end(this_year, 6, 30),
+ gnc_relative_date_to_time64(RelativeDatePeriod::END_NEXT_QUARTER, at_date));
+
+ at_date = time64_from_ymd(this_year, 2, 15);
+ EXPECT_EQ(time64_from_ymd_start(this_year, 1, 1),
+ gnc_relative_date_to_time64(RelativeDatePeriod::START_CURRENT_QUARTER, at_date));
+ EXPECT_EQ(time64_from_ymd_end(this_year, 3, 31),
+ gnc_relative_date_to_time64(RelativeDatePeriod::END_CURRENT_QUARTER, at_date));
+ EXPECT_EQ(time64_from_ymd_start(last_year, 10, 1),
+ gnc_relative_date_to_time64(RelativeDatePeriod::START_PREV_QUARTER, at_date));
+ EXPECT_EQ(time64_from_ymd_end(last_year, 12, 31),
+ gnc_relative_date_to_time64(RelativeDatePeriod::END_PREV_QUARTER, at_date));
+ EXPECT_EQ(time64_from_ymd_start(this_year, 4, 1),
+ gnc_relative_date_to_time64(RelativeDatePeriod::START_NEXT_QUARTER, at_date));
+ EXPECT_EQ(time64_from_ymd_end(this_year, 6, 30),
+ gnc_relative_date_to_time64(RelativeDatePeriod::END_NEXT_QUARTER, at_date));
+
+ at_date = time64_from_ymd(this_year, 3, 31);
+ EXPECT_EQ(time64_from_ymd_start(this_year, 1, 1),
+ gnc_relative_date_to_time64(RelativeDatePeriod::START_CURRENT_QUARTER, at_date));
+ EXPECT_EQ(time64_from_ymd_end(this_year, 3, 31),
+ gnc_relative_date_to_time64(RelativeDatePeriod::END_CURRENT_QUARTER, at_date));
+ EXPECT_EQ(time64_from_ymd_start(last_year, 10, 1),
+ gnc_relative_date_to_time64(RelativeDatePeriod::START_PREV_QUARTER, at_date));
+ EXPECT_EQ(time64_from_ymd_end(last_year, 12, 31),
+ gnc_relative_date_to_time64(RelativeDatePeriod::END_PREV_QUARTER, at_date));
+ EXPECT_EQ(time64_from_ymd_start(this_year, 4, 1),
+ gnc_relative_date_to_time64(RelativeDatePeriod::START_NEXT_QUARTER, at_date));
+ EXPECT_EQ(time64_from_ymd_end(this_year, 6, 30),
+ gnc_relative_date_to_time64(RelativeDatePeriod::END_NEXT_QUARTER, at_date));
+
+ set_fy(last_year, 4, 6);
+
+ at_date = time64_from_ymd(this_year, 2, 15);
+ EXPECT_EQ(time64_from_ymd_start(this_year, 1, 6),
+ gnc_relative_date_to_time64(RelativeDatePeriod::START_CURRENT_QUARTER, at_date));
+ EXPECT_EQ(time64_from_ymd_end(this_year, 4, 5),
+ gnc_relative_date_to_time64(RelativeDatePeriod::END_CURRENT_QUARTER, at_date));
+ EXPECT_EQ(time64_from_ymd_start(last_year, 10, 6),
+ gnc_relative_date_to_time64(RelativeDatePeriod::START_PREV_QUARTER, at_date));
+ EXPECT_EQ(time64_from_ymd_end(this_year, 1, 5),
+ gnc_relative_date_to_time64(RelativeDatePeriod::END_PREV_QUARTER, at_date));
+ EXPECT_EQ(time64_from_ymd_start(this_year, 4, 6),
+ gnc_relative_date_to_time64(RelativeDatePeriod::START_NEXT_QUARTER, at_date));
+ EXPECT_EQ(time64_from_ymd_end(this_year, 7, 5),
+ gnc_relative_date_to_time64(RelativeDatePeriod::END_NEXT_QUARTER, at_date));
+}
+
class GncOptionDateOptionTest : public ::testing::Test
{
protected:
@@ -1111,10 +1236,13 @@ protected:
prefsbackend->set_bool = set_bool;
prefsbackend->get_int = get_int;
prefsbackend->set_int = set_int;
+ prefsbackend->get_value = get_value;
+ prefsbackend->set_value = set_value;
}
~GncOptionDateOptionTest()
{
g_free(prefsbackend);
+ prefsbackend = nullptr;
}
GncOption m_option;
@@ -1176,6 +1304,9 @@ TEST_F(GncDateOptionList, test_set_and_get_relative)
TEST_F(GncDateOption, test_stream_out)
{
+ fpb.set_bool(GNC_PREFS_GROUP_ACCT_SUMMARY, GNC_PREF_START_CHOICE_ABS, FALSE);
+ fpb.set_bool(GNC_PREFS_GROUP_ACCT_SUMMARY, GNC_PREF_END_CHOICE_ABS, FALSE);
+
time64 time1{static_cast<time64>(GncDateTime("2019-07-19 15:32:26 +05:00"))};
m_option.set_value(time1);
std::ostringstream oss;
commit cf369fb2704c60aa8b5460b504292b8f56e5ad4f
Author: John Ralls <jralls at ceridwen.us>
Date: Sat Mar 22 15:41:49 2025 -0700
Add and use a fake preferences backend to set the fiscal year.
This restores the expected precondition for the quarter tests of the
Accounting Period being the current calendar year, replacing the
today-relative quarter begin and end dates.
diff --git a/libgnucash/engine/test/gtest-gnc-option.cpp b/libgnucash/engine/test/gtest-gnc-option.cpp
index cbda3a93fa..b1d1779c45 100644
--- a/libgnucash/engine/test/gtest-gnc-option.cpp
+++ b/libgnucash/engine/test/gtest-gnc-option.cpp
@@ -29,6 +29,9 @@
#include <config.h>
#include "qof.h"
#include "Account.h"
+#include <gnc-accounting-period.h>
+#include <gnc-prefs.h>
+#include <gnc-prefs-p.h>
#include "gnc-budget.h"
#include "gnc-commodity.h"
#include "gnc-date.h"
@@ -941,52 +944,91 @@ TEST(GncOptionDate, test_not_using_32bit_time_t_in_2038)
EXPECT_FALSE(sizeof (time_t) == 4 && time(nullptr) <= 0) << "Time to upgrade 32bit time_t!";
}
-// The relative current quarter start date is the date provided.
-static void
-set_current_quarter_start(GDate *date)
+class FakePrefsBackend
{
-}
+ int m_startabs = 0;
+ int m_endabs = 0;
+ int m_startperiod = GNC_ACCOUNTING_PERIOD_CYEAR;
+ int m_endperiod = GNC_ACCOUNTING_PERIOD_CYEAR;
+public:
+ int get_bool([[maybe_unused]] const char* group, const char* setting)
+ {
+ if (strcmp (setting, GNC_PREF_START_CHOICE_ABS) == 0)
+ return m_startabs;
+ if (strcmp (setting, GNC_PREF_END_CHOICE_ABS) == 0)
+ return m_endabs;
+ return -1;
+ }
+
+ void set_bool([[maybe_unused]] const char* group, const char* setting, int value)
+ {
+ if (strcmp (setting, GNC_PREF_START_CHOICE_ABS) == 0)
+ m_startabs = value;
+ if (strcmp (setting, GNC_PREF_END_CHOICE_ABS) == 0)
+ m_endabs = value;
+ }
+
+ int get_int([[maybe_unused]] const char* group, const char* setting)
+ {
+ if (strcmp (setting, GNC_PREF_START_PERIOD) == 0)
+ return m_startperiod;
+ if (strcmp (setting, GNC_PREF_END_PERIOD) == 0)
+ return m_endperiod;
+ return -1;
+ }
-static GDateDay
-get_last_day_of_month(GDate *date)
+ void set_int([[maybe_unused]] const char* group, const char* setting, int value)
+ {
+ if (strcmp (setting, GNC_PREF_START_PERIOD) == 0)
+ m_startperiod = value;
+ if (strcmp (setting, GNC_PREF_END_PERIOD) == 0)
+ m_endperiod = value;
+ }
+};
+
+FakePrefsBackend fpb;
+
+static int get_bool(const char* group, const char* setting)
{
- return gnc_date_get_last_mday(g_date_get_month(date) - G_DATE_JANUARY, g_date_get_year(date));
+ return fpb.get_bool(group, setting);
}
-static void
-set_current_quarter_end(GDate *date)
+static int set_bool(const char* group, const char* setting, int value)
{
- bool is_last_of_month = g_date_is_last_of_month(date);
- GDateDay day = g_date_get_day(date);
- GDateDay last_day_of_month;
-
- g_date_set_day(date, 1);
- g_date_add_months(date, 3);
- last_day_of_month = get_last_day_of_month(date);
- g_date_set_day(date, is_last_of_month || day > last_day_of_month ? last_day_of_month : day);
- g_date_subtract_days(date, 1);
+ fpb.set_bool(group, setting, value);
+ return 1;
}
-static void
-set_previous_quarter_start(GDate *date)
+static int get_int(const char* group, const char* setting)
{
- bool is_last_of_month = g_date_is_last_of_month(date);
- GDateDay day = g_date_get_day(date);
- GDateDay last_day_of_month;
-
- g_date_set_day(date, 1);
- g_date_subtract_months(date, 3);
- last_day_of_month = get_last_day_of_month(date);
- g_date_set_day(date, is_last_of_month || day > last_day_of_month ? last_day_of_month : day);
+ return fpb.get_int(group, setting);
}
-static void
-set_previous_quarter_end(GDate *date)
+static int set_int(const char* group, const char* setting, int value)
{
- g_date_subtract_days(date, 1);
+ fpb.set_int(group, setting, value);
+ return 1;
}
-TEST(GncOptionDate, test_gnc_relative_date_to_time64)
+class GncOptionDateTest : public ::testing::Test
+{
+protected:
+ GncOptionDateTest()
+ {
+ prefsbackend = g_new0(PrefsBackend, 1);
+ prefsbackend->get_bool = get_bool;
+ prefsbackend->set_bool = set_bool;
+ prefsbackend->get_int = get_int;
+ prefsbackend->set_int = set_int;
+ }
+ ~GncOptionDateTest()
+ {
+ g_free(prefsbackend);
+ gnc_clear_current_session();
+ }
+};
+
+TEST_F(GncOptionDateTest, test_gnc_relative_date_to_time64)
{
GDate date;
g_date_set_time_t(&date, time(nullptr));
@@ -1012,25 +1054,25 @@ TEST(GncOptionDate, test_gnc_relative_date_to_time64)
EXPECT_EQ(time1,
gnc_relative_date_to_time64(RelativeDatePeriod::END_PREV_MONTH));
g_date_set_time_t(&date, time(nullptr));
- set_current_quarter_start(&date);
+ gnc_gdate_set_quarter_start(&date);
time1 = time64_from_gdate(&date, DayPart::start);
EXPECT_EQ(time1,
gnc_relative_date_to_time64(RelativeDatePeriod::START_CURRENT_QUARTER));
g_date_set_time_t(&date, time(nullptr));
- set_current_quarter_end(&date);
+ gnc_gdate_set_quarter_end(&date);
time1 = time64_from_gdate(&date, DayPart::end);
EXPECT_EQ(time1,
gnc_relative_date_to_time64(RelativeDatePeriod::END_CURRENT_QUARTER));
g_date_set_time_t(&date, time(nullptr));
- set_previous_quarter_start(&date);
+ gnc_gdate_set_prev_quarter_start(&date);
time1 = time64_from_gdate(&date, DayPart::start);
EXPECT_EQ(time1,
gnc_relative_date_to_time64(RelativeDatePeriod::START_PREV_QUARTER));
g_date_set_time_t(&date, time(nullptr));
- set_previous_quarter_end(&date);
+ gnc_gdate_set_prev_quarter_end(&date);
time1 = time64_from_gdate(&date, DayPart::end);
EXPECT_EQ(time1,
gnc_relative_date_to_time64(RelativeDatePeriod::END_PREV_QUARTER));
@@ -1062,7 +1104,19 @@ class GncOptionDateOptionTest : public ::testing::Test
protected:
GncOptionDateOptionTest() :
m_option{GncOptionDateValue{"foo", "bar", "a", "Phony Date Option",
- GncOptionUIType::DATE_BOTH}} {}
+ GncOptionUIType::DATE_BOTH}}
+ {
+ prefsbackend = g_new0(PrefsBackend, 1);
+ prefsbackend->get_bool = get_bool;
+ prefsbackend->set_bool = set_bool;
+ prefsbackend->get_int = get_int;
+ prefsbackend->set_int = set_int;
+ }
+ ~GncOptionDateOptionTest()
+ {
+ g_free(prefsbackend);
+ }
+
GncOption m_option;
};
@@ -1270,7 +1324,7 @@ TEST_F(GncDateOption, test_stream_in_quarter_start)
{
GDate date;
g_date_set_time_t(&date, time(nullptr));
- set_current_quarter_start(&date);
+ gnc_gdate_set_quarter_start(&date);
time64 time1{time64_from_gdate(&date, DayPart::start)};
std::istringstream iss{"relative . start-current-quarter"};
iss >> m_option;
@@ -1281,7 +1335,7 @@ TEST_F(GncDateOption, test_stream_in_quarter_end)
{
GDate date;
g_date_set_time_t(&date, time(nullptr));
- set_current_quarter_end(&date);
+ gnc_gdate_set_quarter_end(&date);
time64 time1{time64_from_gdate(&date, DayPart::end)};
std::istringstream iss{"relative . end-current-quarter"};
iss >> m_option;
@@ -1292,7 +1346,7 @@ TEST_F(GncDateOption, test_stream_in_prev_quarter_start)
{
GDate date;
g_date_set_time_t(&date, time(nullptr));
- set_previous_quarter_start(&date);
+ gnc_gdate_set_prev_quarter_start(&date);
time64 time1{time64_from_gdate(&date, DayPart::start)};
std::istringstream iss{"relative . start-prev-quarter"};
iss >> m_option;
@@ -1303,7 +1357,7 @@ TEST_F(GncDateOption, test_stream_in_prev_quarter_end)
{
GDate date;
g_date_set_time_t(&date, time(nullptr));
- set_previous_quarter_end(&date);
+ gnc_gdate_set_prev_quarter_end(&date);
time64 time1{time64_from_gdate(&date, DayPart::end)};
std::istringstream iss{"relative . end-prev-quarter"};
iss >> m_option;
commit 4e4dcf1758cd12a6684f5fe30fb300613150bd18
Author: Sherlock <119709043+agwekixj at users.noreply.github.com>
Date: Sun Mar 2 21:40:51 2025 -0800
Use the fiscal start day of the month for relative quarter reports
Only the quarter's start month in reports is adjusted when Absolute is selected in the Start Date on the Accounting Period preferences. This branch adjusts both the quarter's start day and month when Absolute is selected or when Relative and Today, Start of this month, or Start of previous month are selected.
diff --git a/libgnucash/engine/gnc-option-date.cpp b/libgnucash/engine/gnc-option-date.cpp
index 7ab556ac34..a370681b3a 100644
--- a/libgnucash/engine/gnc-option-date.cpp
+++ b/libgnucash/engine/gnc-option-date.cpp
@@ -427,6 +427,26 @@ days_in_month(int month, int year)
return gnc_date_get_last_mday(month, year + 1900);
}
+static int
+get_last_day_of_month(struct tm& now)
+{
+ /* Ensure that the month is between 0 and 11*/
+ auto year_delta = (now.tm_mon / 12) + (now.tm_mon < 0 ? -1 : 0);
+ return days_in_month(now.tm_mon - (12 * year_delta), now.tm_year + year_delta);
+}
+
+static void
+set_last_day_in_month(struct tm& now)
+{
+ now.tm_mday = get_last_day_of_month(now);
+}
+
+static bool
+is_last_day_in_month(struct tm& now)
+{
+ return now.tm_mday == get_last_day_of_month(now);
+}
+
/* 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.
@@ -464,20 +484,32 @@ normalize_reldate_tm(struct tm& now)
}
static void
-reldate_set_day_and_time(struct tm& now, RelativeDateType type)
+reldate_set_day_and_time(
+ struct tm& now,
+ RelativeDateType type,
+ bool is_offset_quarter,
+ struct tm& acct_per)
{
if (type == RelativeDateType::START)
{
- gnc_tm_set_day_start(&now);
now.tm_mday = 1;
+ if (is_offset_quarter)
+ {
+ set_last_day_in_month(now);
+ if (!is_last_day_in_month(acct_per) && now.tm_mday > acct_per.tm_mday)
+ now.tm_mday = acct_per.tm_mday;
+ }
+ gnc_tm_set_day_start(&now);
}
else if (type == RelativeDateType::END)
{
- /* Ensure that the month is between 0 and 11*/
- 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;
- now.tm_mday = days_in_month(month, year);
+ set_last_day_in_month(now);
+ if (is_offset_quarter)
+ {
+ if (!is_last_day_in_month(acct_per) && now.tm_mday > acct_per.tm_mday)
+ now.tm_mday = acct_per.tm_mday;
+ --now.tm_mday;
+ }
gnc_tm_set_day_end(&now);
}
// Do nothing for LAST and NEXT.
@@ -497,15 +529,16 @@ gnc_relative_date_to_time64(RelativeDatePeriod period)
if (period == RelativeDatePeriod::TODAY)
return static_cast<time64>(now_t);
auto now{static_cast<tm>(now_t)};
- struct tm acct_per{};
- if (gnc_prefs_get_bool (GNC_PREFS_GROUP_ACCT_SUMMARY,
- GNC_PREF_START_CHOICE_ABS))
- acct_per = static_cast<tm>(GncDateTime(gnc_accounting_period_fiscal_start()));
+ struct tm acct_per =
+ static_cast<tm>(GncDateTime(gnc_accounting_period_fiscal_start()));
+ auto offset = reldate_offset(period);
+ bool is_offset_quarter =
+ offset == RelativeDateOffset::QUARTER && acct_per.tm_mday > 1;
- switch(reldate_offset(period))
+ switch(offset)
{
case RelativeDateOffset::NONE:
-// Report on today so nothing to do
+ // Report on today so nothing to do
break;
case RelativeDateOffset::YEAR:
if (reldate_is_prev(period))
@@ -517,14 +550,26 @@ gnc_relative_date_to_time64(RelativeDatePeriod period)
else if (gnc_relative_date_is_ending(period))
now.tm_mon = 11;
break;
- case RelativeDateOffset::SIX:
+ 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:
- now.tm_mon -= (12 + now.tm_mon - acct_per.tm_mon) % 3;
+ {
+ auto delta = (12 + now.tm_mon - acct_per.tm_mon) % 3;
+ if (is_offset_quarter)
+ {
+ if (delta == 0 && !is_last_day_in_month(now) &&
+ (is_last_day_in_month(acct_per) ||
+ now.tm_mday < acct_per.tm_mday))
+ delta = 3;
+ if (gnc_relative_date_is_ending(period))
+ --delta;
+ }
+ now.tm_mon -= delta;
+ }
[[fallthrough]];
case RelativeDateOffset::THREE:
if (reldate_is_prev(period))
@@ -534,7 +579,7 @@ gnc_relative_date_to_time64(RelativeDatePeriod period)
if (gnc_relative_date_is_ending(period))
now.tm_mon += 2;
break;
- case RelativeDateOffset::MONTH:
+ case RelativeDateOffset::MONTH:
if (reldate_is_prev(period))
--now.tm_mon;
else if (reldate_is_next(period))
@@ -546,7 +591,10 @@ gnc_relative_date_to_time64(RelativeDatePeriod period)
else if (reldate_is_next(period))
now.tm_mday += 7;
}
- reldate_set_day_and_time(now, checked_reldate(period).m_type);
+ reldate_set_day_and_time( now,
+ checked_reldate(period).m_type,
+ is_offset_quarter,
+ acct_per);
normalize_reldate_tm(now);
return static_cast<time64>(GncDateTime(now));
}
diff --git a/libgnucash/engine/test/gtest-gnc-option.cpp b/libgnucash/engine/test/gtest-gnc-option.cpp
index 7894f7c02a..cbda3a93fa 100644
--- a/libgnucash/engine/test/gtest-gnc-option.cpp
+++ b/libgnucash/engine/test/gtest-gnc-option.cpp
@@ -941,6 +941,51 @@ TEST(GncOptionDate, test_not_using_32bit_time_t_in_2038)
EXPECT_FALSE(sizeof (time_t) == 4 && time(nullptr) <= 0) << "Time to upgrade 32bit time_t!";
}
+// The relative current quarter start date is the date provided.
+static void
+set_current_quarter_start(GDate *date)
+{
+}
+
+static GDateDay
+get_last_day_of_month(GDate *date)
+{
+ return gnc_date_get_last_mday(g_date_get_month(date) - G_DATE_JANUARY, g_date_get_year(date));
+}
+
+static void
+set_current_quarter_end(GDate *date)
+{
+ bool is_last_of_month = g_date_is_last_of_month(date);
+ GDateDay day = g_date_get_day(date);
+ GDateDay last_day_of_month;
+
+ g_date_set_day(date, 1);
+ g_date_add_months(date, 3);
+ last_day_of_month = get_last_day_of_month(date);
+ g_date_set_day(date, is_last_of_month || day > last_day_of_month ? last_day_of_month : day);
+ g_date_subtract_days(date, 1);
+}
+
+static void
+set_previous_quarter_start(GDate *date)
+{
+ bool is_last_of_month = g_date_is_last_of_month(date);
+ GDateDay day = g_date_get_day(date);
+ GDateDay last_day_of_month;
+
+ g_date_set_day(date, 1);
+ g_date_subtract_months(date, 3);
+ last_day_of_month = get_last_day_of_month(date);
+ g_date_set_day(date, is_last_of_month || day > last_day_of_month ? last_day_of_month : day);
+}
+
+static void
+set_previous_quarter_end(GDate *date)
+{
+ g_date_subtract_days(date, 1);
+}
+
TEST(GncOptionDate, test_gnc_relative_date_to_time64)
{
GDate date;
@@ -967,25 +1012,25 @@ TEST(GncOptionDate, test_gnc_relative_date_to_time64)
EXPECT_EQ(time1,
gnc_relative_date_to_time64(RelativeDatePeriod::END_PREV_MONTH));
g_date_set_time_t(&date, time(nullptr));
- gnc_gdate_set_quarter_start(&date);
+ set_current_quarter_start(&date);
time1 = time64_from_gdate(&date, DayPart::start);
EXPECT_EQ(time1,
gnc_relative_date_to_time64(RelativeDatePeriod::START_CURRENT_QUARTER));
g_date_set_time_t(&date, time(nullptr));
- gnc_gdate_set_quarter_end(&date);
+ set_current_quarter_end(&date);
time1 = time64_from_gdate(&date, DayPart::end);
EXPECT_EQ(time1,
gnc_relative_date_to_time64(RelativeDatePeriod::END_CURRENT_QUARTER));
g_date_set_time_t(&date, time(nullptr));
- gnc_gdate_set_prev_quarter_start(&date);
+ set_previous_quarter_start(&date);
time1 = time64_from_gdate(&date, DayPart::start);
EXPECT_EQ(time1,
gnc_relative_date_to_time64(RelativeDatePeriod::START_PREV_QUARTER));
g_date_set_time_t(&date, time(nullptr));
- gnc_gdate_set_prev_quarter_end(&date);
+ set_previous_quarter_end(&date);
time1 = time64_from_gdate(&date, DayPart::end);
EXPECT_EQ(time1,
gnc_relative_date_to_time64(RelativeDatePeriod::END_PREV_QUARTER));
@@ -1225,7 +1270,7 @@ TEST_F(GncDateOption, test_stream_in_quarter_start)
{
GDate date;
g_date_set_time_t(&date, time(nullptr));
- gnc_gdate_set_quarter_start(&date);
+ set_current_quarter_start(&date);
time64 time1{time64_from_gdate(&date, DayPart::start)};
std::istringstream iss{"relative . start-current-quarter"};
iss >> m_option;
@@ -1236,7 +1281,7 @@ TEST_F(GncDateOption, test_stream_in_quarter_end)
{
GDate date;
g_date_set_time_t(&date, time(nullptr));
- gnc_gdate_set_quarter_end(&date);
+ set_current_quarter_end(&date);
time64 time1{time64_from_gdate(&date, DayPart::end)};
std::istringstream iss{"relative . end-current-quarter"};
iss >> m_option;
@@ -1247,7 +1292,7 @@ TEST_F(GncDateOption, test_stream_in_prev_quarter_start)
{
GDate date;
g_date_set_time_t(&date, time(nullptr));
- gnc_gdate_set_prev_quarter_start(&date);
+ set_previous_quarter_start(&date);
time64 time1{time64_from_gdate(&date, DayPart::start)};
std::istringstream iss{"relative . start-prev-quarter"};
iss >> m_option;
@@ -1258,7 +1303,7 @@ TEST_F(GncDateOption, test_stream_in_prev_quarter_end)
{
GDate date;
g_date_set_time_t(&date, time(nullptr));
- gnc_gdate_set_prev_quarter_end(&date);
+ set_previous_quarter_end(&date);
time64 time1{time64_from_gdate(&date, DayPart::end)};
std::istringstream iss{"relative . end-prev-quarter"};
iss >> m_option;
Summary of changes:
bindings/guile/gnc-optiondb.i | 4 +
libgnucash/engine/gnc-option-date.cpp | 89 ++++++++---
libgnucash/engine/gnc-option-date.hpp | 6 +-
libgnucash/engine/test/gtest-gnc-option.cpp | 234 +++++++++++++++++++++++++++-
4 files changed, 308 insertions(+), 25 deletions(-)
More information about the gnucash-changes
mailing list