diff --git a/libgnucash/engine/gnc-option-date.cpp b/libgnucash/engine/gnc-option-date.cpp index 7ab556ac341..3270f6dfc03 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,33 @@ 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; + else if (now.tm_mday >= acct_per.tm_mday) + now.tm_mday = acct_per.tm_mday - 1; + } gnc_tm_set_day_end(&now); } // Do nothing for LAST and NEXT. @@ -497,15 +530,16 @@ gnc_relative_date_to_time64(RelativeDatePeriod period) if (period == RelativeDatePeriod::TODAY) return static_cast(now_t); auto now{static_cast(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(GncDateTime(gnc_accounting_period_fiscal_start())); + struct tm acct_per = + static_cast(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 +551,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 +580,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 +592,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(GncDateTime(now)); }