1 /* Copyright (C) 2005, Chris Shoemaker <c.shoemaker@cox.net>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, contact:
15  *
16  * Free Software Foundation           Voice:  +1-617-542-5942
17  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652
18  * Boston, MA  02110-1301,  USA       gnu@gnu.org
19  */
20 
21 #include <config.h>
22 
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <glib.h>
26 
27 #include "test-stuff.h"
28 #include "Recurrence.h"
29 #include "gnc-engine.h"
30 
31 static QofBook *book;
32 
check_valid(GDate * next,GDate * ref,GDate * start,guint16 mult,PeriodType pt,WeekendAdjust wadj)33 static gboolean check_valid(GDate *next, GDate *ref, GDate *start,
34                         guint16 mult, PeriodType pt, WeekendAdjust wadj)
35 {
36     gboolean valid;
37     gint startToNext;
38     gboolean ret_val = TRUE;
39 
40     valid = g_date_valid(next);
41     if (pt == PERIOD_ONCE && g_date_compare(start, ref) <= 0)
42         do_test(!valid, "incorrectly valid");
43     else
44         do_test(valid, "incorrectly invalid");
45 
46     if (!valid) return valid;
47 
48     do_test(g_date_compare(ref, next) < 0,
49             "next date not strictly later than ref date");
50     startToNext = g_date_get_julian(next) - g_date_get_julian(start);
51 
52     // Phase test
53     switch (pt)
54     {
55     case PERIOD_YEAR:
56         ret_val &= do_test((g_date_get_year(next) - g_date_get_year(start)) % mult == 0,
57                 "year period phase wrong"); // redundant
58         mult *= 12;
59         // fall through
60     case PERIOD_END_OF_MONTH:
61         if (pt == PERIOD_END_OF_MONTH)
62         {
63             if(wadj == WEEKEND_ADJ_NONE)
64                 ret_val &= do_test(g_date_is_last_of_month(next), "end of month phase wrong");
65             else
66             {
67                 gboolean result = TRUE;
68                 if(!g_date_is_last_of_month(next))
69                 {
70                     GDate adj_date = *next;
71                     if(wadj == WEEKEND_ADJ_BACK)
72                     {
73                         // If adjusting back, one of the next two days to be end of month
74                         g_date_add_days(&adj_date,1);
75                         result = g_date_is_last_of_month(&adj_date);
76                         g_date_add_days(&adj_date,1);
77                         result |= g_date_is_last_of_month(&adj_date);
78                     }
79                     if(wadj == WEEKEND_ADJ_FORWARD)
80                     {
81                         // If adjusting forward, one of the two previous days has to be end of month
82                         g_date_subtract_days(&adj_date,1);
83                         result = g_date_is_last_of_month(&adj_date);
84                         g_date_subtract_days(&adj_date,1);
85                         result |= g_date_is_last_of_month(&adj_date);
86                     }
87                     ret_val &= do_test(result, "end of month phase wrong");
88                 }
89             }
90         }
91         // fall through
92     case PERIOD_LAST_WEEKDAY:
93     case PERIOD_NTH_WEEKDAY:
94     case PERIOD_MONTH:
95     {
96         gint monthdiff;
97         GDateDay day_start, day_next;
98 
99         monthdiff = (g_date_get_month(next) - g_date_get_month(start)) +
100                     12 * (g_date_get_year(next) - g_date_get_year(start));
101         monthdiff %= mult;
102         ret_val &= do_test(monthdiff == 0 || (monthdiff == -1 && wadj == WEEKEND_ADJ_BACK) || (monthdiff == 1 && wadj == WEEKEND_ADJ_FORWARD), "month or year phase wrong");
103 
104         if (pt == PERIOD_NTH_WEEKDAY || pt == PERIOD_LAST_WEEKDAY)
105         {
106             guint sweek, nweek;
107 
108             ret_val &= do_test(g_date_get_weekday(next) == g_date_get_weekday(start),
109                     "weekday phase wrong");
110             sweek = (g_date_get_day(start) - 1) / 7;
111             nweek = (g_date_get_day(next) - 1) / 7;
112 
113             /* 3 cases: either the weeks agree, OR 'next' didn't have
114                5 of the weekday that 'start' did, so it's only the
115                4th, OR 'start' didn't have 5 of the weekday that
116                'next' does and we want the LAST weekday, so it's the
117                5th of that weekday */
118             ret_val &= do_test(sweek == nweek ||
119                     (sweek == 4 && nweek == 3 && (g_date_get_day(next) + 7) >
120                      g_date_get_days_in_month(
121                          g_date_get_month(next), g_date_get_year(next))) ||
122                     (sweek == 3 && nweek == 4 && (pt == PERIOD_LAST_WEEKDAY)),
123                     "week of month phase wrong");
124 
125         }
126         else
127         {
128             GDateWeekday week_day;
129             GDateWeekday week_day_1;
130             GDateWeekday week_day_2;
131             day_start = g_date_get_day(start);
132             day_next = g_date_get_day(next);
133             if (day_start < 28)
134             {
135                 gboolean result = TRUE;
136                 week_day = g_date_get_weekday (next);
137                 switch (wadj) {
138                     case WEEKEND_ADJ_NONE:
139                         ret_val &= do_test(day_start == day_next, "dom don't match");
140                         break;
141                     case WEEKEND_ADJ_BACK:
142                         // Week_day cannot be a weekend.
143                         result = (week_day != G_DATE_SATURDAY && week_day != G_DATE_SUNDAY);
144                         if(day_start != day_next)
145                         {
146                             // If the dom don't match day must be a Friday
147                             result &= (week_day == G_DATE_FRIDAY);
148                             // Either day_next+1 or day_next+2 matches day_start
149                             g_date_add_days(next,1);
150                             week_day_1 = g_date_get_day(next);
151                             g_date_add_days(next,1);
152                             week_day_2 = g_date_get_day(next);
153                             result &= week_day_1 == day_start || week_day_2 == day_start;
154                         }
155                         ret_val &= do_test(result, "dom don't match");
156                         break;
157                     case WEEKEND_ADJ_FORWARD:
158                         // Week_day cannot be a weekend.
159                         result = (week_day != G_DATE_SATURDAY && week_day != G_DATE_SUNDAY);
160                         if(day_start != day_next)
161                         {
162                             // If the dom don't match day must be a Monday
163                             result &= (week_day == G_DATE_MONDAY);
164                             // Either day_next-1 or day_next-2 matches day_start
165                             g_date_subtract_days(next,1);
166                             week_day_1 = g_date_get_day(next);
167                             g_date_subtract_days(next,1);
168                             week_day_2 = g_date_get_day(next);
169                             result &= week_day_1 == day_start || week_day_2 == day_start;
170                         }
171                         ret_val &= do_test(result, "dom don't match");
172                         break;
173                     default:
174                         break;
175                 }
176             }
177             else if (pt != PERIOD_END_OF_MONTH && wadj == WEEKEND_ADJ_NONE)
178             {
179                 // the end of month case was already checked above.  near
180                 // the end of the month, the days should still agree,
181                 // unless they can't because of a short month.
182                 ret_val &= do_test(day_start == day_next || g_date_is_last_of_month(next),
183                         "dom don't match and next is not eom");
184             }
185         }
186     }
187     break;
188     case PERIOD_WEEK:
189         mult *= 7;
190         // fall through
191     case PERIOD_DAY:
192         ret_val &= do_test((startToNext % mult) == 0, "week or day period phase wrong");
193         break;
194     case PERIOD_ONCE:
195         ret_val &= do_test(startToNext == 0, "period once not on start date");
196         break;
197     default:
198         ret_val &=do_test(FALSE, "invalid PeriodType");
199         break;
200     }
201     return ret_val;
202 }
203 
204 #define NUM_DATES_TO_TEST 300
205 #define NUM_DATES_TO_TEST_REF 300
206 #define NUM_MULT_TO_TEST 10
207 #define JULIAN_START 2003*365     // years have to be < 10000
208 
209 /* Mult of zero is usually not valid, but it gets regularized to 1, so
210    the effect is just that we end up testing mult of 1 twice, plus the
211    regularization. */
test_all()212 static void test_all()
213 {
214     Recurrence r;
215     GDate d_start, d_start_reg;
216     GDate d_ref, d_next;
217     guint16 mult, mult_reg;
218     PeriodType pt, pt_reg;
219     WeekendAdjust wadj, wadj_reg;
220     gint32 j1, j2;
221     gint i_ref;
222 
223     for (pt = PERIOD_ONCE; pt < NUM_PERIOD_TYPES; pt++)
224     {
225         for (wadj = WEEKEND_ADJ_NONE; wadj < NUM_WEEKEND_ADJS; wadj++)
226         {
227             for (j1 = JULIAN_START; j1 < JULIAN_START + NUM_DATES_TO_TEST; j1++)
228             {
229                 g_date_set_julian(&d_start, j1);
230                 for (i_ref = 0; i_ref < NUM_DATES_TO_TEST_REF; i_ref++)
231                 {
232                     j2 = (guint32) get_random_int_in_range(1, 1 << 19);
233                     g_date_set_julian(&d_ref, j2);
234 
235                     for (mult = 0; mult < NUM_MULT_TO_TEST; mult++)
236                     {
237                         recurrenceSet(&r, mult, pt, &d_start, wadj);
238                         pt_reg = recurrenceGetPeriodType(&r);
239                         d_start_reg = recurrenceGetDate(&r);
240                         mult_reg = recurrenceGetMultiplier(&r);
241                         wadj_reg = recurrenceGetWeekendAdjust(&r);
242 
243                         recurrenceNextInstance(&r, &d_ref, &d_next);
244                         if (!check_valid(&d_next, &d_ref, &d_start_reg,
245                                     mult_reg, pt_reg, wadj_reg))
246                             return;
247                     }
248                 }
249             }
250         }
251     }
252 }
253 
test_equal(GDate * d1,GDate * d2)254 static gboolean test_equal(GDate *d1, GDate *d2)
255 {
256     if (!do_test(g_date_compare(d1, d2) == 0, "dates don't match"))
257     {
258         gchar s1[21];
259         gchar s2[21];
260         g_date_strftime(s1, 20, "%x", d1);
261         g_date_strftime(s2, 20, "%x", d2);
262 
263         printf("%s != %s\n", s1, s2);
264         return FALSE;
265     }
266     return TRUE;
267 }
268 
269 
test_specific(PeriodType pt,guint16 mult,GDateMonth sm,GDateDay sd,GDateYear sy,GDateMonth rm,GDateDay rd,GDateYear ry,GDateMonth nm,GDateDay nd,GDateYear ny)270 static void test_specific(PeriodType pt, guint16 mult,
271                           GDateMonth sm, GDateDay sd, GDateYear sy,
272                           GDateMonth rm, GDateDay rd, GDateYear ry,
273                           GDateMonth nm, GDateDay nd, GDateYear ny)
274 {
275     GDate start;
276     GDate ref, next, true_next;
277     Recurrence r;
278 
279     g_date_set_dmy(&start, sd, sm, sy);
280     g_date_set_dmy(&ref, rd, rm, ry);
281     g_date_set_dmy(&true_next, nd, nm, ny);
282 
283 
284     recurrenceSet(&r, mult, pt, &start, WEEKEND_ADJ_NONE);
285     recurrenceNextInstance(&r, &ref, &next);
286 
287     check_valid(&next, &ref, &start, mult, pt, WEEKEND_ADJ_NONE);
288     if (!test_equal(&next, &true_next))
289     {
290         gchar s1[21], s2[21], s3[21];
291         g_date_strftime(s1, 20, "%x", &start);
292         g_date_strftime(s2, 20, "%x", &ref);
293         g_date_strftime(s3, 20, "%x", &true_next);
294         printf("pt = %d; mult = %d; start = %s; ref = %s; true_next = %s\n",
295                pt, mult, s1, s2, s3);
296     }
297 }
298 
299 #if 0
300 static void test_nth(GDateMonth sm, GDateDay sd, GDateYear sy,
301                      GDateMonth nm, GDateDay nd, GDateYear ny,
302                      gint diff, PeriodType pt)
303 {
304     GDate start, next;
305     gint d;
306 
307     g_date_set_dmy(&start, sd, sm, sy);
308     g_date_set_dmy(&next, nd, nm, ny);
309 
310     d = nth_weekday_compare(&start, &next, pt);
311     do_test(d == diff, "nth");
312 }
313 
314 static void test_nth_compare()
315 {
316     test_nth(4, 1, 2005,   4, 2, 2005, -1, PERIOD_NTH_WEEKDAY);
317     test_nth(4, 1, 2005,   4, 4, 2005, -3, PERIOD_NTH_WEEKDAY);
318     test_nth(4, 1, 2005,   4, 7, 2005, -6, PERIOD_NTH_WEEKDAY);
319     test_nth(4, 1, 2005,   4, 8, 2005, -7, PERIOD_NTH_WEEKDAY);
320     test_nth(4, 1, 2005,   4, 14, 2005, -13, PERIOD_NTH_WEEKDAY);
321     test_nth(4, 1, 2005,   4, 30, 2005, -29, PERIOD_NTH_WEEKDAY);
322     test_nth(4, 1, 2005,   5, 1, 2005, 5, PERIOD_NTH_WEEKDAY);
323     test_nth(4, 1, 2005,   5, 5, 2005, 1, PERIOD_NTH_WEEKDAY);
324     test_nth(4, 1, 2005,   5, 6, 2005, 0, PERIOD_NTH_WEEKDAY);
325     test_nth(4, 1, 2005,   5, 7, 2005, -1, PERIOD_NTH_WEEKDAY);
326     test_nth(4, 1, 2005,   5, 8, 2005, -2, PERIOD_NTH_WEEKDAY);
327     test_nth(4, 1, 2005,   5, 21, 2005, -15, PERIOD_NTH_WEEKDAY);
328 
329 
330     test_nth(4, 6, 2005,   4, 1, 2005, 5, PERIOD_NTH_WEEKDAY);
331     test_nth(4, 6, 2005,   4, 4, 2005, 2, PERIOD_NTH_WEEKDAY);
332     test_nth(4, 6, 2005,   4, 6, 2005, 0, PERIOD_NTH_WEEKDAY);
333     test_nth(4, 6, 2005,   4, 9, 2005, -3, PERIOD_NTH_WEEKDAY);
334     test_nth(4, 6, 2005,   4, 11, 2005, -5, PERIOD_NTH_WEEKDAY);
335     test_nth(4, 6, 2005,   4, 13, 2005, -7, PERIOD_NTH_WEEKDAY);
336     test_nth(4, 6, 2005,   4, 14, 2005, -8, PERIOD_NTH_WEEKDAY);
337     test_nth(4, 6, 2005,   4, 29, 2005, -23, PERIOD_NTH_WEEKDAY);
338 
339     test_nth(4, 12, 2005,   4, 1, 2005, 11, PERIOD_NTH_WEEKDAY);
340     test_nth(4, 12, 2005,   4, 4, 2005, 8, PERIOD_NTH_WEEKDAY);
341     test_nth(4, 12, 2005,   4, 11, 2005, 1, PERIOD_NTH_WEEKDAY);
342     test_nth(4, 12, 2005,   4, 12, 2005, 0, PERIOD_NTH_WEEKDAY);
343     test_nth(4, 12, 2005,   4, 13, 2005, -1, PERIOD_NTH_WEEKDAY);
344     test_nth(4, 12, 2005,   4, 17, 2005, -5, PERIOD_NTH_WEEKDAY);
345     test_nth(4, 12, 2005,   4, 19, 2005, -7, PERIOD_NTH_WEEKDAY);
346     test_nth(4, 12, 2005,   4, 28, 2005, -16, PERIOD_NTH_WEEKDAY);
347 
348     test_nth(4, 29, 2005,   4, 30, 2005, -1, PERIOD_LAST_WEEKDAY);
349     test_nth(4, 29, 2005,   5, 1, 2005, 26, PERIOD_LAST_WEEKDAY);
350     test_nth(4, 29, 2005,   7, 9, 2005, 20, PERIOD_LAST_WEEKDAY);
351     test_nth(4, 29, 2005,   7, 31, 2005, -2, PERIOD_LAST_WEEKDAY);
352 
353     test_nth(4, 28, 2005,   4, 30, 2005, -2, PERIOD_LAST_WEEKDAY);
354     test_nth(4, 28, 2005,   5, 1, 2005, 25, PERIOD_LAST_WEEKDAY);
355     test_nth(4, 28, 2005,   7, 9, 2005, 19, PERIOD_LAST_WEEKDAY);
356     test_nth(4, 28, 2005,   7, 31, 2005, -3, PERIOD_LAST_WEEKDAY);
357     test_nth(4, 28, 2005,   9, 21, 2005, 8, PERIOD_LAST_WEEKDAY);
358 
359 }
360 #endif
test_some()361 static void test_some()
362 {
363     test_specific(PERIOD_NTH_WEEKDAY, 1, 4, 1, 2005,    4, 2, 2005,  5, 6, 2005);
364     test_specific(PERIOD_NTH_WEEKDAY, 1, 7, 14, 2005,   11, 15, 2005,  12, 8, 2005);
365     test_specific(PERIOD_NTH_WEEKDAY, 1, 7, 14, 2005,   11, 5, 2005,  11, 10, 2005);
366     test_specific(PERIOD_NTH_WEEKDAY, 1, 4, 1, 2005,    4, 2, 2005,  5, 6, 2005);
367     test_specific(PERIOD_NTH_WEEKDAY, 1, 4, 1, 2005,    4, 2, 2005,  5, 6, 2005);
368 
369     test_specific(PERIOD_LAST_WEEKDAY, 1, 4, 29, 2005,    4, 30, 2005,  5, 27, 2005);
370     test_specific(PERIOD_LAST_WEEKDAY, 1, 4, 29, 2005,    5, 1, 2005,  5, 27, 2005);
371     test_specific(PERIOD_LAST_WEEKDAY, 1, 4, 29, 2005,    7, 9, 2005,  7, 29, 2005);
372     test_specific(PERIOD_LAST_WEEKDAY, 1, 4, 29, 2005,    6, 30, 2005,  7, 29, 2005);
373     test_specific(PERIOD_LAST_WEEKDAY, 1, 4, 29, 2005,    7, 31, 2005,  8, 26, 2005);
374 
375     test_specific(PERIOD_NTH_WEEKDAY, 2, 4, 27, 2005,    4, 27, 2005,  6, 22, 2005);
376     //exit(1);
377     //return;
378     test_specific(PERIOD_YEAR,          3,   9, 8, 838,    6, 30, 1094,  9, 8, 1096);
379     test_specific(PERIOD_YEAR,          2,   9, 8, 838,    6, 30, 1094,  9, 8, 1094);
380     test_specific(PERIOD_YEAR,          1,   1, 10, 1000,  1, 5, 1002,  1, 10, 1002);
381     //return;
382     test_specific(PERIOD_MONTH, 1,     1, 12, 1,    2, 6, 1,    2, 12, 1);
383 
384     test_specific(PERIOD_MONTH, 1,     1, 12, 1,    2, 12, 1,   3, 12, 1);
385     test_specific(PERIOD_MONTH, 1,     1, 12, 1,    2, 20, 1,   3, 12, 1);
386     test_specific(PERIOD_MONTH, 1,     1, 30, 1,    2, 28, 1,   3, 30, 1);
387     test_specific(PERIOD_MONTH, 1,     1, 30, 1,    2, 27, 1,   2, 28, 1);
388     test_specific(PERIOD_MONTH, 1,     2, 28, 1,    3, 30, 1,   4, 28, 1);
389 
390     test_specific(PERIOD_END_OF_MONTH, 1,   2, 28, 1,    3, 30, 1,   3, 31, 1);
391     test_specific(PERIOD_END_OF_MONTH, 5,   4, 30, 1,    4, 21, 1,  4, 30, 1);
392     test_specific(PERIOD_END_OF_MONTH, 5,   2, 28, 1,    5, 21, 1,  7, 31, 1);
393 
394     test_specific(PERIOD_YEAR,          7,   6, 8, 199,    9, 10, 1338,  6, 8, 1340);
395     test_specific(PERIOD_YEAR,          2,   9, 8, 838,    6, 30, 1094,  9, 8, 1094);
396 
397     test_specific(PERIOD_YEAR, 1,    5, 2, 13, 1, 11, 101,   5, 2, 101);
398     test_specific(PERIOD_DAY, 7,    4, 1, 2000,    4, 8, 2000,  4, 15, 2000);
399 }
400 
test_use()401 static void test_use()
402 {
403     Recurrence *r;
404 
405     r = g_new(Recurrence, 1);
406     do_test(r != NULL, "allocation");
407     g_free(r);
408 }
409 
test_main()410 static void test_main()
411 {
412 
413     book = qof_book_new ();
414 
415     test_use();
416 
417     test_some();
418 
419     test_all();
420 
421     qof_book_destroy (book);
422 }
423 
424 
425 int
main(int argc,char ** argv)426 main (int argc, char **argv)
427 {
428     qof_init();
429 
430     g_log_set_always_fatal( G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING );
431 
432 #if 0
433     set_success_print(TRUE);
434 #endif
435 
436     test_main();
437 
438     print_test_results();
439     return get_rv();
440 }
441