LCOV - code coverage report
Current view: top level - libgnucash/engine - Recurrence.cpp (source / functions) Coverage Total Hit
Test: gnucash.info Lines: 47.7 % 388 185
Test Date: 2025-02-07 16:25:45 Functions: 66.7 % 27 18
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: - 0 0

             Branch data     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                 :    21635868 : static void adjust_for_weekend(PeriodType pt, WeekendAdjust wadj, GDate *date)
     177                 :             : {
     178                 :    21635868 :     if (pt == PERIOD_YEAR || pt == PERIOD_MONTH || pt == PERIOD_END_OF_MONTH)
     179                 :             :     {
     180                 :     8135771 :         if (g_date_get_weekday(date) == G_DATE_SATURDAY || g_date_get_weekday(date) == G_DATE_SUNDAY)
     181                 :             :         {
     182                 :     2105038 :             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                 :      719038 :                 case WEEKEND_ADJ_NONE:
     191                 :             :                 default:
     192                 :      719038 :                     break;
     193                 :             :             }
     194                 :             :         }
     195                 :             :     }
     196                 :    21635868 : }
     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                 :    21617972 : 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                 :    43218020 :     g_return_if_fail(r);
     212                 :    21617972 :     g_return_if_fail(ref);
     213                 :    21617972 :     g_return_if_fail(g_date_valid(&r->start));
     214                 :    21617972 :     g_return_if_fail(g_date_valid(ref));
     215                 :             : 
     216                 :    21617972 :     start = &r->start;
     217                 :    21617972 :     mult = r->mult;
     218                 :    21617972 :     pt = r->ptype;
     219                 :    21617972 :     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                 :    21617972 :     adjusted_start = *start;
     224                 :    21617972 :     adjust_for_weekend(pt,wadj,&adjusted_start);
     225                 :    21617972 :     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                 :       17934 :     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                 :       17934 :     switch (pt)
     234                 :             :     {
     235                 :           6 :     case PERIOD_YEAR:
     236                 :           6 :         mult *= 12;
     237                 :             :         /* fall through */
     238                 :       17896 :     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                 :       17896 :                 (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                 :       17896 :                 (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                 :       35785 :         else if ( g_date_is_last_of_month(next) ||
     310                 :          13 :                   ((pt == PERIOD_MONTH || pt == PERIOD_YEAR) &&
     311                 :       35798 :                    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                 :       17888 :             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                 :       17896 :         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                 :       17924 :     switch (pt)
     336                 :             :     {
     337                 :       17896 :     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                 :       17896 :         n_months = 12 * (g_date_get_year(next) - g_date_get_year(start)) +
     346                 :       17896 :                    (g_date_get_month(next) - g_date_get_month(start));
     347                 :       17896 :         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                 :       17896 :         dim = g_date_get_days_in_month(g_date_get_month(next),
     352                 :       17896 :                                        g_date_get_year(next));
     353                 :       17896 :         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                 :       17885 :         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                 :       17882 :             g_date_set_day(next, g_date_get_day(start)); /*same day as start*/
     368                 :             : 
     369                 :             :         /* Adjust for dates on the weekend. */
     370                 :       17896 :         adjust_for_weekend(pt,wadj,next);
     371                 :             :     }
     372                 :       17896 :     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                 :        7020 : recurrenceNthInstance(const Recurrence *r, guint n, GDate *date)
     386                 :             : {
     387                 :             :     GDate ref;
     388                 :             :     guint i;
     389                 :             : 
     390                 :       24891 :     for (*date = ref = r->start, i = 0; i < n; i++)
     391                 :             :     {
     392                 :       17871 :         recurrenceNextInstance(r, &ref, date);
     393                 :       17871 :         ref = *date;
     394                 :             :     }
     395                 :        7020 : }
     396                 :             : 
     397                 :             : time64
     398                 :        7020 : recurrenceGetPeriodTime(const Recurrence *r, guint period_num, gboolean end)
     399                 :             : {
     400                 :             :     GDate date;
     401                 :             :     time64 time;
     402                 :        7020 :     recurrenceNthInstance(r, period_num + (end ? 1 : 0), &date);
     403                 :        7020 :     if (end)
     404                 :             :     {
     405                 :        2823 :         g_date_subtract_days(&date, 1);
     406                 :        2823 :         time = gnc_dmy2time64_end (g_date_get_day(&date),
     407                 :        2823 :                                    g_date_get_month(&date),
     408                 :        2823 :                                    g_date_get_year (&date));
     409                 :             : 
     410                 :             :     }
     411                 :             :     else
     412                 :             :     {
     413                 :        4197 :         time = gnc_dmy2time64 (g_date_get_day(&date),
     414                 :        4197 :                                g_date_get_month(&date),
     415                 :        4197 :                                g_date_get_year (&date));
     416                 :             :     }
     417                 :        7020 :     return time;
     418                 :             : }
     419                 :             : 
     420                 :             : gnc_numeric
     421                 :        2119 : 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                 :        2119 :     g_return_val_if_fail(r && acc, gnc_numeric_zero());
     427                 :        2119 :     t1 = recurrenceGetPeriodTime(r, n, FALSE);
     428                 :        2119 :     t2 = recurrenceGetPeriodTime(r, n, TRUE);
     429                 :        2119 :     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_foreach(*recurrences, (GFunc)g_free, NULL);
     896                 :           0 :     g_list_free(*recurrences);
     897                 :           0 :     *recurrences = NULL;
     898                 :           0 : }
        

Generated by: LCOV version 2.0-1