24 #include <glib/gi18n.h>
26 #include "Recurrence.h"
33 #define LOG_MOD "gnc.engine.recurrence"
36 #define G_LOG_DOMAIN LOG_MOD
38 static GDate invalid_gdate;
41 static gchar *period_type_strings[NUM_PERIOD_TYPES] =
43 "once",
"day",
"week",
"month",
"end of month",
44 "nth weekday",
"last weekday",
"year",
46 static gchar *weekend_adj_strings[NUM_WEEKEND_ADJS] =
48 "none",
"back",
"forward",
51 #define VALID_PERIOD_TYPE(pt) ((0 <= (pt)) && ((pt) < NUM_PERIOD_TYPES))
52 #define VALID_WEEKEND_ADJ(wadj) ((0 <= (wadj)) && ((wadj) < NUM_WEEKEND_ADJS))
57 return r ? r->ptype : PERIOD_INVALID;
63 return r ? r->mult : 0;
69 return r ? r->start : invalid_gdate;
73 recurrenceGetWeekendAdjust(
const Recurrence *r)
75 return r ? r->wadj : WEEKEND_ADJ_INVALID;
79 recurrenceSet(
Recurrence *r, guint16 mult, PeriodType pt,
const GDate *_start, WeekendAdjust wadj)
81 r->ptype = VALID_PERIOD_TYPE(pt) ? pt : PERIOD_MONTH;
82 r->mult = (pt == PERIOD_ONCE) ? 0 : (mult > 0 ? mult : 1);
84 if (_start && g_date_valid(_start))
97 case PERIOD_END_OF_MONTH:
98 g_date_set_day(&r->start, g_date_get_days_in_month
99 (g_date_get_month(&r->start),
100 g_date_get_year(&r->start)));
102 case PERIOD_LAST_WEEKDAY:
105 dim = g_date_get_days_in_month(g_date_get_month(&r->start),
106 g_date_get_year(&r->start));
107 while (dim - g_date_get_day(&r->start) >= 7)
108 g_date_add_days(&r->start, 7);
111 case PERIOD_NTH_WEEKDAY:
112 if ((g_date_get_day(&r->start) - 1) / 7 == 4)
113 r->ptype = PERIOD_LAST_WEEKDAY;
122 case PERIOD_END_OF_MONTH:
127 r->wadj = WEEKEND_ADJ_NONE;
140 nth_weekday_compare(
const GDate *start,
const GDate *next, PeriodType pt)
143 gint matchday, dim, week;
145 nd = g_date_get_day(next);
146 sd = g_date_get_day(start);
147 week = sd / 7 > 3 ? 3 : sd / 7;
148 if (week > 0 && sd % 7 == 0 && sd != 28)
152 matchday = 7 * week +
153 (nd - g_date_get_weekday(next) + g_date_get_weekday(start) + 7) % 7;
156 dim = g_date_get_days_in_month(
157 g_date_get_month(next), g_date_get_year(next));
158 if ((dim - matchday) >= 7 && pt == PERIOD_LAST_WEEKDAY)
160 if (pt == PERIOD_NTH_WEEKDAY && (matchday % 7 == 0))
163 return matchday - nd;
172 recurrenceNextInstance(
const Recurrence *r,
const GDate *ref, GDate *next)
180 g_return_if_fail(ref);
181 g_return_if_fail(g_date_valid(&r->start));
182 g_return_if_fail(g_date_valid(ref));
187 if (g_date_compare(ref, start) < 0)
189 g_date_set_julian(next, g_date_get_julian(start));
192 g_date_set_julian(next, g_date_get_julian(ref));
204 case PERIOD_NTH_WEEKDAY:
205 case PERIOD_LAST_WEEKDAY:
206 case PERIOD_END_OF_MONTH:
208 if (r->wadj == WEEKEND_ADJ_BACK &&
209 (pt == PERIOD_YEAR || pt == PERIOD_MONTH || pt == PERIOD_END_OF_MONTH) &&
210 (g_date_get_weekday(next) == G_DATE_SATURDAY || g_date_get_weekday(next) == G_DATE_SUNDAY))
214 g_date_subtract_days(next, g_date_get_weekday(next) == G_DATE_SATURDAY ? 1 : 2);
216 if (r->wadj == WEEKEND_ADJ_BACK &&
217 (pt == PERIOD_YEAR || pt == PERIOD_MONTH || pt == PERIOD_END_OF_MONTH) &&
218 g_date_get_weekday(next) == G_DATE_FRIDAY)
222 g_date_set_julian(&tmp_sat, g_date_get_julian(next));
223 g_date_set_julian(&tmp_sun, g_date_get_julian(next));
224 g_date_add_days(&tmp_sat, 1);
225 g_date_add_days(&tmp_sun, 2);
227 if (pt == PERIOD_END_OF_MONTH)
229 if (g_date_is_last_of_month(next) ||
230 g_date_is_last_of_month(&tmp_sat) ||
231 g_date_is_last_of_month(&tmp_sun))
232 g_date_add_months(next, mult);
235 g_date_add_months(next, mult - 1);
239 if (g_date_get_day(&tmp_sat) == g_date_get_day(start))
241 g_date_add_days(next, 1);
242 g_date_add_months(next, mult);
244 else if (g_date_get_day(&tmp_sun) == g_date_get_day(start))
246 g_date_add_days(next, 2);
247 g_date_add_months(next, mult);
249 else if (g_date_get_day(next) >= g_date_get_day(start))
251 g_date_add_months(next, mult);
253 else if (g_date_is_last_of_month(next))
255 g_date_add_months(next, mult);
257 else if (g_date_is_last_of_month(&tmp_sat))
259 g_date_add_days(next, 1);
260 g_date_add_months(next, mult);
262 else if (g_date_is_last_of_month(&tmp_sun))
264 g_date_add_days(next, 2);
265 g_date_add_months(next, mult);
270 g_date_add_months(next, mult - 1);
274 else if ( g_date_is_last_of_month(next) ||
275 ((pt == PERIOD_MONTH || pt == PERIOD_YEAR) &&
276 g_date_get_day(next) >= g_date_get_day(start)) ||
277 ((pt == PERIOD_NTH_WEEKDAY || pt == PERIOD_LAST_WEEKDAY) &&
278 nth_weekday_compare(start, next, pt) <= 0) )
279 g_date_add_months(next, mult);
282 g_date_add_months(next, mult - 1);
288 g_date_add_days(next, mult);
291 g_date_clear(next, 1);
294 PERR(
"Invalid period type");
304 case PERIOD_NTH_WEEKDAY:
305 case PERIOD_LAST_WEEKDAY:
306 case PERIOD_END_OF_MONTH:
310 n_months = 12 * (g_date_get_year(next) - g_date_get_year(start)) +
311 (g_date_get_month(next) - g_date_get_month(start));
312 g_date_subtract_months(next, n_months % mult);
316 dim = g_date_get_days_in_month(g_date_get_month(next),
317 g_date_get_year(next));
318 if (pt == PERIOD_LAST_WEEKDAY || pt == PERIOD_NTH_WEEKDAY)
320 gint wdresult = nth_weekday_compare(start, next, pt);
323 wdresult = -wdresult;
324 g_date_subtract_days(next, wdresult);
327 g_date_add_days(next, wdresult);
329 else if (pt == PERIOD_END_OF_MONTH || g_date_get_day(start) >= dim)
330 g_date_set_day(next, dim);
332 g_date_set_day(next, g_date_get_day(start));
335 if (pt == PERIOD_YEAR || pt == PERIOD_MONTH || pt == PERIOD_END_OF_MONTH)
337 if (g_date_get_weekday(next) == G_DATE_SATURDAY || g_date_get_weekday(next) == G_DATE_SUNDAY)
341 case WEEKEND_ADJ_BACK:
342 g_date_subtract_days(next, g_date_get_weekday(next) == G_DATE_SATURDAY ? 1 : 2);
344 case WEEKEND_ADJ_FORWARD:
345 g_date_add_days(next, g_date_get_weekday(next) == G_DATE_SATURDAY ? 2 : 1);
347 case WEEKEND_ADJ_NONE:
358 g_date_subtract_days(next, g_date_days_between(start, next) % mult);
361 PERR(
"Invalid period type");
368 recurrenceNthInstance(
const Recurrence *r, guint n, GDate *date)
373 for (*date = ref = r->start, i = 0; i < n; i++)
375 recurrenceNextInstance(r, &ref, date);
381 recurrenceGetPeriodTime(
const Recurrence *r, guint period_num, gboolean end)
384 recurrenceNthInstance(r, period_num + (end ? 1 : 0), &date);
387 g_date_subtract_days(&date, 1);
402 g_return_val_if_fail(r && acc, gnc_numeric_zero());
403 t1 = recurrenceGetPeriodTime(r, n, FALSE);
404 t2 = recurrenceGetPeriodTime(r, n, TRUE);
405 return xaccAccountGetBalanceChangeForPeriod (acc, t1, t2, TRUE);
409 recurrenceListNextInstance(
const GList *rlist,
const GDate *ref, GDate *next)
414 g_date_clear(next, 1);
422 g_return_if_fail(ref && next && g_date_valid(ref));
424 for (iter = rlist; iter; iter = iter->next)
428 recurrenceNextInstance(r, ref, &nextSingle);
429 if (!g_date_valid(&nextSingle))
continue;
431 if (g_date_valid(next))
432 g_date_order(next, &nextSingle);
443 gchar *tmpPeriod, *ret;
445 g_return_val_if_fail(g_date_valid(&r->start), NULL);
449 if (r->ptype == PERIOD_ONCE)
451 ret = g_strdup_printf(
"once on %s", tmpDate);
455 tmpPeriod = period_type_strings[r->ptype];
457 ret = g_strdup_printf(
"Every %d %ss beginning %s",
458 r->mult, tmpPeriod, tmpDate);
460 ret = g_strdup_printf(
"Every %s beginning %s",
469 recurrenceListToString(
const GList *r)
475 str = g_string_new(
"");
478 g_string_append(str, _(
"None"));
482 for (iter = r; iter; iter = iter->next)
487 g_string_append(str, _(
" + "));
489 s = recurrenceToString((
Recurrence *)iter->data);
490 g_string_append(str, s);
494 return g_string_free(str, FALSE);
498 recurrencePeriodTypeToString(PeriodType pt)
500 return VALID_PERIOD_TYPE(pt) ? g_strdup(period_type_strings[pt]) : NULL;
504 recurrencePeriodTypeFromString(
const gchar *str)
508 for (i = 0; i < NUM_PERIOD_TYPES; i++)
509 if (g_strcmp0(period_type_strings[i], str) == 0)
515 recurrenceWeekendAdjustToString(WeekendAdjust wadj)
517 return VALID_WEEKEND_ADJ(wadj) ? g_strdup(weekend_adj_strings[wadj]) : NULL;
521 recurrenceWeekendAdjustFromString(
const gchar *str)
525 for (i = 0; i < NUM_WEEKEND_ADJS; i++)
526 if (g_strcmp0(weekend_adj_strings[i], str) == 0)
532 recurrenceListIsSemiMonthly(GList *recurrences)
534 if (g_list_length(recurrences) != 2)
541 PeriodType first_period, second_period;
542 first_period = recurrenceGetPeriodType(first);
543 second_period = recurrenceGetPeriodType(second);
545 if (!((first_period == PERIOD_MONTH
546 || first_period == PERIOD_END_OF_MONTH
547 || first_period == PERIOD_LAST_WEEKDAY)
548 && (second_period == PERIOD_MONTH
549 || second_period == PERIOD_END_OF_MONTH
550 || second_period == PERIOD_LAST_WEEKDAY)))
561 recurrenceListIsWeeklyMultiple(
const GList *recurrences)
565 for (r_iter = recurrences; r_iter != NULL; r_iter = r_iter->next)
568 if (recurrenceGetPeriodType(r) != PERIOD_WEEK)
577 _weekly_list_to_compact_string(GList *rs, GString *buf)
580 char dow_present_bits = 0;
582 for (; rs != NULL; rs = rs->next)
585 GDate date = recurrenceGetDate(r);
586 GDateWeekday dow = g_date_get_weekday(&date);
587 if (dow == G_DATE_BAD_WEEKDAY)
589 g_critical(
"bad weekday pretty-printing recurrence");
592 dow_present_bits |= (1 << (dow % 7));
596 multiplier = recurrenceGetMultiplier(r);
598 g_string_printf(buf,
"%s", _(
"Weekly"));
603 g_string_append_printf(buf, _(
" (x%u)"), multiplier);
605 g_string_append_printf(buf,
": ");
608 for (dow_idx = 0; dow_idx < 7; dow_idx++)
610 if ((dow_present_bits & (1 << dow_idx)) != 0)
614 g_string_append_unichar(buf, g_utf8_get_char(dbuf));
618 g_string_append_printf(buf,
"-");
624 #define abbrev_day_name_bufsize 10
626 _monthly_append_when(
Recurrence *r, GString *buf)
628 GDate date = recurrenceGetDate(r);
629 if (recurrenceGetPeriodType(r) == PERIOD_LAST_WEEKDAY)
631 gchar day_name_buf[abbrev_day_name_bufsize];
633 gnc_dow_abbrev(day_name_buf, abbrev_day_name_bufsize, g_date_get_weekday(&date) % 7);
636 g_string_append_printf(buf, _(
"last %s"), day_name_buf);
638 else if (recurrenceGetPeriodType(r) == PERIOD_NTH_WEEKDAY)
641 int day_of_month_index = 0;
642 const char *numerals[] = {N_(
"1st"), N_(
"2nd"), N_(
"3rd"), N_(
"4th")};
643 gchar day_name_buf[abbrev_day_name_bufsize];
645 gnc_dow_abbrev(day_name_buf, abbrev_day_name_bufsize, g_date_get_weekday(&date) % 7);
646 day_of_month_index = g_date_get_day(&date) - 1;
647 week = day_of_month_index / 7 > 3 ? 3 : day_of_month_index / 7;
648 if (week > 0 && day_of_month_index % 7 == 0)
652 g_string_append_printf(buf, _(
"%s %s"), _(numerals[week]), day_name_buf);
657 g_string_append_printf(buf,
"%u", g_date_get_day(&date));
662 recurrenceListToCompactString(GList *rs)
664 GString *buf = g_string_sized_new(16);
666 if (g_list_length(rs) == 0)
668 g_string_printf(buf,
"%s", _(
"None"));
672 if (g_list_length(rs) > 1)
674 if (recurrenceListIsWeeklyMultiple(rs))
676 _weekly_list_to_compact_string(rs, buf);
678 else if (recurrenceListIsSemiMonthly(rs))
683 if (recurrenceGetMultiplier(first) != recurrenceGetMultiplier(second))
685 g_warning(
"lying about non-equal semi-monthly recurrence multiplier: %d vs. %d",
686 recurrenceGetMultiplier(first), recurrenceGetMultiplier(second));
689 g_string_printf(buf,
"%s", _(
"Semi-monthly"));
690 g_string_append_printf(buf,
" ");
691 if (recurrenceGetMultiplier(first) > 1)
694 g_string_append_printf(buf, _(
" (x%u)"), recurrenceGetMultiplier(first));
696 g_string_append_printf(buf,
": ");
697 _monthly_append_when(first, buf);
698 g_string_append_printf(buf,
", ");
699 _monthly_append_when(second, buf);
704 g_string_printf(buf, _(
"Unknown, %d-size list."), g_list_length(rs));
710 guint multiplier = recurrenceGetMultiplier(r);
712 switch (recurrenceGetPeriodType(r))
716 g_string_printf(buf,
"%s", _(
"Once"));
721 g_string_printf(buf,
"%s", _(
"Daily"));
725 g_string_append_printf(buf, _(
" (x%u)"), multiplier);
731 _weekly_list_to_compact_string(rs, buf);
735 case PERIOD_END_OF_MONTH:
736 case PERIOD_LAST_WEEKDAY:
738 g_string_printf(buf,
"%s", _(
"Monthly"));
742 g_string_append_printf(buf, _(
" (x%u)"), multiplier);
744 g_string_append_printf(buf,
": ");
745 _monthly_append_when(r, buf);
748 case PERIOD_NTH_WEEKDAY:
752 g_string_printf(buf,
"%s", _(
"Monthly"));
756 g_string_append_printf(buf, _(
" (x%u)"), multiplier);
758 g_string_append_printf(buf,
": ");
759 _monthly_append_when(r, buf);
764 g_string_printf(buf,
"%s", _(
"Yearly"));
768 g_string_append_printf(buf, _(
" (x%u)"), multiplier);
773 g_error(
"unknown Recurrence period %d", recurrenceGetPeriodType(r));
779 return g_string_free(buf, FALSE);
790 static int cmp_order_indexes[] =
804 static int cmp_monthly_order_indexes[] =
819 PeriodType period_a, period_b;
820 int a_order_index, b_order_index;
823 g_return_val_if_fail(a != NULL && b != NULL, 0);
824 g_return_val_if_fail(a != NULL, 1);
825 g_return_val_if_fail(b != NULL, -1);
827 period_a = recurrenceGetPeriodType(a);
828 period_b = recurrenceGetPeriodType(b);
830 a_order_index = cmp_order_indexes[period_a];
831 b_order_index = cmp_order_indexes[period_b];
832 if (a_order_index != b_order_index)
834 return a_order_index - b_order_index;
836 else if (a_order_index == cmp_order_indexes[PERIOD_MONTH])
839 a_order_index = cmp_monthly_order_indexes[period_a];
840 b_order_index = cmp_monthly_order_indexes[period_b];
841 g_assert(a_order_index != -1 && b_order_index != -1);
842 if (a_order_index != b_order_index)
843 return a_order_index - b_order_index;
847 a_mult = recurrenceGetMultiplier(a);
848 b_mult = recurrenceGetMultiplier(b);
850 return a_mult - b_mult;
854 recurrenceListCmp(GList *a, GList *b)
858 g_return_val_if_fail(g_list_length(a) != 0 && g_list_length(b) != 0, 0);
859 g_return_val_if_fail(g_list_length(a) != 0, -1);
860 g_return_val_if_fail(g_list_length(b) != 0, 1);
862 most_freq_a = (
Recurrence*)g_list_nth_data(g_list_sort(a, (GCompareFunc)recurrenceCmp), 0);
863 most_freq_b = (
Recurrence*)g_list_nth_data(g_list_sort(b, (GCompareFunc)recurrenceCmp), 0);
865 return recurrenceCmp(most_freq_a, most_freq_b);
869 recurrenceListFree(GList **recurrences)
871 g_list_foreach(*recurrences, (GFunc)g_free, NULL);
872 g_list_free(*recurrences);
void gnc_gdate_set_today(GDate *gd)
Date and Time handling routines.
time64 gnc_time64_get_day_start_gdate(const GDate *date)
void gnc_dow_abbrev(gchar *buf, int buf_len, int dow)
#define PERR(format, args...)
time64 gnc_time64_get_day_end_gdate(const GDate *date)
Account handling public routines.
All type declarations for the whole Gnucash engine.
const gchar * QofLogModule