Merge Jean Laroche's 'fix-recurrence' into maint

This commit is contained in:
John Ralls 2020-03-22 13:14:50 -07:00
commit 2bbf5b2ce0
2 changed files with 127 additions and 45 deletions

View File

@ -174,6 +174,28 @@ nth_weekday_compare(const GDate *start, const GDate *next, PeriodType pt)
}
static void adjust_for_weekend(PeriodType pt, WeekendAdjust wadj, GDate *date)
{
if (pt == PERIOD_YEAR || pt == PERIOD_MONTH || pt == PERIOD_END_OF_MONTH)
{
if (g_date_get_weekday(date) == G_DATE_SATURDAY || g_date_get_weekday(date) == G_DATE_SUNDAY)
{
switch (wadj)
{
case WEEKEND_ADJ_BACK:
g_date_subtract_days(date, g_date_get_weekday(date) == G_DATE_SATURDAY ? 1 : 2);
break;
case WEEKEND_ADJ_FORWARD:
g_date_add_days(date, g_date_get_weekday(date) == G_DATE_SATURDAY ? 2 : 1);
break;
case WEEKEND_ADJ_NONE:
default:
break;
}
}
}
}
/* This is the only real algorithm related to recurrences. It goes:
Step 1) Go forward one period from the reference date.
Step 2) Back up to align to the phase of the start date.
@ -183,6 +205,7 @@ recurrenceNextInstance(const Recurrence *r, const GDate *ref, GDate *next)
{
PeriodType pt;
const GDate *start;
GDate adjusted_start;
guint mult;
WeekendAdjust wadj;
@ -191,20 +214,23 @@ recurrenceNextInstance(const Recurrence *r, const GDate *ref, GDate *next)
g_return_if_fail(g_date_valid(&r->start));
g_return_if_fail(g_date_valid(ref));
/* If the ref date comes before the start date then the next
occurrence is always the start date, and we're done. */
start = &r->start;
if (g_date_compare(ref, start) < 0)
mult = r->mult;
pt = r->ptype;
wadj = r->wadj;
/* If the ref date comes before the start date then the next
occurrence is always the start date, and we're done. */
// However, it's possible for the start date to fall on an exception (a weekend), in that case, it needs to be corrected.
adjusted_start = *start;
adjust_for_weekend(pt,wadj,&adjusted_start);
if (g_date_compare(ref, &adjusted_start) < 0)
{
g_date_set_julian(next, g_date_get_julian(start));
g_date_set_julian(next, g_date_get_julian(&adjusted_start));
return;
}
g_date_set_julian(next, g_date_get_julian(ref)); /* start at refDate */
/* Step 1: move FORWARD one period, passing exactly one occurrence. */
mult = r->mult;
pt = r->ptype;
wadj = r->wadj;
switch (pt)
{
case PERIOD_YEAR:
@ -342,25 +368,7 @@ recurrenceNextInstance(const Recurrence *r, const GDate *ref, GDate *next)
g_date_set_day(next, g_date_get_day(start)); /*same day as start*/
/* Adjust for dates on the weekend. */
if (pt == PERIOD_YEAR || pt == PERIOD_MONTH || pt == PERIOD_END_OF_MONTH)
{
if (g_date_get_weekday(next) == G_DATE_SATURDAY || g_date_get_weekday(next) == G_DATE_SUNDAY)
{
switch (wadj)
{
case WEEKEND_ADJ_BACK:
g_date_subtract_days(next, g_date_get_weekday(next) == G_DATE_SATURDAY ? 1 : 2);
break;
case WEEKEND_ADJ_FORWARD:
g_date_add_days(next, g_date_get_weekday(next) == G_DATE_SATURDAY ? 2 : 1);
break;
case WEEKEND_ADJ_NONE:
default:
break;
}
}
}
adjust_for_weekend(pt,wadj,next);
}
break;
case PERIOD_WEEK:

View File

@ -30,13 +30,13 @@
static QofBook *book;
static void check_valid(GDate *next, GDate *ref, GDate *start,
static gboolean check_valid(GDate *next, GDate *ref, GDate *start,
guint16 mult, PeriodType pt, WeekendAdjust wadj)
{
gboolean valid;
GDate adj_date;
gint startToNext;
/* FIXME: The WeekendAdjust argument is completely ignored for
now. */
gboolean ret_val = TRUE;
valid = g_date_valid(next);
if (pt == PERIOD_ONCE && g_date_compare(start, ref) <= 0)
@ -44,7 +44,7 @@ static void check_valid(GDate *next, GDate *ref, GDate *start,
else
do_test(valid, "incorrectly invalid");
if (!valid) return;
if (!valid) return valid;
do_test(g_date_compare(ref, next) < 0,
"next date not strictly later than ref date");
@ -54,13 +54,41 @@ static void check_valid(GDate *next, GDate *ref, GDate *start,
switch (pt)
{
case PERIOD_YEAR:
do_test((g_date_get_year(next) - g_date_get_year(start)) % mult == 0,
ret_val &= do_test((g_date_get_year(next) - g_date_get_year(start)) % mult == 0,
"year period phase wrong"); // redundant
mult *= 12;
// fall through
case PERIOD_END_OF_MONTH:
if (pt == PERIOD_END_OF_MONTH)
do_test(g_date_is_last_of_month(next), "end of month phase wrong");
{
if(wadj == WEEKEND_ADJ_NONE)
ret_val &= do_test(g_date_is_last_of_month(next), "end of month phase wrong");
else
{
gboolean result;
if(!g_date_is_last_of_month(next))
{
adj_date = *next;
if(wadj == WEEKEND_ADJ_BACK)
{
// If adjusting back, one of the next two days to be end of month
g_date_add_days(&adj_date,1);
result = g_date_is_last_of_month(&adj_date);
g_date_add_days(&adj_date,1);
result |= g_date_is_last_of_month(&adj_date);
}
if(wadj == WEEKEND_ADJ_FORWARD)
{
// If adjusting forward, one of the two previous days has to be end of month
g_date_subtract_days(&adj_date,1);
result = g_date_is_last_of_month(&adj_date);
g_date_subtract_days(&adj_date,1);
result |= g_date_is_last_of_month(&adj_date);
}
ret_val &= do_test(result, "end of month phase wrong");
}
}
}
// fall through
case PERIOD_LAST_WEEKDAY:
case PERIOD_NTH_WEEKDAY:
@ -71,13 +99,14 @@ static void check_valid(GDate *next, GDate *ref, GDate *start,
monthdiff = (g_date_get_month(next) - g_date_get_month(start)) +
12 * (g_date_get_year(next) - g_date_get_year(start));
do_test(monthdiff % mult == 0, "month or year phase wrong");
monthdiff %= mult;
ret_val &= do_test(monthdiff == 0 || (monthdiff == -1 && wadj == WEEKEND_ADJ_BACK) || (monthdiff == 1 && wadj == WEEKEND_ADJ_FORWARD), "month or year phase wrong");
if (pt == PERIOD_NTH_WEEKDAY || pt == PERIOD_LAST_WEEKDAY)
{
guint sweek, nweek;
do_test(g_date_get_weekday(next) == g_date_get_weekday(start),
ret_val &= do_test(g_date_get_weekday(next) == g_date_get_weekday(start),
"weekday phase wrong");
sweek = (g_date_get_day(start) - 1) / 7;
nweek = (g_date_get_day(next) - 1) / 7;
@ -87,7 +116,7 @@ static void check_valid(GDate *next, GDate *ref, GDate *start,
4th, OR 'start' didn't have 5 of the weekday that
'next' does and we want the LAST weekday, so it's the
5th of that weekday */
do_test(sweek == nweek ||
ret_val &= do_test(sweek == nweek ||
(sweek == 4 && nweek == 3 && (g_date_get_day(next) + 7) >
g_date_get_days_in_month(
g_date_get_month(next), g_date_get_year(next))) ||
@ -97,16 +126,61 @@ static void check_valid(GDate *next, GDate *ref, GDate *start,
}
else
{
GDateWeekday week_day;
GDateWeekday week_day_1;
GDateWeekday week_day_2;
day_start = g_date_get_day(start);
day_next = g_date_get_day(next);
if (day_start < 28)
do_test(day_start == day_next, "dom don't match");
else if (pt != PERIOD_END_OF_MONTH)
{
gboolean result;
week_day = g_date_get_weekday (next);
switch (wadj) {
case WEEKEND_ADJ_NONE:
ret_val &= do_test(day_start == day_next, "dom don't match");
break;
case WEEKEND_ADJ_BACK:
// Week_day cannot be a weekend.
result = (week_day != G_DATE_SATURDAY && week_day != G_DATE_SUNDAY);
if(day_start != day_next)
{
// If the dom don't match day must be a Friday
result &= (week_day == G_DATE_FRIDAY);
// Either day_next+1 or day_next+2 matches day_start
g_date_add_days(next,1);
week_day_1 = g_date_get_day(next);
g_date_add_days(next,1);
week_day_2 = g_date_get_day(next);
result &= week_day_1 == day_start || week_day_2 == day_start;
}
ret_val &= do_test(result, "dom don't match");
break;
case WEEKEND_ADJ_FORWARD:
// Week_day cannot be a weekend.
result = (week_day != G_DATE_SATURDAY && week_day != G_DATE_SUNDAY);
if(day_start != day_next)
{
// If the dom don't match day must be a Monday
result &= (week_day == G_DATE_MONDAY);
// Either day_next-1 or day_next-2 matches day_start
g_date_subtract_days(next,1);
week_day_1 = g_date_get_day(next);
g_date_subtract_days(next,1);
week_day_2 = g_date_get_day(next);
result &= week_day_1 == day_start || week_day_2 == day_start;
}
ret_val &= do_test(result, "dom don't match");
break;
default:
break;
}
}
else if (pt != PERIOD_END_OF_MONTH && wadj == WEEKEND_ADJ_NONE)
{
// the end of month case was already checked above. near
// the end of the month, the days should still agree,
// unless they can't because of a short month.
do_test(day_start == day_next || g_date_is_last_of_month(next),
ret_val &= do_test(day_start == day_next || g_date_is_last_of_month(next),
"dom don't match and next is not eom");
}
}
@ -116,16 +190,16 @@ static void check_valid(GDate *next, GDate *ref, GDate *start,
mult *= 7;
// fall through
case PERIOD_DAY:
do_test((startToNext % mult) == 0, "week or day period phase wrong");
ret_val &= do_test((startToNext % mult) == 0, "week or day period phase wrong");
break;
case PERIOD_ONCE:
do_test(startToNext == 0, "period once not on start date");
ret_val &= do_test(startToNext == 0, "period once not on start date");
break;
default:
do_test(FALSE, "invalid PeriodType");
ret_val &=do_test(FALSE, "invalid PeriodType");
break;
}
return ret_val;
}
#define NUM_DATES_TO_TEST 300
@ -168,9 +242,9 @@ static void test_all()
wadj_reg = recurrenceGetWeekendAdjust(&r);
recurrenceNextInstance(&r, &d_ref, &d_next);
check_valid(&d_next, &d_ref, &d_start_reg,
mult_reg, pt_reg, wadj_reg);
if (!check_valid(&d_next, &d_ref, &d_start_reg,
mult_reg, pt_reg, wadj_reg))
return;
}
}
}