mirror of
https://github.com/Gnucash/gnucash.git
synced 2024-12-02 05:29:20 -06:00
441 lines
17 KiB
C
441 lines
17 KiB
C
/* Copyright (C) 2005, Chris Shoemaker <c.shoemaker@cox.net>
|
|
*
|
|
* 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 <config.h>
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <glib.h>
|
|
|
|
#include "test-stuff.h"
|
|
#include "Recurrence.h"
|
|
#include "gnc-engine.h"
|
|
|
|
static QofBook *book;
|
|
|
|
static gboolean check_valid(GDate *next, GDate *ref, GDate *start,
|
|
guint16 mult, PeriodType pt, WeekendAdjust wadj)
|
|
{
|
|
gboolean valid;
|
|
gint startToNext;
|
|
gboolean ret_val = TRUE;
|
|
|
|
valid = g_date_valid(next);
|
|
if (pt == PERIOD_ONCE && g_date_compare(start, ref) <= 0)
|
|
do_test(!valid, "incorrectly valid");
|
|
else
|
|
do_test(valid, "incorrectly invalid");
|
|
|
|
if (!valid) return valid;
|
|
|
|
do_test(g_date_compare(ref, next) < 0,
|
|
"next date not strictly later than ref date");
|
|
startToNext = g_date_get_julian(next) - g_date_get_julian(start);
|
|
|
|
// Phase test
|
|
switch (pt)
|
|
{
|
|
case PERIOD_YEAR:
|
|
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)
|
|
{
|
|
if(wadj == WEEKEND_ADJ_NONE)
|
|
ret_val &= do_test(g_date_is_last_of_month(next), "end of month phase wrong");
|
|
else
|
|
{
|
|
gboolean result = TRUE;
|
|
if(!g_date_is_last_of_month(next))
|
|
{
|
|
GDate 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:
|
|
case PERIOD_MONTH:
|
|
{
|
|
gint monthdiff;
|
|
GDateDay day_start, day_next;
|
|
|
|
monthdiff = (g_date_get_month(next) - g_date_get_month(start)) +
|
|
12 * (g_date_get_year(next) - g_date_get_year(start));
|
|
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;
|
|
|
|
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;
|
|
|
|
/* 3 cases: either the weeks agree, OR 'next' didn't have
|
|
5 of the weekday that 'start' did, so it's only the
|
|
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 */
|
|
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))) ||
|
|
(sweek == 3 && nweek == 4 && (pt == PERIOD_LAST_WEEKDAY)),
|
|
"week of month phase wrong");
|
|
|
|
}
|
|
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)
|
|
{
|
|
gboolean result = TRUE;
|
|
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.
|
|
ret_val &= do_test(day_start == day_next || g_date_is_last_of_month(next),
|
|
"dom don't match and next is not eom");
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case PERIOD_WEEK:
|
|
mult *= 7;
|
|
// fall through
|
|
case PERIOD_DAY:
|
|
ret_val &= do_test((startToNext % mult) == 0, "week or day period phase wrong");
|
|
break;
|
|
case PERIOD_ONCE:
|
|
ret_val &= do_test(startToNext == 0, "period once not on start date");
|
|
break;
|
|
default:
|
|
ret_val &=do_test(FALSE, "invalid PeriodType");
|
|
break;
|
|
}
|
|
return ret_val;
|
|
}
|
|
|
|
#define NUM_DATES_TO_TEST 300
|
|
#define NUM_DATES_TO_TEST_REF 300
|
|
#define NUM_MULT_TO_TEST 10
|
|
#define JULIAN_START 2003*365 // years have to be < 10000
|
|
|
|
/* Mult of zero is usually not valid, but it gets regularized to 1, so
|
|
the effect is just that we end up testing mult of 1 twice, plus the
|
|
regularization. */
|
|
static void test_all()
|
|
{
|
|
Recurrence r;
|
|
GDate d_start, d_start_reg;
|
|
GDate d_ref, d_next;
|
|
guint16 mult, mult_reg;
|
|
PeriodType pt, pt_reg;
|
|
WeekendAdjust wadj, wadj_reg;
|
|
gint32 j1, j2;
|
|
gint i_ref;
|
|
|
|
for (pt = PERIOD_ONCE; pt < NUM_PERIOD_TYPES; pt++)
|
|
{
|
|
for (wadj = WEEKEND_ADJ_NONE; wadj < NUM_WEEKEND_ADJS; wadj++)
|
|
{
|
|
for (j1 = JULIAN_START; j1 < JULIAN_START + NUM_DATES_TO_TEST; j1++)
|
|
{
|
|
g_date_set_julian(&d_start, j1);
|
|
for (i_ref = 0; i_ref < NUM_DATES_TO_TEST_REF; i_ref++)
|
|
{
|
|
j2 = (guint32) get_random_int_in_range(1, 1 << 19);
|
|
g_date_set_julian(&d_ref, j2);
|
|
|
|
for (mult = 0; mult < NUM_MULT_TO_TEST; mult++)
|
|
{
|
|
recurrenceSet(&r, mult, pt, &d_start, wadj);
|
|
pt_reg = recurrenceGetPeriodType(&r);
|
|
d_start_reg = recurrenceGetDate(&r);
|
|
mult_reg = recurrenceGetMultiplier(&r);
|
|
wadj_reg = recurrenceGetWeekendAdjust(&r);
|
|
|
|
recurrenceNextInstance(&r, &d_ref, &d_next);
|
|
if (!check_valid(&d_next, &d_ref, &d_start_reg,
|
|
mult_reg, pt_reg, wadj_reg))
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static gboolean test_equal(GDate *d1, GDate *d2)
|
|
{
|
|
if (!do_test(g_date_compare(d1, d2) == 0, "dates don't match"))
|
|
{
|
|
gchar s1[21];
|
|
gchar s2[21];
|
|
g_date_strftime(s1, 20, "%x", d1);
|
|
g_date_strftime(s2, 20, "%x", d2);
|
|
|
|
printf("%s != %s\n", s1, s2);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static void test_specific(PeriodType pt, guint16 mult,
|
|
GDateMonth sm, GDateDay sd, GDateYear sy,
|
|
GDateMonth rm, GDateDay rd, GDateYear ry,
|
|
GDateMonth nm, GDateDay nd, GDateYear ny)
|
|
{
|
|
GDate start;
|
|
GDate ref, next, true_next;
|
|
Recurrence r;
|
|
|
|
g_date_set_dmy(&start, sd, sm, sy);
|
|
g_date_set_dmy(&ref, rd, rm, ry);
|
|
g_date_set_dmy(&true_next, nd, nm, ny);
|
|
|
|
|
|
recurrenceSet(&r, mult, pt, &start, WEEKEND_ADJ_NONE);
|
|
recurrenceNextInstance(&r, &ref, &next);
|
|
|
|
check_valid(&next, &ref, &start, mult, pt, WEEKEND_ADJ_NONE);
|
|
if (!test_equal(&next, &true_next))
|
|
{
|
|
gchar s1[21], s2[21], s3[21];
|
|
g_date_strftime(s1, 20, "%x", &start);
|
|
g_date_strftime(s2, 20, "%x", &ref);
|
|
g_date_strftime(s3, 20, "%x", &true_next);
|
|
printf("pt = %d; mult = %d; start = %s; ref = %s; true_next = %s\n",
|
|
pt, mult, s1, s2, s3);
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
static void test_nth(GDateMonth sm, GDateDay sd, GDateYear sy,
|
|
GDateMonth nm, GDateDay nd, GDateYear ny,
|
|
gint diff, PeriodType pt)
|
|
{
|
|
GDate start, next;
|
|
gint d;
|
|
|
|
g_date_set_dmy(&start, sd, sm, sy);
|
|
g_date_set_dmy(&next, nd, nm, ny);
|
|
|
|
d = nth_weekday_compare(&start, &next, pt);
|
|
do_test(d == diff, "nth");
|
|
}
|
|
|
|
static void test_nth_compare()
|
|
{
|
|
test_nth(4, 1, 2005, 4, 2, 2005, -1, PERIOD_NTH_WEEKDAY);
|
|
test_nth(4, 1, 2005, 4, 4, 2005, -3, PERIOD_NTH_WEEKDAY);
|
|
test_nth(4, 1, 2005, 4, 7, 2005, -6, PERIOD_NTH_WEEKDAY);
|
|
test_nth(4, 1, 2005, 4, 8, 2005, -7, PERIOD_NTH_WEEKDAY);
|
|
test_nth(4, 1, 2005, 4, 14, 2005, -13, PERIOD_NTH_WEEKDAY);
|
|
test_nth(4, 1, 2005, 4, 30, 2005, -29, PERIOD_NTH_WEEKDAY);
|
|
test_nth(4, 1, 2005, 5, 1, 2005, 5, PERIOD_NTH_WEEKDAY);
|
|
test_nth(4, 1, 2005, 5, 5, 2005, 1, PERIOD_NTH_WEEKDAY);
|
|
test_nth(4, 1, 2005, 5, 6, 2005, 0, PERIOD_NTH_WEEKDAY);
|
|
test_nth(4, 1, 2005, 5, 7, 2005, -1, PERIOD_NTH_WEEKDAY);
|
|
test_nth(4, 1, 2005, 5, 8, 2005, -2, PERIOD_NTH_WEEKDAY);
|
|
test_nth(4, 1, 2005, 5, 21, 2005, -15, PERIOD_NTH_WEEKDAY);
|
|
|
|
|
|
test_nth(4, 6, 2005, 4, 1, 2005, 5, PERIOD_NTH_WEEKDAY);
|
|
test_nth(4, 6, 2005, 4, 4, 2005, 2, PERIOD_NTH_WEEKDAY);
|
|
test_nth(4, 6, 2005, 4, 6, 2005, 0, PERIOD_NTH_WEEKDAY);
|
|
test_nth(4, 6, 2005, 4, 9, 2005, -3, PERIOD_NTH_WEEKDAY);
|
|
test_nth(4, 6, 2005, 4, 11, 2005, -5, PERIOD_NTH_WEEKDAY);
|
|
test_nth(4, 6, 2005, 4, 13, 2005, -7, PERIOD_NTH_WEEKDAY);
|
|
test_nth(4, 6, 2005, 4, 14, 2005, -8, PERIOD_NTH_WEEKDAY);
|
|
test_nth(4, 6, 2005, 4, 29, 2005, -23, PERIOD_NTH_WEEKDAY);
|
|
|
|
test_nth(4, 12, 2005, 4, 1, 2005, 11, PERIOD_NTH_WEEKDAY);
|
|
test_nth(4, 12, 2005, 4, 4, 2005, 8, PERIOD_NTH_WEEKDAY);
|
|
test_nth(4, 12, 2005, 4, 11, 2005, 1, PERIOD_NTH_WEEKDAY);
|
|
test_nth(4, 12, 2005, 4, 12, 2005, 0, PERIOD_NTH_WEEKDAY);
|
|
test_nth(4, 12, 2005, 4, 13, 2005, -1, PERIOD_NTH_WEEKDAY);
|
|
test_nth(4, 12, 2005, 4, 17, 2005, -5, PERIOD_NTH_WEEKDAY);
|
|
test_nth(4, 12, 2005, 4, 19, 2005, -7, PERIOD_NTH_WEEKDAY);
|
|
test_nth(4, 12, 2005, 4, 28, 2005, -16, PERIOD_NTH_WEEKDAY);
|
|
|
|
test_nth(4, 29, 2005, 4, 30, 2005, -1, PERIOD_LAST_WEEKDAY);
|
|
test_nth(4, 29, 2005, 5, 1, 2005, 26, PERIOD_LAST_WEEKDAY);
|
|
test_nth(4, 29, 2005, 7, 9, 2005, 20, PERIOD_LAST_WEEKDAY);
|
|
test_nth(4, 29, 2005, 7, 31, 2005, -2, PERIOD_LAST_WEEKDAY);
|
|
|
|
test_nth(4, 28, 2005, 4, 30, 2005, -2, PERIOD_LAST_WEEKDAY);
|
|
test_nth(4, 28, 2005, 5, 1, 2005, 25, PERIOD_LAST_WEEKDAY);
|
|
test_nth(4, 28, 2005, 7, 9, 2005, 19, PERIOD_LAST_WEEKDAY);
|
|
test_nth(4, 28, 2005, 7, 31, 2005, -3, PERIOD_LAST_WEEKDAY);
|
|
test_nth(4, 28, 2005, 9, 21, 2005, 8, PERIOD_LAST_WEEKDAY);
|
|
|
|
}
|
|
#endif
|
|
static void test_some()
|
|
{
|
|
test_specific(PERIOD_NTH_WEEKDAY, 1, 4, 1, 2005, 4, 2, 2005, 5, 6, 2005);
|
|
test_specific(PERIOD_NTH_WEEKDAY, 1, 7, 14, 2005, 11, 15, 2005, 12, 8, 2005);
|
|
test_specific(PERIOD_NTH_WEEKDAY, 1, 7, 14, 2005, 11, 5, 2005, 11, 10, 2005);
|
|
test_specific(PERIOD_NTH_WEEKDAY, 1, 4, 1, 2005, 4, 2, 2005, 5, 6, 2005);
|
|
test_specific(PERIOD_NTH_WEEKDAY, 1, 4, 1, 2005, 4, 2, 2005, 5, 6, 2005);
|
|
|
|
test_specific(PERIOD_LAST_WEEKDAY, 1, 4, 29, 2005, 4, 30, 2005, 5, 27, 2005);
|
|
test_specific(PERIOD_LAST_WEEKDAY, 1, 4, 29, 2005, 5, 1, 2005, 5, 27, 2005);
|
|
test_specific(PERIOD_LAST_WEEKDAY, 1, 4, 29, 2005, 7, 9, 2005, 7, 29, 2005);
|
|
test_specific(PERIOD_LAST_WEEKDAY, 1, 4, 29, 2005, 6, 30, 2005, 7, 29, 2005);
|
|
test_specific(PERIOD_LAST_WEEKDAY, 1, 4, 29, 2005, 7, 31, 2005, 8, 26, 2005);
|
|
|
|
test_specific(PERIOD_NTH_WEEKDAY, 2, 4, 27, 2005, 4, 27, 2005, 6, 22, 2005);
|
|
//exit(1);
|
|
//return;
|
|
test_specific(PERIOD_YEAR, 3, 9, 8, 838, 6, 30, 1094, 9, 8, 1096);
|
|
test_specific(PERIOD_YEAR, 2, 9, 8, 838, 6, 30, 1094, 9, 8, 1094);
|
|
test_specific(PERIOD_YEAR, 1, 1, 10, 1000, 1, 5, 1002, 1, 10, 1002);
|
|
//return;
|
|
test_specific(PERIOD_MONTH, 1, 1, 12, 1, 2, 6, 1, 2, 12, 1);
|
|
|
|
test_specific(PERIOD_MONTH, 1, 1, 12, 1, 2, 12, 1, 3, 12, 1);
|
|
test_specific(PERIOD_MONTH, 1, 1, 12, 1, 2, 20, 1, 3, 12, 1);
|
|
test_specific(PERIOD_MONTH, 1, 1, 30, 1, 2, 28, 1, 3, 30, 1);
|
|
test_specific(PERIOD_MONTH, 1, 1, 30, 1, 2, 27, 1, 2, 28, 1);
|
|
test_specific(PERIOD_MONTH, 1, 2, 28, 1, 3, 30, 1, 4, 28, 1);
|
|
|
|
test_specific(PERIOD_END_OF_MONTH, 1, 2, 28, 1, 3, 30, 1, 3, 31, 1);
|
|
test_specific(PERIOD_END_OF_MONTH, 5, 4, 30, 1, 4, 21, 1, 4, 30, 1);
|
|
test_specific(PERIOD_END_OF_MONTH, 5, 2, 28, 1, 5, 21, 1, 7, 31, 1);
|
|
|
|
test_specific(PERIOD_YEAR, 7, 6, 8, 199, 9, 10, 1338, 6, 8, 1340);
|
|
test_specific(PERIOD_YEAR, 2, 9, 8, 838, 6, 30, 1094, 9, 8, 1094);
|
|
|
|
test_specific(PERIOD_YEAR, 1, 5, 2, 13, 1, 11, 101, 5, 2, 101);
|
|
test_specific(PERIOD_DAY, 7, 4, 1, 2000, 4, 8, 2000, 4, 15, 2000);
|
|
}
|
|
|
|
static void test_use()
|
|
{
|
|
Recurrence *r;
|
|
|
|
r = g_new(Recurrence, 1);
|
|
do_test(r != NULL, "allocation");
|
|
g_free(r);
|
|
}
|
|
|
|
static void test_main()
|
|
{
|
|
|
|
book = qof_book_new ();
|
|
|
|
test_use();
|
|
|
|
test_some();
|
|
|
|
test_all();
|
|
|
|
qof_book_destroy (book);
|
|
}
|
|
|
|
|
|
int
|
|
main (int argc, char **argv)
|
|
{
|
|
qof_init();
|
|
|
|
g_log_set_always_fatal( G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING );
|
|
|
|
#if 0
|
|
set_success_print(TRUE);
|
|
#endif
|
|
|
|
test_main();
|
|
|
|
print_test_results();
|
|
return get_rv();
|
|
}
|