/* Copyright (C) 2005, Chris Shoemaker * * 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 #include #include #include #include #include #include "Recurrence.h" #include "gnc-date.h" #include "qof.h" #include "gnc-engine.h" #include "gnc-date.h" #include "Account.h" #include #include #include #define LOG_MOD "gnc.engine.recurrence" static QofLogModule log_module = LOG_MOD; #undef G_LOG_DOMAIN #define G_LOG_DOMAIN LOG_MOD static GDate invalid_gdate; /* Do not intl. These are used for xml storage. */ static gchar *period_type_strings[NUM_PERIOD_TYPES] = { "once", "day", "week", "month", "end of month", "nth weekday", "last weekday", "year", }; static gchar *weekend_adj_strings[NUM_WEEKEND_ADJS] = { "none", "back", "forward", }; #define VALID_PERIOD_TYPE(pt) ((0 <= (pt)) && ((pt) < NUM_PERIOD_TYPES)) #define VALID_WEEKEND_ADJ(wadj) ((0 <= (wadj)) && ((wadj) < NUM_WEEKEND_ADJS)) PeriodType recurrenceGetPeriodType(const Recurrence *r) { return r ? r->ptype : PERIOD_INVALID; } guint recurrenceGetMultiplier(const Recurrence *r) { return r ? r->mult : 0; } GDate recurrenceGetDate(const Recurrence *r) { return r ? r->start : invalid_gdate; } time64 recurrenceGetTime(const Recurrence *r) { return r ? gdate_to_time64(r->start) : INT64_MAX; } WeekendAdjust recurrenceGetWeekendAdjust(const Recurrence *r) { return r ? r->wadj : WEEKEND_ADJ_INVALID; } void recurrenceSet(Recurrence *r, guint16 mult, PeriodType pt, const GDate *_start, WeekendAdjust wadj) { r->ptype = VALID_PERIOD_TYPE(pt) ? pt : PERIOD_MONTH; r->mult = (pt == PERIOD_ONCE) ? 0 : (mult > 0 ? mult : 1); if (_start && g_date_valid(_start)) { r->start = *_start; } else { gnc_gdate_set_today (&r->start); } /* Some of the unusual period types also specify phase. For those types, we ensure that the start date agrees with that phase. */ switch (r->ptype) { case PERIOD_END_OF_MONTH: g_date_set_day(&r->start, g_date_get_days_in_month (g_date_get_month(&r->start), g_date_get_year(&r->start))); break; case PERIOD_LAST_WEEKDAY: { GDateDay dim; dim = g_date_get_days_in_month(g_date_get_month(&r->start), g_date_get_year(&r->start)); while (dim - g_date_get_day(&r->start) >= 7) g_date_add_days(&r->start, 7); } break; case PERIOD_NTH_WEEKDAY: if ((g_date_get_day(&r->start) - 1) / 7 == 4) /* Fifth week */ r->ptype = PERIOD_LAST_WEEKDAY; break; default: break; } switch (r->ptype) { case PERIOD_MONTH: case PERIOD_END_OF_MONTH: case PERIOD_YEAR: r->wadj = wadj; break; default: r->wadj = WEEKEND_ADJ_NONE; break; } } /* nth_weekday_compare() is a helper function for the PERIOD_{NTH,LAST}_WEEKDAY case. It returns the offset, in days, from 'next' to the nth weekday specified by the 'start' date (and the period type), in the same month as 'next'. A negative offset means earlier than 'next'; a zero offset means 'next' *is* the nth weekday in that month; a positive offset means later than 'next'. */ static gint nth_weekday_compare(const GDate *start, const GDate *next, PeriodType pt) { GDateDay sd, nd; gint matchday, dim, week; nd = g_date_get_day(next); sd = g_date_get_day(start); week = sd / 7 > 3 ? 3 : sd / 7; if (week > 0 && sd % 7 == 0 && sd != 28) --week; /* matchday has a week part, capped at 3 weeks, and a day part, capped at 7 days, so max(matchday) == 3*7 + 7 == 28. */ matchday = 7 * week + //((sd - 1) / 7 == 4 ? 3 : (sd - 1) / 7) + (nd - g_date_get_weekday(next) + g_date_get_weekday(start) + 7) % 7; /* That " + 7" is to avoid negative modulo in case nd < 6. */ dim = g_date_get_days_in_month( g_date_get_month(next), g_date_get_year(next)); if ((dim - matchday) >= 7 && pt == PERIOD_LAST_WEEKDAY) matchday += 7; /* Go to the fifth week, if needed */ if (pt == PERIOD_NTH_WEEKDAY && (matchday % 7 == 0)) matchday += 7; return matchday - nd; /* Offset from 'next' to matchday */ } 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. */ void recurrenceNextInstance(const Recurrence *r, const GDate *ref, GDate *next) { PeriodType pt; const GDate *start; GDate adjusted_start; guint mult; WeekendAdjust wadj; g_return_if_fail(r); g_return_if_fail(ref); g_return_if_fail(g_date_valid(&r->start)); g_return_if_fail(g_date_valid(ref)); start = &r->start; 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(&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. */ switch (pt) { case PERIOD_YEAR: mult *= 12; /* fall through */ case PERIOD_MONTH: case PERIOD_NTH_WEEKDAY: case PERIOD_LAST_WEEKDAY: case PERIOD_END_OF_MONTH: /* Takes care of short months. */ if (r->wadj == WEEKEND_ADJ_BACK && (pt == PERIOD_YEAR || pt == PERIOD_MONTH || pt == PERIOD_END_OF_MONTH) && (g_date_get_weekday(next) == G_DATE_SATURDAY || g_date_get_weekday(next) == G_DATE_SUNDAY)) { /* Allows the following Friday-based calculations to proceed if 'next' is between Friday and the target day. */ g_date_subtract_days(next, g_date_get_weekday(next) == G_DATE_SATURDAY ? 1 : 2); } if (r->wadj == WEEKEND_ADJ_BACK && (pt == PERIOD_YEAR || pt == PERIOD_MONTH || pt == PERIOD_END_OF_MONTH) && g_date_get_weekday(next) == G_DATE_FRIDAY) { GDate tmp_sat; GDate tmp_sun; g_date_set_julian(&tmp_sat, g_date_get_julian(next)); g_date_set_julian(&tmp_sun, g_date_get_julian(next)); g_date_add_days(&tmp_sat, 1); g_date_add_days(&tmp_sun, 2); if (pt == PERIOD_END_OF_MONTH) { if (g_date_is_last_of_month(next) || g_date_is_last_of_month(&tmp_sat) || g_date_is_last_of_month(&tmp_sun)) g_date_add_months(next, mult); else /* one fewer month fwd because of the occurrence in this month */ g_date_add_months(next, mult - 1); } else { if (g_date_get_day(&tmp_sat) == g_date_get_day(start)) { g_date_add_days(next, 1); g_date_add_months(next, mult); } else if (g_date_get_day(&tmp_sun) == g_date_get_day(start)) { g_date_add_days(next, 2); g_date_add_months(next, mult); } else if (g_date_get_day(next) >= g_date_get_day(start)) { g_date_add_months(next, mult); } else if (g_date_is_last_of_month(next)) { g_date_add_months(next, mult); } else if (g_date_is_last_of_month(&tmp_sat)) { g_date_add_days(next, 1); g_date_add_months(next, mult); } else if (g_date_is_last_of_month(&tmp_sun)) { g_date_add_days(next, 2); g_date_add_months(next, mult); } else { /* one fewer month fwd because of the occurrence in this month */ g_date_add_months(next, mult - 1); } } } else if ( g_date_is_last_of_month(next) || ((pt == PERIOD_MONTH || pt == PERIOD_YEAR) && g_date_get_day(next) >= g_date_get_day(start)) || ((pt == PERIOD_NTH_WEEKDAY || pt == PERIOD_LAST_WEEKDAY) && nth_weekday_compare(start, next, pt) <= 0) ) g_date_add_months(next, mult); else /* one fewer month fwd because of the occurrence in this month */ g_date_add_months(next, mult - 1); break; case PERIOD_WEEK: mult *= 7; /* fall through */ case PERIOD_DAY: g_date_add_days(next, mult); break; case PERIOD_ONCE: g_date_clear(next, 1); /* We already caught the case where ref is */ return; /* earlier than start, so this is invalid. */ default: PERR("Invalid period type"); break; } /* Step 2: Back up to align to the base phase. To ensure forward progress, we never subtract as much as we added (x % mult < mult). */ switch (pt) { case PERIOD_YEAR: case PERIOD_MONTH: case PERIOD_NTH_WEEKDAY: case PERIOD_LAST_WEEKDAY: case PERIOD_END_OF_MONTH: { guint dim, n_months; n_months = 12 * (g_date_get_year(next) - g_date_get_year(start)) + (g_date_get_month(next) - g_date_get_month(start)); g_date_subtract_months(next, n_months % mult); /* Ok, now we're in the right month, so we just have to align the day in one of the three possible ways. */ dim = g_date_get_days_in_month(g_date_get_month(next), g_date_get_year(next)); if (pt == PERIOD_LAST_WEEKDAY || pt == PERIOD_NTH_WEEKDAY) { gint wdresult = nth_weekday_compare(start, next, pt); if (wdresult < 0) { wdresult = -wdresult; g_date_subtract_days(next, wdresult); } else g_date_add_days(next, wdresult); } else if (pt == PERIOD_END_OF_MONTH || g_date_get_day(start) >= dim) g_date_set_day(next, dim); /* last day in the month */ else g_date_set_day(next, g_date_get_day(start)); /*same day as start*/ /* Adjust for dates on the weekend. */ adjust_for_weekend(pt,wadj,next); } break; case PERIOD_WEEK: case PERIOD_DAY: g_date_subtract_days(next, g_date_days_between(start, next) % mult); break; default: PERR("Invalid period type"); break; } } /* Zero-based index */ void recurrenceNthInstance(const Recurrence *r, guint n, GDate *date) { GDate ref; guint i; for (*date = ref = r->start, i = 0; i < n; i++) { recurrenceNextInstance(r, &ref, date); ref = *date; } } time64 recurrenceGetPeriodTime(const Recurrence *r, guint period_num, gboolean end) { GDate date; time64 time; recurrenceNthInstance(r, period_num + (end ? 1 : 0), &date); if (end) { g_date_subtract_days(&date, 1); time = gnc_dmy2time64_end (g_date_get_day(&date), g_date_get_month(&date), g_date_get_year (&date)); } else { time = gnc_dmy2time64 (g_date_get_day(&date), g_date_get_month(&date), g_date_get_year (&date)); } return time; } gnc_numeric recurrenceGetAccountPeriodValue(const Recurrence *r, Account *acc, guint n) { time64 t1, t2; // FIXME: maybe zero is not best error return val. g_return_val_if_fail(r && acc, gnc_numeric_zero()); t1 = recurrenceGetPeriodTime(r, n, FALSE); t2 = recurrenceGetPeriodTime(r, n, TRUE); return xaccAccountGetNoclosingBalanceChangeForPeriod (acc, t1, t2, TRUE); } void recurrenceListNextInstance(const GList *rlist, const GDate *ref, GDate *next) { const GList *iter; GDate nextSingle; /* The next date for an individual recurrence */ g_date_clear(next, 1); // empty rlist = no recurrence if (rlist == NULL) { return; } g_return_if_fail(ref && next && g_date_valid(ref)); for (iter = rlist; iter; iter = iter->next) { const Recurrence *r = iter->data; recurrenceNextInstance(r, ref, &nextSingle); if (!g_date_valid(&nextSingle)) continue; if (g_date_valid(next)) g_date_order(next, &nextSingle); /* swaps dates if needed */ else *next = nextSingle; /* first date is always earliest so far */ } } /* Caller owns the returned memory */ gchar * recurrenceToString(const Recurrence *r) { gchar *tmpDate; gchar *tmpPeriod, *ret; g_return_val_if_fail(g_date_valid(&r->start), NULL); tmpDate = g_new0(gchar, MAX_DATE_LENGTH + 1); g_date_strftime(tmpDate, MAX_DATE_LENGTH, "%x", &r->start); if (r->ptype == PERIOD_ONCE) { ret = g_strdup_printf("once on %s", tmpDate); goto done; } tmpPeriod = period_type_strings[r->ptype]; if (r->mult > 1) ret = g_strdup_printf("Every %d %ss beginning %s", r->mult, tmpPeriod, tmpDate); else ret = g_strdup_printf("Every %s beginning %s", tmpPeriod, tmpDate); done: g_free(tmpDate); return ret; } /* caller owns the returned memory */ gchar * recurrenceListToString(const GList *r) { const GList *iter; GString *str; gchar *s; str = g_string_new(""); if (r == NULL) { g_string_append(str, _("None")); } else { for (iter = r; iter; iter = iter->next) { if (iter != r) { /* Translators: " + " is an separator in a list of string-representations of recurrence frequencies */ g_string_append(str, _(" + ")); } s = recurrenceToString((Recurrence *)iter->data); g_string_append(str, s); g_free(s); } } return g_string_free(str, FALSE); } gchar * recurrencePeriodTypeToString(PeriodType pt) { return VALID_PERIOD_TYPE(pt) ? g_strdup(period_type_strings[pt]) : NULL; } PeriodType recurrencePeriodTypeFromString(const gchar *str) { int i; for (i = 0; i < NUM_PERIOD_TYPES; i++) if (g_strcmp0(period_type_strings[i], str) == 0) return i; return -1; } gchar * recurrenceWeekendAdjustToString(WeekendAdjust wadj) { return VALID_WEEKEND_ADJ(wadj) ? g_strdup(weekend_adj_strings[wadj]) : NULL; } WeekendAdjust recurrenceWeekendAdjustFromString(const gchar *str) { int i; for (i = 0; i < NUM_WEEKEND_ADJS; i++) if (g_strcmp0(weekend_adj_strings[i], str) == 0) return i; return -1; } gboolean recurrenceListIsSemiMonthly(GList *recurrences) { if (g_list_length(recurrences) != 2) return FALSE; // should be a "semi-monthly": { Recurrence *first = (Recurrence*)g_list_nth_data(recurrences, 0); Recurrence *second = (Recurrence*)g_list_nth_data(recurrences, 1); PeriodType first_period, second_period; first_period = recurrenceGetPeriodType(first); second_period = recurrenceGetPeriodType(second); if (!((first_period == PERIOD_MONTH || first_period == PERIOD_END_OF_MONTH || first_period == PERIOD_LAST_WEEKDAY) && (second_period == PERIOD_MONTH || second_period == PERIOD_END_OF_MONTH || second_period == PERIOD_LAST_WEEKDAY))) { /*g_error("unknown 2-recurrence composite with period_types first [%d] second [%d]", first_period, second_periodD);*/ return FALSE; } } return TRUE; } gboolean recurrenceListIsWeeklyMultiple(const GList *recurrences) { const GList *r_iter; for (r_iter = recurrences; r_iter != NULL; r_iter = r_iter->next) { Recurrence *r = (Recurrence*)r_iter->data; if (recurrenceGetPeriodType(r) != PERIOD_WEEK) { return FALSE; } } return TRUE; } static void _weekly_list_to_compact_string(GList *rs, GString *buf) { int dow_idx; char dow_present_bits = 0; int multiplier = -1; for (; rs != NULL; rs = rs->next) { Recurrence *r = (Recurrence*)rs->data; GDate date = recurrenceGetDate(r); GDateWeekday dow = g_date_get_weekday(&date); if (dow == G_DATE_BAD_WEEKDAY) { g_critical("bad weekday pretty-printing recurrence"); continue; } dow_present_bits |= (1 << (dow % 7)); // there's not necessarily a single multiplier, but for all intents // and purposes this will be fine. multiplier = recurrenceGetMultiplier(r); } g_string_printf(buf, "%s", _("Weekly")); if (multiplier > 1) { /* Translators: %u is the recurrence multiplier, i.e. this event should occur every %u'th week. */ g_string_append_printf(buf, _(" (x%u)"), multiplier); } g_string_append_printf(buf, ": "); // @@fixme: this is only Sunday-started weeks. :/ for (dow_idx = 0; dow_idx < 7; dow_idx++) { if ((dow_present_bits & (1 << dow_idx)) != 0) { gchar dbuf[10]; gnc_dow_abbrev(dbuf, 10, dow_idx); g_string_append_unichar(buf, g_utf8_get_char(dbuf)); } else { g_string_append_printf(buf, "-"); } } } /* A constant is needed for the array size */ #define abbrev_day_name_bufsize 10 static void _monthly_append_when(Recurrence *r, GString *buf) { GDate date = recurrenceGetDate(r); if (recurrenceGetPeriodType(r) == PERIOD_LAST_WEEKDAY) { gchar day_name_buf[abbrev_day_name_bufsize]; gnc_dow_abbrev(day_name_buf, abbrev_day_name_bufsize, g_date_get_weekday(&date) % 7); /* Translators: %s is an already-localized form of the day of the week. */ g_string_append_printf(buf, _("last %s"), day_name_buf); } else if (recurrenceGetPeriodType(r) == PERIOD_NTH_WEEKDAY) { int week = 0; int day_of_month_index = 0; const char *numerals[] = {N_("1st"), N_("2nd"), N_("3rd"), N_("4th")}; gchar day_name_buf[abbrev_day_name_bufsize]; gnc_dow_abbrev(day_name_buf, abbrev_day_name_bufsize, g_date_get_weekday(&date) % 7); day_of_month_index = g_date_get_day(&date) - 1; week = day_of_month_index / 7 > 3 ? 3 : day_of_month_index / 7; /* Translators: %s is the string 1st, 2nd, 3rd and so on, and %s is an already-localized form of the day of the week. */ g_string_append_printf(buf, _("%s %s"), _(numerals[week]), day_name_buf); } else { /* Translators: %u is the day of month */ g_string_append_printf(buf, "%u", g_date_get_day(&date)); } } gchar* recurrenceListToCompactString(GList *rs) { GString *buf = g_string_sized_new(16); if (g_list_length(rs) == 0) { g_string_printf(buf, "%s", _("None")); goto rtn; } if (g_list_length(rs) > 1) { if (recurrenceListIsWeeklyMultiple(rs)) { _weekly_list_to_compact_string(rs, buf); } else if (recurrenceListIsSemiMonthly(rs)) { Recurrence *first, *second; first = (Recurrence*)g_list_nth_data(rs, 0); second = (Recurrence*)g_list_nth_data(rs, 1); if (recurrenceGetMultiplier(first) != recurrenceGetMultiplier(second)) { g_warning("lying about non-equal semi-monthly recurrence multiplier: %d vs. %d", recurrenceGetMultiplier(first), recurrenceGetMultiplier(second)); } g_string_printf(buf, "%s", _("Semi-monthly")); g_string_append_printf(buf, " "); if (recurrenceGetMultiplier(first) > 1) { /* Translators: %u is the recurrence multiplier number */ g_string_append_printf(buf, _(" (x%u)"), recurrenceGetMultiplier(first)); } g_string_append_printf(buf, ": "); _monthly_append_when(first, buf); g_string_append_printf(buf, ", "); _monthly_append_when(second, buf); } else { /* Translators: %d is the number of Recurrences in the list. */ g_string_printf(buf, _("Unknown, %d-size list."), g_list_length(rs)); } } else { Recurrence *r = (Recurrence*)g_list_nth_data(rs, 0); guint multiplier = recurrenceGetMultiplier(r); switch (recurrenceGetPeriodType(r)) { case PERIOD_ONCE: { g_string_printf(buf, "%s", _("Once")); } break; case PERIOD_DAY: { g_string_printf(buf, "%s", _("Daily")); if (multiplier > 1) { /* Translators: %u is the recurrence multiplier. */ g_string_append_printf(buf, _(" (x%u)"), multiplier); } } break; case PERIOD_WEEK: { _weekly_list_to_compact_string(rs, buf); } break; case PERIOD_MONTH: case PERIOD_END_OF_MONTH: case PERIOD_LAST_WEEKDAY: { g_string_printf(buf, "%s", _("Monthly")); if (multiplier > 1) { /* Translators: %u is the recurrence multiplier. */ g_string_append_printf(buf, _(" (x%u)"), multiplier); } g_string_append_printf(buf, ": "); _monthly_append_when(r, buf); } break; case PERIOD_NTH_WEEKDAY: { //g_warning("nth weekday not handled"); //g_string_printf(buf, "@fixme: nth weekday not handled"); g_string_printf(buf, "%s", _("Monthly")); if (multiplier > 1) { /* Translators: %u is the recurrence multiplier. */ g_string_append_printf(buf, _(" (x%u)"), multiplier); } g_string_append_printf(buf, ": "); _monthly_append_when(r, buf); } break; case PERIOD_YEAR: { g_string_printf(buf, "%s", _("Yearly")); if (multiplier > 1) { /* Translators: %u is the recurrence multiplier. */ g_string_append_printf(buf, _(" (x%u)"), multiplier); } } break; default: g_error("unknown Recurrence period %d", recurrenceGetPeriodType(r)); break; } } rtn: return g_string_free(buf, FALSE); } /** * The ordering, in increasing degrees of frequent-ness: * * day < week < {nth-weekday < month < end-month, last_weekday} < year < once * * all the monthly types are basically together, but are broken down * internally cause they have to be ordered somehow. **/ static int cmp_order_indexes[] = { 6, // PERIOD_ONCE 1, // PERIOD_DAY 2, // PERIOD_WEEK // 3, // "semi-monthly" ... Note that this isn't presently used, just the // // way the code worked out. :( 4, // PERIOD_MONTH 4, // PERIOD_END_OF_MONTH 4, // PERIOD_NTH_WEEKDAY 4, // PERIOD_LAST_WEEKDAY 5, // PERIOD_YEAR }; static int cmp_monthly_order_indexes[] = { -1, // PERIOD_ONCE -1, // PERIOD_DAY -1, // PERIOD_WEEK 2, // PERIOD_MONTH 3, // PERIOD_END_OF_MONTH 1, // PERIOD_NTH_WEEKDAY 4, // PERIOD_LAST_WEEKDAY -1, // PERIOD_YEAR }; int recurrenceCmp(Recurrence *a, Recurrence *b) { PeriodType period_a, period_b; int a_order_index, b_order_index; int a_mult, b_mult; g_return_val_if_fail(a != NULL && b != NULL, 0); g_return_val_if_fail(a != NULL, 1); g_return_val_if_fail(b != NULL, -1); period_a = recurrenceGetPeriodType(a); period_b = recurrenceGetPeriodType(b); a_order_index = cmp_order_indexes[period_a]; b_order_index = cmp_order_indexes[period_b]; if (a_order_index != b_order_index) { return a_order_index - b_order_index; } else if (a_order_index == cmp_order_indexes[PERIOD_MONTH]) { // re-order intra-month options: a_order_index = cmp_monthly_order_indexes[period_a]; b_order_index = cmp_monthly_order_indexes[period_b]; g_assert(a_order_index != -1 && b_order_index != -1); if (a_order_index != b_order_index) return a_order_index - b_order_index; } /* else { the basic periods are equal; compare the multipliers } */ a_mult = recurrenceGetMultiplier(a); b_mult = recurrenceGetMultiplier(b); return a_mult - b_mult; } int recurrenceListCmp(GList *a, GList *b) { Recurrence *most_freq_a, *most_freq_b; g_return_val_if_fail(g_list_length(a) != 0 && g_list_length(b) != 0, 0); g_return_val_if_fail(g_list_length(a) != 0, -1); g_return_val_if_fail(g_list_length(b) != 0, 1); most_freq_a = (Recurrence*)g_list_nth_data(g_list_sort(a, (GCompareFunc)recurrenceCmp), 0); most_freq_b = (Recurrence*)g_list_nth_data(g_list_sort(b, (GCompareFunc)recurrenceCmp), 0); return recurrenceCmp(most_freq_a, most_freq_b); } void recurrenceListFree(GList **recurrences) { g_list_foreach(*recurrences, (GFunc)g_free, NULL); g_list_free(*recurrences); *recurrences = NULL; }