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