LCOV - code coverage report
Current view: top level - libgnucash/engine - Recurrence.cpp (source / functions) Coverage Total Hit
Test: gnucash.info Lines: 47.8 % 387 185
Test Date: 2026-11-02 18:19:43 Functions: 66.7 % 27 18
Legend: Lines:     hit not hit

            Line data    Source code
       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              : #include <time.h>
      23              : #include <glib.h>
      24              : #include <glib/gi18n.h>
      25              : #include <string.h>
      26              : #include <stdint.h>
      27              : #include "Recurrence.h"
      28              : #include "gnc-date.h"
      29              : #include "qof.h"
      30              : #include "gnc-engine.h"
      31              : #include "gnc-date.h"
      32              : #include "Account.h"
      33              : #include <stdint.h>
      34              : #include <gnc-glib-utils.h>
      35              : 
      36              : #define LOG_MOD "gnc.engine.recurrence"
      37              : static QofLogModule log_module = LOG_MOD;
      38              : #undef G_LOG_DOMAIN
      39              : #define G_LOG_DOMAIN LOG_MOD
      40              : 
      41              : static GDate invalid_gdate;
      42              : 
      43              : /* Do not intl. These are used for xml storage. */
      44              : static const gchar *period_type_strings[NUM_PERIOD_TYPES] =
      45              : {
      46              :     "once", "day", "week", "month", "end of month",
      47              :     "nth weekday", "last weekday", "year",
      48              : };
      49              : static const gchar *weekend_adj_strings[NUM_WEEKEND_ADJS] =
      50              : {
      51              :     "none", "back", "forward",
      52              : };
      53              : 
      54              : #define VALID_PERIOD_TYPE(pt)    ((0 <= (pt)) && ((pt) < NUM_PERIOD_TYPES))
      55              : #define VALID_WEEKEND_ADJ(wadj)  ((0 <= (wadj)) && ((wadj) < NUM_WEEKEND_ADJS))
      56              : 
      57              : PeriodType
      58     21600006 : recurrenceGetPeriodType(const Recurrence *r)
      59              : {
      60     21600006 :     return r ? r->ptype : PERIOD_INVALID;
      61              : }
      62              : 
      63              : guint
      64     21600002 : recurrenceGetMultiplier(const Recurrence *r)
      65              : {
      66     21600002 :     return r ? r->mult : 0;
      67              : }
      68              : 
      69              : GDate
      70     21600006 : recurrenceGetDate(const Recurrence *r)
      71              : {
      72     21600006 :     return r ? r->start : invalid_gdate;
      73              : }
      74              : 
      75              : time64
      76            0 : recurrenceGetTime(const Recurrence *r)
      77              : {
      78            0 :     return r ? gdate_to_time64(r->start) : INT64_MAX;
      79              : }
      80              : 
      81              : WeekendAdjust
      82     21600006 : recurrenceGetWeekendAdjust(const Recurrence *r)
      83              : {
      84     21600006 :     return r ? r->wadj : WEEKEND_ADJ_INVALID;
      85              : }
      86              : 
      87              : void
      88     21600061 : recurrenceSet(Recurrence *r, guint16 mult, PeriodType pt, const GDate *_start, WeekendAdjust wadj)
      89              : {
      90     21600061 :     r->ptype = VALID_PERIOD_TYPE(pt) ? pt : PERIOD_MONTH;
      91     21600061 :     r->mult = (pt == PERIOD_ONCE) ? 0 : (mult > 0 ? mult : 1);
      92              : 
      93     21600061 :     if (_start && g_date_valid(_start))
      94              :     {
      95     21600061 :         r->start = *_start;
      96              :     }
      97              :     else
      98              :     {
      99            0 :         gnc_gdate_set_today (&r->start);
     100              :     }
     101              : 
     102              :     /* Some of the unusual period types also specify phase.  For those
     103              :        types, we ensure that the start date agrees with that phase. */
     104     21600061 :     switch (r->ptype)
     105              :     {
     106      2700003 :     case PERIOD_END_OF_MONTH:
     107      2700003 :         g_date_set_day(&r->start, g_date_get_days_in_month
     108      2700003 :                        (g_date_get_month(&r->start),
     109      2700003 :                         g_date_get_year(&r->start)));
     110      2700003 :         break;
     111      2700005 :     case PERIOD_LAST_WEEKDAY:
     112              :     {
     113              :         GDateDay dim;
     114      2700005 :         dim = g_date_get_days_in_month(g_date_get_month(&r->start),
     115      2700005 :                                        g_date_get_year(&r->start));
     116      7272005 :         while (dim - g_date_get_day(&r->start) >= 7)
     117      4572000 :             g_date_add_days(&r->start, 7);
     118              :     }
     119      2700005 :     break;
     120      2700006 :     case PERIOD_NTH_WEEKDAY:
     121      2700006 :         if ((g_date_get_day(&r->start) - 1) / 7 == 4) /* Fifth week */
     122       189000 :             r->ptype = PERIOD_LAST_WEEKDAY;
     123      2700006 :         break;
     124     13500047 :     default:
     125     13500047 :         break;
     126              :     }
     127              : 
     128     21600061 :     switch (r->ptype)
     129              :     {
     130      8100033 :     case PERIOD_MONTH:
     131              :     case PERIOD_END_OF_MONTH:
     132              :     case PERIOD_YEAR:
     133      8100033 :         r->wadj = wadj;
     134      8100033 :         break;
     135     13500028 :     default:
     136     13500028 :         r->wadj = WEEKEND_ADJ_NONE;
     137     13500028 :         break;
     138              :     }
     139     21600061 : }
     140              : 
     141              : /* nth_weekday_compare() is a helper function for the
     142              :    PERIOD_{NTH,LAST}_WEEKDAY case.  It returns the offset, in days,
     143              :    from 'next' to the nth weekday specified by the 'start' date (and
     144              :    the period type), in the same month as 'next'.  A negative offset
     145              :    means earlier than 'next'; a zero offset means 'next' *is* the nth
     146              :    weekday in that month; a positive offset means later than
     147              :    'next'. */
     148              : static gint
     149           19 : nth_weekday_compare(const GDate *start, const GDate *next, PeriodType pt)
     150              : {
     151              :     GDateDay sd, nd;
     152              :     gint matchday, dim, week;
     153              : 
     154           19 :     nd = g_date_get_day(next);
     155           19 :     sd = g_date_get_day(start);
     156           19 :     week = sd / 7 > 3 ? 3 : sd / 7;
     157           19 :     if (week > 0 && sd % 7 == 0 && sd != 28)
     158            4 :         --week;
     159              :     /* matchday has a week part, capped at 3 weeks, and a day part,
     160              :        capped at 7 days, so max(matchday) == 3*7 + 7 == 28. */
     161           38 :     matchday = 7 * week + //((sd - 1) / 7 == 4 ? 3 : (sd - 1) / 7) +
     162           19 :                (nd - g_date_get_weekday(next) + g_date_get_weekday(start) + 7) % 7;
     163              :     /* That " + 7" is to avoid negative modulo in case nd < 6. */
     164              : 
     165           19 :     dim = g_date_get_days_in_month(
     166           19 :               g_date_get_month(next), g_date_get_year(next));
     167           19 :     if ((dim - matchday) >= 7 && pt == PERIOD_LAST_WEEKDAY)
     168            3 :         matchday += 7;     /* Go to the fifth week, if needed */
     169           19 :     if (pt == PERIOD_NTH_WEEKDAY && (matchday % 7 == 0))
     170            0 :         matchday += 7;
     171              : 
     172           19 :     return matchday - nd;  /* Offset from 'next' to matchday */
     173              : }
     174              : 
     175              : 
     176     21609960 : static void adjust_for_weekend(PeriodType pt, WeekendAdjust wadj, GDate *date)
     177              : {
     178     21609960 :     if (pt == PERIOD_YEAR || pt == PERIOD_MONTH || pt == PERIOD_END_OF_MONTH)
     179              :     {
     180      8109863 :         if (g_date_get_weekday(date) == G_DATE_SATURDAY || g_date_get_weekday(date) == G_DATE_SUNDAY)
     181              :         {
     182      2084092 :             switch (wadj)
     183              :             {
     184       693000 :                 case WEEKEND_ADJ_BACK:
     185       693000 :                     g_date_subtract_days(date, g_date_get_weekday(date) == G_DATE_SATURDAY ? 1 : 2);
     186       693000 :                     break;
     187       693000 :                 case WEEKEND_ADJ_FORWARD:
     188       693000 :                     g_date_add_days(date, g_date_get_weekday(date) == G_DATE_SATURDAY ? 2 : 1);
     189       693000 :                     break;
     190       698092 :                 case WEEKEND_ADJ_NONE:
     191              :                 default:
     192       698092 :                     break;
     193              :             }
     194              :         }
     195              :     }
     196     21609960 : }
     197              : 
     198              : /* This is the only real algorithm related to recurrences.  It goes:
     199              :    Step 1) Go forward one period from the reference date.
     200              :    Step 2) Back up to align to the phase of the start date.
     201              : */
     202              : void
     203     21605018 : recurrenceNextInstance(const Recurrence *r, const GDate *ref, GDate *next)
     204              : {
     205              :     PeriodType pt;
     206              :     const GDate *start;
     207              :     GDate adjusted_start;
     208              :     guint mult;
     209              :     WeekendAdjust wadj;
     210              : 
     211     43205066 :     g_return_if_fail(r);
     212     21605018 :     g_return_if_fail(ref);
     213     21605018 :     g_return_if_fail(g_date_valid(&r->start));
     214     21605018 :     g_return_if_fail(g_date_valid(ref));
     215              : 
     216     21605018 :     start = &r->start;
     217     21605018 :     mult = r->mult;
     218     21605018 :     pt = r->ptype;
     219     21605018 :     wadj = r->wadj;
     220              :     /* If the ref date comes before the start date then the next
     221              :      occurrence is always the start date, and we're done. */
     222              :     // However, it's possible for the start date to fall on an exception (a weekend), in that case, it needs to be corrected.
     223     21605018 :     adjusted_start = *start;
     224     21605018 :     adjust_for_weekend(pt,wadj,&adjusted_start);
     225     21605018 :     if (g_date_compare(ref, &adjusted_start) < 0)
     226              :     {
     227     21600038 :         g_date_set_julian(next, g_date_get_julian(&adjusted_start));
     228     21600038 :         return;
     229              :     }
     230         4980 :     g_date_set_julian(next, g_date_get_julian(ref)); /* start at refDate */
     231              : 
     232              :     /* Step 1: move FORWARD one period, passing exactly one occurrence. */
     233         4980 :     switch (pt)
     234              :     {
     235            6 :     case PERIOD_YEAR:
     236            6 :         mult *= 12;
     237              :         /* fall through */
     238         4942 :     case PERIOD_MONTH:
     239              :     case PERIOD_NTH_WEEKDAY:
     240              :     case PERIOD_LAST_WEEKDAY:
     241              :     case PERIOD_END_OF_MONTH:
     242              :         /* Takes care of short months. */
     243            0 :         if (r->wadj == WEEKEND_ADJ_BACK &&
     244         4942 :                 (pt == PERIOD_YEAR || pt == PERIOD_MONTH || pt == PERIOD_END_OF_MONTH) &&
     245            0 :                 (g_date_get_weekday(next) == G_DATE_SATURDAY || g_date_get_weekday(next) == G_DATE_SUNDAY))
     246              :         {
     247              :             /* Allows the following Friday-based calculations to proceed if 'next'
     248              :                is between Friday and the target day. */
     249            0 :             g_date_subtract_days(next, g_date_get_weekday(next) == G_DATE_SATURDAY ? 1 : 2);
     250              :         }
     251            0 :         if (r->wadj == WEEKEND_ADJ_BACK &&
     252         4942 :                 (pt == PERIOD_YEAR || pt == PERIOD_MONTH || pt == PERIOD_END_OF_MONTH) &&
     253            0 :                 g_date_get_weekday(next) == G_DATE_FRIDAY)
     254              :         {
     255              :             GDate tmp_sat;
     256              :             GDate tmp_sun;
     257            0 :             g_date_set_julian(&tmp_sat, g_date_get_julian(next));
     258            0 :             g_date_set_julian(&tmp_sun, g_date_get_julian(next));
     259            0 :             g_date_add_days(&tmp_sat, 1);
     260            0 :             g_date_add_days(&tmp_sun, 2);
     261              : 
     262            0 :             if (pt == PERIOD_END_OF_MONTH)
     263              :             {
     264            0 :                 if (g_date_is_last_of_month(next) ||
     265            0 :                         g_date_is_last_of_month(&tmp_sat) ||
     266            0 :                         g_date_is_last_of_month(&tmp_sun))
     267            0 :                     g_date_add_months(next, mult);
     268              :                 else
     269              :                     /* one fewer month fwd because of the occurrence in this month */
     270            0 :                     g_date_add_months(next, mult - 1);
     271              :             }
     272              :             else
     273              :             {
     274            0 :                 if (g_date_get_day(&tmp_sat) == g_date_get_day(start))
     275              :                 {
     276            0 :                     g_date_add_days(next, 1);
     277            0 :                     g_date_add_months(next, mult);
     278              :                 }
     279            0 :                 else if (g_date_get_day(&tmp_sun) == g_date_get_day(start))
     280              :                 {
     281            0 :                     g_date_add_days(next, 2);
     282            0 :                     g_date_add_months(next, mult);
     283              :                 }
     284            0 :                 else if (g_date_get_day(next) >= g_date_get_day(start))
     285              :                 {
     286            0 :                     g_date_add_months(next, mult);
     287              :                 }
     288            0 :                 else if (g_date_is_last_of_month(next))
     289              :                 {
     290            0 :                     g_date_add_months(next, mult);
     291              :                 }
     292            0 :                 else if (g_date_is_last_of_month(&tmp_sat))
     293              :                 {
     294            0 :                     g_date_add_days(next, 1);
     295            0 :                     g_date_add_months(next, mult);
     296              :                 }
     297            0 :                 else if (g_date_is_last_of_month(&tmp_sun))
     298              :                 {
     299            0 :                     g_date_add_days(next, 2);
     300            0 :                     g_date_add_months(next, mult);
     301              :                 }
     302              :                 else
     303              :                 {
     304              :                     /* one fewer month fwd because of the occurrence in this month */
     305            0 :                     g_date_add_months(next, mult - 1);
     306              :                 }
     307              :             }
     308              :         }
     309         9877 :         else if ( g_date_is_last_of_month(next) ||
     310           13 :                   ((pt == PERIOD_MONTH || pt == PERIOD_YEAR) &&
     311         9890 :                    g_date_get_day(next) >= g_date_get_day(start)) ||
     312            7 :                   ((pt == PERIOD_NTH_WEEKDAY || pt == PERIOD_LAST_WEEKDAY) &&
     313            8 :                    nth_weekday_compare(start, next, pt) <= 0) )
     314         4934 :             g_date_add_months(next, mult);
     315              :         else
     316              :             /* one fewer month fwd because of the occurrence in this month */
     317            8 :             g_date_add_months(next, mult - 1);
     318         4942 :         break;
     319            0 :     case PERIOD_WEEK:
     320            0 :         mult *= 7;
     321              :         /* fall through */
     322           28 :     case PERIOD_DAY:
     323           28 :         g_date_add_days(next, mult);
     324           28 :         break;
     325           10 :     case PERIOD_ONCE:
     326           10 :         g_date_clear(next, 1);  /* We already caught the case where ref is */
     327           10 :         return;                 /* earlier than start, so this is invalid. */
     328            0 :     default:
     329            0 :         PERR("Invalid period type");
     330            0 :         break;
     331              :     }
     332              : 
     333              :     /* Step 2: Back up to align to the base phase. To ensure forward
     334              :        progress, we never subtract as much as we added (x % mult < mult). */
     335         4970 :     switch (pt)
     336              :     {
     337         4942 :     case PERIOD_YEAR:
     338              :     case PERIOD_MONTH:
     339              :     case PERIOD_NTH_WEEKDAY:
     340              :     case PERIOD_LAST_WEEKDAY:
     341              :     case PERIOD_END_OF_MONTH:
     342              :     {
     343              :         guint dim, n_months;
     344              : 
     345         4942 :         n_months = 12 * (g_date_get_year(next) - g_date_get_year(start)) +
     346         4942 :                    (g_date_get_month(next) - g_date_get_month(start));
     347         4942 :         g_date_subtract_months(next, n_months % mult);
     348              : 
     349              :         /* Ok, now we're in the right month, so we just have to align
     350              :            the day in one of the three possible ways. */
     351         4942 :         dim = g_date_get_days_in_month(g_date_get_month(next),
     352         4942 :                                        g_date_get_year(next));
     353         4942 :         if (pt == PERIOD_LAST_WEEKDAY || pt == PERIOD_NTH_WEEKDAY)
     354              :         {
     355           11 :             gint wdresult = nth_weekday_compare(start, next, pt);
     356           11 :             if (wdresult < 0)
     357              :             {
     358            5 :                 wdresult = -wdresult;
     359            5 :                 g_date_subtract_days(next, wdresult);
     360              :             }
     361              :             else
     362            6 :                 g_date_add_days(next, wdresult);
     363           11 :         }
     364         4931 :         else if (pt == PERIOD_END_OF_MONTH || g_date_get_day(start) >= dim)
     365            3 :             g_date_set_day(next, dim);  /* last day in the month */
     366              :         else
     367         4928 :             g_date_set_day(next, g_date_get_day(start)); /*same day as start*/
     368              : 
     369              :         /* Adjust for dates on the weekend. */
     370         4942 :         adjust_for_weekend(pt,wadj,next);
     371              :     }
     372         4942 :     break;
     373           28 :     case PERIOD_WEEK:
     374              :     case PERIOD_DAY:
     375           28 :         g_date_subtract_days(next, g_date_days_between(start, next) % mult);
     376           28 :         break;
     377            0 :     default:
     378            0 :         PERR("Invalid period type");
     379            0 :         break;
     380              :     }
     381              : }
     382              : 
     383              : /* Zero-based index */
     384              : void
     385         1914 : recurrenceNthInstance(const Recurrence *r, guint n, GDate *date)
     386              : {
     387              :     GDate ref;
     388              :     guint i;
     389              : 
     390         6831 :     for (*date = ref = r->start, i = 0; i < n; i++)
     391              :     {
     392         4917 :         recurrenceNextInstance(r, &ref, date);
     393         4917 :         ref = *date;
     394              :     }
     395         1914 : }
     396              : 
     397              : time64
     398         1914 : recurrenceGetPeriodTime(const Recurrence *r, guint period_num, gboolean end)
     399              : {
     400              :     GDate date;
     401              :     time64 time;
     402         1914 :     recurrenceNthInstance(r, period_num + (end ? 1 : 0), &date);
     403         1914 :     if (end)
     404              :     {
     405          847 :         g_date_subtract_days(&date, 1);
     406          847 :         time = gnc_dmy2time64_end (g_date_get_day(&date),
     407          847 :                                    g_date_get_month(&date),
     408          847 :                                    g_date_get_year (&date));
     409              : 
     410              :     }
     411              :     else
     412              :     {
     413         1067 :         time = gnc_dmy2time64 (g_date_get_day(&date),
     414         1067 :                                g_date_get_month(&date),
     415         1067 :                                g_date_get_year (&date));
     416              :     }
     417         1914 :     return time;
     418              : }
     419              : 
     420              : gnc_numeric
     421          549 : recurrenceGetAccountPeriodValue(const Recurrence *r, Account *acc, guint n)
     422              : {
     423              :     time64 t1, t2;
     424              : 
     425              :     // FIXME: maybe zero is not best error return val.
     426          549 :     g_return_val_if_fail(r && acc, gnc_numeric_zero());
     427          549 :     t1 = recurrenceGetPeriodTime(r, n, FALSE);
     428          549 :     t2 = recurrenceGetPeriodTime(r, n, TRUE);
     429          549 :     return xaccAccountGetNoclosingBalanceChangeInCurrencyForPeriod (acc, t1, t2, TRUE);
     430              : }
     431              : 
     432              : void
     433           74 : recurrenceListNextInstance(const GList *rlist, const GDate *ref, GDate *next)
     434              : {
     435              :     const GList *iter;
     436              :     GDate nextSingle;  /* The next date for an individual recurrence */
     437              : 
     438           74 :     g_date_clear(next, 1);
     439              : 
     440              :     // empty rlist = no recurrence
     441           74 :     if (rlist == NULL)
     442              :     {
     443            0 :         return;
     444              :     }
     445              : 
     446           74 :     g_return_if_fail(ref && next && g_date_valid(ref));
     447              : 
     448          148 :     for (iter = rlist; iter; iter = iter->next)
     449              :     {
     450           74 :         auto r = static_cast<const Recurrence *>(iter->data);
     451              : 
     452           74 :         recurrenceNextInstance(r, ref, &nextSingle);
     453           74 :         if (!g_date_valid(&nextSingle)) continue;
     454              : 
     455           64 :         if (g_date_valid(next))
     456            0 :             g_date_order(next, &nextSingle); /* swaps dates if needed */
     457              :         else
     458           64 :             *next = nextSingle; /* first date is always earliest so far */
     459              :     }
     460              : }
     461              : 
     462              : /* Caller owns the returned memory */
     463              : gchar *
     464           10 : recurrenceToString(const Recurrence *r)
     465              : {
     466              :     gchar *tmpDate, *ret;
     467              :     const gchar *tmpPeriod;
     468              : 
     469           10 :     g_return_val_if_fail(g_date_valid(&r->start), NULL);
     470           10 :     tmpDate = g_new0(gchar, MAX_DATE_LENGTH + 1);
     471           10 :     g_date_strftime(tmpDate, MAX_DATE_LENGTH, "%x", &r->start);
     472              : 
     473           10 :     if (r->ptype == PERIOD_ONCE)
     474              :     {
     475            0 :         ret = g_strdup_printf("once on %s", tmpDate);
     476            0 :         goto done;
     477              :     }
     478              : 
     479           10 :     tmpPeriod = period_type_strings[r->ptype];
     480           10 :     if (r->mult > 1)
     481            0 :         ret = g_strdup_printf("Every %d %ss beginning %s",
     482            0 :                               r->mult, tmpPeriod, tmpDate);
     483              :     else
     484           10 :         ret = g_strdup_printf("Every %s beginning %s",
     485              :                               tmpPeriod, tmpDate);
     486           10 : done:
     487           10 :     g_free(tmpDate);
     488           10 :     return ret;
     489              : }
     490              : 
     491              : /* caller owns the returned memory */
     492              : gchar *
     493            5 : recurrenceListToString(const GList *r)
     494              : {
     495              :     const GList *iter;
     496              :     GString *str;
     497              :     gchar *s;
     498              : 
     499            5 :     str = g_string_new("");
     500            5 :     if (r == NULL)
     501              :     {
     502            0 :         g_string_append(str, _("None"));
     503              :     }
     504              :     else
     505              :     {
     506           10 :         for (iter = r; iter; iter = iter->next)
     507              :         {
     508            5 :             if (iter != r)
     509              :             {
     510              :                 /* Translators: " + " is an separator in a list of string-representations of recurrence frequencies */
     511            0 :                 g_string_append(str, _(" + "));
     512              :             }
     513            5 :             s = recurrenceToString((Recurrence *)iter->data);
     514              :             g_string_append(str, s);
     515            5 :             g_free(s);
     516              :         }
     517              :     }
     518            5 :     return g_string_free(str, FALSE);
     519              : }
     520              : 
     521              : const gchar *
     522            6 : recurrencePeriodTypeToString(PeriodType pt)
     523              : {
     524            6 :     return VALID_PERIOD_TYPE(pt) ? period_type_strings[pt] : NULL;
     525              : }
     526              : 
     527              : PeriodType
     528           10 : recurrencePeriodTypeFromString(const gchar *str)
     529              : {
     530              :     int i;
     531              : 
     532           40 :     for (i = 0; i < NUM_PERIOD_TYPES; i++)
     533           40 :         if (g_strcmp0(period_type_strings[i], str) == 0)
     534           10 :             return static_cast<PeriodType>(i);
     535            0 :     return static_cast<PeriodType>(-1);
     536              : }
     537              : 
     538              : const gchar *
     539            6 : recurrenceWeekendAdjustToString(WeekendAdjust wadj)
     540              : {
     541            6 :     return VALID_WEEKEND_ADJ(wadj) ? weekend_adj_strings[wadj] : NULL;
     542              : }
     543              : 
     544              : WeekendAdjust
     545            6 : recurrenceWeekendAdjustFromString(const gchar *str)
     546              : {
     547              :     int i;
     548              : 
     549           10 :     for (i = 0; i < NUM_WEEKEND_ADJS; i++)
     550           10 :         if (g_strcmp0(weekend_adj_strings[i], str) == 0)
     551            6 :             return static_cast<WeekendAdjust>(i);
     552            0 :     return static_cast<WeekendAdjust>(-1);
     553              : }
     554              : 
     555              : gboolean
     556            0 : recurrenceListIsSemiMonthly(GList *recurrences)
     557              : {
     558            0 :     if (gnc_list_length_cmp (recurrences, 2))
     559            0 :         return FALSE;
     560              : 
     561              :     // should be a "semi-monthly":
     562              :     {
     563            0 :         Recurrence *first = (Recurrence*)g_list_nth_data(recurrences, 0);
     564            0 :         Recurrence *second = (Recurrence*)g_list_nth_data(recurrences, 1);
     565              :         PeriodType first_period, second_period;
     566            0 :         first_period = recurrenceGetPeriodType(first);
     567            0 :         second_period = recurrenceGetPeriodType(second);
     568              : 
     569            0 :         if (!((first_period == PERIOD_MONTH
     570            0 :                 || first_period == PERIOD_END_OF_MONTH
     571            0 :                 || first_period == PERIOD_LAST_WEEKDAY)
     572              :                 && (second_period == PERIOD_MONTH
     573            0 :                     || second_period == PERIOD_END_OF_MONTH
     574            0 :                     || second_period == PERIOD_LAST_WEEKDAY)))
     575              :         {
     576              :             /*g_error("unknown 2-recurrence composite with period_types first [%d] second [%d]",
     577              :               first_period, second_periodD);*/
     578            0 :             return FALSE;
     579              :         }
     580              :     }
     581            0 :     return TRUE;
     582              : }
     583              : 
     584              : gboolean
     585            0 : recurrenceListIsWeeklyMultiple(const GList *recurrences)
     586              : {
     587              :     const GList *r_iter;
     588              : 
     589            0 :     for (r_iter = recurrences; r_iter != NULL; r_iter = r_iter->next)
     590              :     {
     591            0 :         Recurrence *r = (Recurrence*)r_iter->data;
     592            0 :         if (recurrenceGetPeriodType(r) != PERIOD_WEEK)
     593              :         {
     594            0 :             return FALSE;
     595              :         }
     596              :     }
     597            0 :     return TRUE;
     598              : }
     599              : 
     600              : static void
     601            0 : _weekly_list_to_compact_string(GList *rs, GString *buf)
     602              : {
     603              :     int dow_idx;
     604            0 :     char dow_present_bits = 0;
     605            0 :     int multiplier = -1;
     606            0 :     for (; rs != NULL; rs = rs->next)
     607              :     {
     608            0 :         Recurrence *r = (Recurrence*)rs->data;
     609            0 :         GDate date = recurrenceGetDate(r);
     610            0 :         GDateWeekday dow = g_date_get_weekday(&date);
     611            0 :         if (dow == G_DATE_BAD_WEEKDAY)
     612              :         {
     613            0 :             g_critical("bad weekday pretty-printing recurrence");
     614            0 :             continue;
     615              :         }
     616            0 :         dow_present_bits |= (1 << (dow % 7));
     617              : 
     618              :         // there's not necessarily a single multiplier, but for all intents
     619              :         // and purposes this will be fine.
     620            0 :         multiplier = recurrenceGetMultiplier(r);
     621              :     }
     622            0 :     g_string_printf(buf, "%s", _("Weekly"));
     623            0 :     if (multiplier > 1)
     624              :     {
     625              :         /* Translators: %u is the recurrence multiplier, i.e. this
     626              :                    event should occur every %u'th week. */
     627            0 :         g_string_append_printf(buf, _(" (x%u)"), multiplier);
     628              :     }
     629            0 :     g_string_append_printf(buf, ": ");
     630              : 
     631              :     // @@fixme: this is only Sunday-started weeks. :/
     632            0 :     for (dow_idx = 0; dow_idx < 7; dow_idx++)
     633              :     {
     634            0 :         if ((dow_present_bits & (1 << dow_idx)) != 0)
     635              :         {
     636              :             gchar dbuf[10];
     637            0 :             gnc_dow_abbrev(dbuf, 10, dow_idx);
     638            0 :             g_string_append_unichar(buf, g_utf8_get_char(dbuf));
     639              :         }
     640              :         else
     641              :         {
     642            0 :             g_string_append_printf(buf, "-");
     643              :         }
     644              :     }
     645            0 : }
     646              : 
     647              : /* A constant is needed for the array size */
     648              : #define abbrev_day_name_bufsize 10
     649              : static void
     650            0 : _monthly_append_when(Recurrence *r, GString *buf)
     651              : {
     652            0 :     GDate date = recurrenceGetDate(r);
     653            0 :     if (recurrenceGetPeriodType(r) == PERIOD_LAST_WEEKDAY)
     654              :     {
     655              :         gchar day_name_buf[abbrev_day_name_bufsize];
     656              : 
     657            0 :         gnc_dow_abbrev(day_name_buf, abbrev_day_name_bufsize, g_date_get_weekday(&date) % 7);
     658              : 
     659              :         /* Translators: %s is an already-localized form of the day of the week. */
     660            0 :         g_string_append_printf(buf, _("last %s"), day_name_buf);
     661              :     }
     662            0 :     else if (recurrenceGetPeriodType(r) == PERIOD_NTH_WEEKDAY)
     663              :     {
     664            0 :         int week = 0;
     665            0 :         int day_of_month_index = 0;
     666            0 :         const char *numerals[] = {N_("1st"), N_("2nd"), N_("3rd"), N_("4th")};
     667              :         gchar day_name_buf[abbrev_day_name_bufsize];
     668              : 
     669            0 :         gnc_dow_abbrev(day_name_buf, abbrev_day_name_bufsize, g_date_get_weekday(&date) % 7);
     670            0 :         day_of_month_index = g_date_get_day(&date) - 1;
     671            0 :         week = day_of_month_index / 7 > 3 ? 3 : day_of_month_index / 7;
     672              :         /* Translators: %s is the string 1st, 2nd, 3rd and so on, and
     673              :            %s is an already-localized form of the day of the week. */
     674            0 :         g_string_append_printf(buf, _("%s %s"), _(numerals[week]), day_name_buf);
     675              :     }
     676              :     else
     677              :     {
     678              :         /* Translators: %u is the day of month */
     679            0 :         g_string_append_printf(buf, "%u", g_date_get_day(&date));
     680              :     }
     681            0 : }
     682              : 
     683              : gchar*
     684            0 : recurrenceListToCompactString(GList *rs)
     685              : {
     686            0 :     GString *buf = g_string_sized_new(16);
     687            0 :     gint rs_len = g_list_length (rs);
     688              : 
     689            0 :     if (rs_len == 0)
     690              :     {
     691            0 :         g_string_printf(buf, "%s", _("None"));
     692            0 :         goto rtn;
     693              :     }
     694              : 
     695            0 :     if (rs_len > 1)
     696              :     {
     697            0 :         if (recurrenceListIsWeeklyMultiple(rs))
     698              :         {
     699            0 :             _weekly_list_to_compact_string(rs, buf);
     700              :         }
     701            0 :         else if (recurrenceListIsSemiMonthly(rs))
     702              :         {
     703              :             Recurrence *first, *second;
     704            0 :             first = (Recurrence*)g_list_nth_data(rs, 0);
     705            0 :             second = (Recurrence*)g_list_nth_data(rs, 1);
     706            0 :             if (recurrenceGetMultiplier(first) != recurrenceGetMultiplier(second))
     707              :             {
     708            0 :                 g_warning("lying about non-equal semi-monthly recurrence multiplier: %d vs. %d",
     709              :                           recurrenceGetMultiplier(first), recurrenceGetMultiplier(second));
     710              :             }
     711              : 
     712            0 :             g_string_printf(buf, "%s", _("Semi-monthly"));
     713            0 :             g_string_append_printf(buf, " ");
     714            0 :             if (recurrenceGetMultiplier(first) > 1)
     715              :             {
     716              :                 /* Translators: %u is the recurrence multiplier number */
     717            0 :                 g_string_append_printf(buf, _(" (x%u)"), recurrenceGetMultiplier(first));
     718              :             }
     719            0 :             g_string_append_printf(buf, ": ");
     720            0 :             _monthly_append_when(first, buf);
     721            0 :             g_string_append_printf(buf, ", ");
     722            0 :             _monthly_append_when(second, buf);
     723              :         }
     724              :         else
     725              :         {
     726              :             /* Translators: %d is the number of Recurrences in the list. */
     727            0 :             g_string_printf(buf, _("Unknown, %d-size list."), rs_len);
     728              :         }
     729              :     }
     730              :     else
     731              :     {
     732            0 :         Recurrence *r = (Recurrence*)g_list_nth_data(rs, 0);
     733            0 :         guint multiplier = recurrenceGetMultiplier(r);
     734              : 
     735            0 :         switch (recurrenceGetPeriodType(r))
     736              :         {
     737            0 :         case PERIOD_ONCE:
     738              :         {
     739            0 :             g_string_printf(buf, "%s", _("Once"));
     740              :         }
     741            0 :         break;
     742            0 :         case PERIOD_DAY:
     743              :         {
     744            0 :             g_string_printf(buf, "%s", _("Daily"));
     745            0 :             if (multiplier > 1)
     746              :             {
     747              :                 /* Translators: %u is the recurrence multiplier. */
     748            0 :                 g_string_append_printf(buf, _(" (x%u)"), multiplier);
     749              :             }
     750              :         }
     751            0 :         break;
     752            0 :         case PERIOD_WEEK:
     753              :         {
     754            0 :             _weekly_list_to_compact_string(rs, buf);
     755              :         }
     756            0 :         break;
     757            0 :         case PERIOD_MONTH:
     758              :         case PERIOD_END_OF_MONTH:
     759              :         case PERIOD_LAST_WEEKDAY:
     760              :         {
     761            0 :             g_string_printf(buf, "%s", _("Monthly"));
     762            0 :             if (multiplier > 1)
     763              :             {
     764              :                 /* Translators: %u is the recurrence multiplier. */
     765            0 :                 g_string_append_printf(buf, _(" (x%u)"), multiplier);
     766              :             }
     767            0 :             g_string_append_printf(buf, ": ");
     768            0 :             _monthly_append_when(r, buf);
     769              :         }
     770            0 :         break;
     771            0 :         case PERIOD_NTH_WEEKDAY:
     772              :         {
     773              :             //g_warning("nth weekday not handled");
     774              :             //g_string_printf(buf, "@fixme: nth weekday not handled");
     775            0 :             g_string_printf(buf, "%s", _("Monthly"));
     776            0 :             if (multiplier > 1)
     777              :             {
     778              :                 /* Translators: %u is the recurrence multiplier. */
     779            0 :                 g_string_append_printf(buf, _(" (x%u)"), multiplier);
     780              :             }
     781            0 :             g_string_append_printf(buf, ": ");
     782            0 :             _monthly_append_when(r, buf);
     783              :         }
     784            0 :         break;
     785            0 :         case PERIOD_YEAR:
     786              :         {
     787            0 :             g_string_printf(buf, "%s", _("Yearly"));
     788            0 :             if (multiplier > 1)
     789              :             {
     790              :                 /* Translators: %u is the recurrence multiplier. */
     791            0 :                 g_string_append_printf(buf, _(" (x%u)"), multiplier);
     792              :             }
     793              :         }
     794            0 :         break;
     795            0 :         default:
     796            0 :             g_error("unknown Recurrence period %d", recurrenceGetPeriodType(r));
     797              :             break;
     798              :         }
     799              :     }
     800              : 
     801            0 : rtn:
     802            0 :     return g_string_free(buf, FALSE);
     803              : }
     804              : 
     805              : /**
     806              :  * The ordering, in increasing degrees of frequent-ness:
     807              :  *
     808              :  *   day < week < {nth-weekday < month < end-month, last_weekday} < year < once
     809              :  *
     810              :  * all the monthly types are basically together, but are broken down
     811              :  * internally cause they have to be ordered somehow.
     812              :  **/
     813              : static int cmp_order_indexes[] =
     814              : {
     815              :     6, // PERIOD_ONCE
     816              :     1, // PERIOD_DAY
     817              :     2, // PERIOD_WEEK
     818              :     // 3, // "semi-monthly" ... Note that this isn't presently used, just the
     819              :     //    // way the code worked out. :(
     820              :     4, // PERIOD_MONTH
     821              :     4, // PERIOD_END_OF_MONTH
     822              :     4, // PERIOD_NTH_WEEKDAY
     823              :     4, // PERIOD_LAST_WEEKDAY
     824              :     5, // PERIOD_YEAR
     825              : };
     826              : 
     827              : static int cmp_monthly_order_indexes[] =
     828              : {
     829              :     -1, // PERIOD_ONCE
     830              :     -1, // PERIOD_DAY
     831              :     -1, // PERIOD_WEEK
     832              :     2, // PERIOD_MONTH
     833              :     3, // PERIOD_END_OF_MONTH
     834              :     1, // PERIOD_NTH_WEEKDAY
     835              :     4, // PERIOD_LAST_WEEKDAY
     836              :     -1, // PERIOD_YEAR
     837              : };
     838              : 
     839              : int
     840            0 : recurrenceCmp(Recurrence *a, Recurrence *b)
     841              : {
     842              :     PeriodType period_a, period_b;
     843              :     int a_order_index, b_order_index;
     844              :     int a_mult, b_mult;
     845              : 
     846            0 :     g_return_val_if_fail(a != NULL && b != NULL, 0);
     847            0 :     g_return_val_if_fail(a != NULL, 1);
     848            0 :     g_return_val_if_fail(b != NULL, -1);
     849              : 
     850            0 :     period_a = recurrenceGetPeriodType(a);
     851            0 :     period_b = recurrenceGetPeriodType(b);
     852              : 
     853            0 :     a_order_index = cmp_order_indexes[period_a];
     854            0 :     b_order_index = cmp_order_indexes[period_b];
     855            0 :     if (a_order_index != b_order_index)
     856              :     {
     857            0 :         return a_order_index - b_order_index;
     858              :     }
     859            0 :     else if (a_order_index == cmp_order_indexes[PERIOD_MONTH])
     860              :     {
     861              :         // re-order intra-month options:
     862            0 :         a_order_index = cmp_monthly_order_indexes[period_a];
     863            0 :         b_order_index = cmp_monthly_order_indexes[period_b];
     864            0 :         g_assert(a_order_index != -1 && b_order_index != -1);
     865            0 :         if (a_order_index != b_order_index)
     866            0 :             return a_order_index - b_order_index;
     867              :     }
     868              :     /* else { the basic periods are equal; compare the multipliers } */
     869              : 
     870            0 :     a_mult = recurrenceGetMultiplier(a);
     871            0 :     b_mult = recurrenceGetMultiplier(b);
     872              : 
     873            0 :     return a_mult - b_mult;
     874              : }
     875              : 
     876              : int
     877            0 : recurrenceListCmp(GList *a, GList *b)
     878              : {
     879              :     Recurrence *most_freq_a, *most_freq_b;
     880              : 
     881            0 :     if (!a)
     882            0 :         return (b ? -1 : 0);
     883            0 :     else if (!b)
     884            0 :         return 1;
     885              : 
     886            0 :     most_freq_a = (Recurrence*)g_list_nth_data(g_list_sort(a, (GCompareFunc)recurrenceCmp), 0);
     887            0 :     most_freq_b = (Recurrence*)g_list_nth_data(g_list_sort(b, (GCompareFunc)recurrenceCmp), 0);
     888              : 
     889            0 :     return recurrenceCmp(most_freq_a, most_freq_b);
     890              : }
     891              : 
     892              : void
     893            0 : recurrenceListFree(GList **recurrences)
     894              : {
     895            0 :     g_list_free_full(*recurrences, (GDestroyNotify)g_free);
     896            0 :     *recurrences = NULL;
     897            0 : }
        

Generated by: LCOV version 2.0-1