[evolution-data-server] Bug 788193 - [itip-formatter] Describe used recurrence, if possible



commit 3e5bbfc850c8a6d60482791d3f4f0166ac9187ac
Author: Milan Crha <mcrha redhat com>
Date:   Mon Mar 12 14:19:29 2018 +0100

    Bug 788193 - [itip-formatter] Describe used recurrence, if possible

 src/calendar/libecal/e-cal-recur.c          |  965 +++++++++++++++++++++++++++
 src/calendar/libecal/e-cal-recur.h          |   23 +
 tests/libecal/CMakeLists.txt                |    1 +
 tests/libecal/test-ecal-recur-description.c |  216 ++++++
 4 files changed, 1205 insertions(+), 0 deletions(-)
---
diff --git a/src/calendar/libecal/e-cal-recur.c b/src/calendar/libecal/e-cal-recur.c
index 35892df..b56dd07 100644
--- a/src/calendar/libecal/e-cal-recur.c
+++ b/src/calendar/libecal/e-cal-recur.c
@@ -4811,3 +4811,968 @@ e_cal_recur_get_localized_nth (gint nth)
 
        return _(e_cal_recur_nth[nth]);
 }
+
+static gint
+cal_comp_util_recurrence_count_by_xxx (gshort *field,
+                                      gint max_elements)
+{
+       gint ii;
+
+       for (ii = 0; ii < max_elements; ii++)
+               if (field[ii] == ICAL_RECURRENCE_ARRAY_MAX)
+                       break;
+
+       return ii;
+}
+
+/**
+ * e_cal_recur_describe_recurrence:
+ * @icalcomp: an icalcomponent
+ * @week_start_day: a day when the week starts
+ * @flags: bit-or of #ECalRecurDescribeRecurrenceFlags
+ *
+ * Describes some simple types of recurrences in a human-readable and localized way.
+ * The @flags influence the output output format and what to do when the @icalcomp
+ * contain more complicated recurrence, some which the function cannot describe.
+ *
+ * The @week_start_day is used for weekly recurrences, to start the list of selected
+ * days at that day.
+ *
+ * Free the returned string with g_free(), when no longer needed.
+ *
+ * Returns: (nullable) (transfer full): a newly allocated string, which
+ *    describes the recurrence of the @icalcomp, or #NULL, when the @icalcomp
+ *    doesn't recur or the recurrence is too complicated to describe, also
+ *    according to given @flags.
+ *
+ * Since: 3.30
+ **/
+gchar *
+e_cal_recur_describe_recurrence (icalcomponent *icalcomp,
+                                GDateWeekday week_start_day,
+                                guint32 flags)
+{
+       gchar *prefix = NULL, *mid = NULL, *suffix = NULL, *result = NULL;
+       icalproperty *prop;
+       struct icalrecurrencetype rrule;
+       gint n_by_second, n_by_minute, n_by_hour;
+       gint n_by_day, n_by_month_day, n_by_year_day;
+       gint n_by_week_no, n_by_month, n_by_set_pos;
+       gboolean prefixed, fallback;
+
+       g_return_val_if_fail (icalcomp != NULL, NULL);
+
+       prefixed = (flags & E_CAL_RECUR_DESCRIBE_RECURRENCE_FLAG_PREFIXED) != 0;
+       fallback = (flags & E_CAL_RECUR_DESCRIBE_RECURRENCE_FLAG_FALLBACK) != 0;
+
+       prop = icalcomponent_get_first_property (icalcomp, ICAL_RRULE_PROPERTY);
+       if (!prop)
+               return NULL;
+
+       switch (icalcomponent_isa (icalcomp)) {
+       case ICAL_VEVENT_COMPONENT:
+       case ICAL_VTODO_COMPONENT:
+       case ICAL_VJOURNAL_COMPONENT:
+               break;
+       default:
+               return NULL;
+       }
+
+       if (icalcomponent_count_properties (icalcomp, ICAL_RRULE_PROPERTY) != 1 ||
+           icalcomponent_count_properties (icalcomp, ICAL_RDATE_PROPERTY) != 0 ||
+           icalcomponent_count_properties (icalcomp, ICAL_EXRULE_PROPERTY) != 0)
+               goto custom;
+
+       rrule = icalproperty_get_rrule (prop);
+
+       switch (rrule.freq) {
+       case ICAL_DAILY_RECURRENCE:
+       case ICAL_WEEKLY_RECURRENCE:
+       case ICAL_MONTHLY_RECURRENCE:
+       case ICAL_YEARLY_RECURRENCE:
+               break;
+       default:
+               goto custom;
+
+       }
+
+#define N_HAS_BY(field) (cal_comp_util_recurrence_count_by_xxx (field, G_N_ELEMENTS (field)))
+
+       n_by_second = N_HAS_BY (rrule.by_second);
+       n_by_minute = N_HAS_BY (rrule.by_minute);
+       n_by_hour = N_HAS_BY (rrule.by_hour);
+       n_by_day = N_HAS_BY (rrule.by_day);
+       n_by_month_day = N_HAS_BY (rrule.by_month_day);
+       n_by_year_day = N_HAS_BY (rrule.by_year_day);
+       n_by_week_no = N_HAS_BY (rrule.by_week_no);
+       n_by_month = N_HAS_BY (rrule.by_month);
+       n_by_set_pos = N_HAS_BY (rrule.by_set_pos);
+
+#undef N_HAS_BY
+
+       if (n_by_second != 0 ||
+           n_by_minute != 0 ||
+           n_by_hour != 0)
+               goto custom;
+
+       switch (rrule.freq) {
+       case ICAL_DAILY_RECURRENCE:
+               if (n_by_day != 0 ||
+                   n_by_month_day != 0 ||
+                   n_by_year_day != 0 ||
+                   n_by_week_no != 0 ||
+                   n_by_month != 0 ||
+                   n_by_set_pos != 0)
+                       goto custom;
+
+               if (rrule.interval > 0) {
+                       if (!rrule.count && !rrule.until.year) {
+                               if (prefixed) {
+                                       result = g_strdup_printf (
+                                               g_dngettext (GETTEXT_PACKAGE,
+                                                       "every day forever",
+                                                       "every %d days forever",
+                                                       rrule.interval), rrule.interval);
+                               } else {
+                                       result = g_strdup_printf (
+                                               g_dngettext (GETTEXT_PACKAGE,
+                                                       "Every day forever",
+                                                       "Every %d days forever",
+                                                       rrule.interval), rrule.interval);
+                               }
+                       } else {
+                               if (prefixed) {
+                                       prefix = g_strdup_printf (
+                                               g_dngettext (GETTEXT_PACKAGE,
+                                                       "every day",
+                                                       "every %d days",
+                                                       rrule.interval), rrule.interval);
+                               } else {
+                                       prefix = g_strdup_printf (
+                                               g_dngettext (GETTEXT_PACKAGE,
+                                                       "Every day",
+                                                       "Every %d days",
+                                                       rrule.interval), rrule.interval);
+                               }
+                       }
+               }
+               break;
+
+       case ICAL_WEEKLY_RECURRENCE: {
+               gint ii, ndays;
+               guint8 day_mask;
+               gint day_shift = week_start_day;
+
+               /* Sunday is at bit 0 in the day_mask */
+               if (day_shift < 0 || day_shift >= G_DATE_SUNDAY)
+                       day_shift = 0;
+
+               if (n_by_month_day != 0 ||
+                   n_by_year_day != 0 ||
+                   n_by_week_no != 0 ||
+                   n_by_month != 0 ||
+                   n_by_set_pos != 0)
+                       goto custom;
+
+               day_mask = 0;
+
+               for (ii = 0; ii < 8 && rrule.by_day[ii] != ICAL_RECURRENCE_ARRAY_MAX; ii++) {
+                       enum icalrecurrencetype_weekday weekday;
+                       gint pos;
+
+                       weekday = icalrecurrencetype_day_day_of_week (rrule.by_day[ii]);
+                       pos = icalrecurrencetype_day_position (rrule.by_day[ii]);
+
+                       if (pos != 0)
+                               goto custom;
+
+                       switch (weekday) {
+                       case ICAL_SUNDAY_WEEKDAY:
+                               day_mask |= 1 << 0;
+                               break;
+
+                       case ICAL_MONDAY_WEEKDAY:
+                               day_mask |= 1 << 1;
+                               break;
+
+                       case ICAL_TUESDAY_WEEKDAY:
+                               day_mask |= 1 << 2;
+                               break;
+
+                       case ICAL_WEDNESDAY_WEEKDAY:
+                               day_mask |= 1 << 3;
+                               break;
+
+                       case ICAL_THURSDAY_WEEKDAY:
+                               day_mask |= 1 << 4;
+                               break;
+
+                       case ICAL_FRIDAY_WEEKDAY:
+                               day_mask |= 1 << 5;
+                               break;
+
+                       case ICAL_SATURDAY_WEEKDAY:
+                               day_mask |= 1 << 6;
+                               break;
+
+                       default:
+                               break;
+                       }
+               }
+
+               if (ii == 0) {
+                       struct icaltimetype dtstart;
+
+                       dtstart = icalcomponent_get_dtstart (icalcomp);
+
+                       ii = icaltime_day_of_week (dtstart);
+                       if (ii >= 1)
+                               day_mask |= 1 << (ii - 1);
+               }
+
+               ndays = 0;
+
+               for (ii = 0; ii < 7; ii++) {
+                       if ((day_mask & (1 << ii)) != 0)
+                               ndays++;
+               }
+
+               if (prefixed) {
+                       prefix = g_strdup_printf (
+                               g_dngettext (GETTEXT_PACKAGE,
+                                       "every week",
+                                       "every %d weeks",
+                                       rrule.interval), rrule.interval);
+               } else {
+                       prefix = g_strdup_printf (
+                               g_dngettext (GETTEXT_PACKAGE,
+                                       "Every week",
+                                       "Every %d weeks",
+                                       rrule.interval), rrule.interval);
+               }
+
+               for (ii = 0; ii < 7 && ndays; ii++) {
+                       gint bit = ((ii + day_shift) % 7);
+
+                       if ((day_mask & (1 << bit)) != 0) {
+                               /* Translators: This is used to merge set of week day names in a recurrence, 
which can contain any
+                                  of the week day names. The string always starts with "on DAYNAME", then it 
can continue either
+                                  with ", DAYNAME" or " and DAYNAME", thus it can be something like "on 
Monday and Tuesday"
+                                  or "on Monday, Wednesday and Friday" or simply "on Saturday". The '%1$s' 
is replaced with
+                                  the previously gathered text, while the '%2$s' is replaced with the text 
to append. */
+                               #define merge_fmt C_("recur-description-dayname", "%1$s%2$s")
+                               #define merge_string(_on_str, _merge_str, _and_str) G_STMT_START {\
+                                       if (mid) { \
+                                               gchar *tmp; \
+                                               if (ndays == 1) \
+                                                       tmp = g_strdup_printf (merge_fmt, mid, _and_str); \
+                                               else \
+                                                       tmp = g_strdup_printf (merge_fmt, mid, _merge_str); \
+                                               g_free (mid); \
+                                               mid = tmp; \
+                                       } else { \
+                                               mid = g_strdup (_on_str); \
+                                       } \
+                                       ndays--; \
+                                       } G_STMT_END
+                               switch (bit) {
+                               case 0:
+                                       merge_string (C_("recur-description", "on Sunday"),
+                                                     C_("recur-description", ", Sunday"),
+                                                     C_("recur-description", " and Sunday"));
+                                       break;
+                               case 1:
+                                       merge_string (C_("recur-description", "on Monday"),
+                                                     C_("recur-description", ", Monday"),
+                                                     C_("recur-description", " and Monday"));
+                                       break;
+                               case 2:
+                                       merge_string (C_("recur-description", "on Tuesday"),
+                                                     C_("recur-description", ", Tuesday"),
+                                                     C_("recur-description", " and Tuesday"));
+                                       break;
+                               case 3:
+                                       merge_string (C_("recur-description", "on Wednesday"),
+                                                     C_("recur-description", ", Wednesday"),
+                                                     C_("recur-description", " and Wednesday"));
+                                       break;
+                               case 4:
+                                       merge_string (C_("recur-description", "on Thursday"),
+                                                     C_("recur-description", ", Thursday"),
+                                                     C_("recur-description", " and Thursday"));
+                                       break;
+                               case 5:
+                                       merge_string (C_("recur-description", "on Friday"),
+                                                     C_("recur-description", ", Friday"),
+                                                     C_("recur-description", " and Friday"));
+                                       break;
+                               case 6:
+                                       merge_string (C_("recur-description", "on Saturday"),
+                                                     C_("recur-description", ", Saturday"),
+                                                     C_("recur-description", " and Saturday"));
+                                       break;
+                               default:
+                                       g_warn_if_reached ();
+                                       break;
+                               }
+                               #undef merge_string
+                               #undef merge_fmt
+                       }
+               }
+               break;
+       }
+
+       case ICAL_MONTHLY_RECURRENCE: {
+               enum month_num_options {
+                       MONTH_NUM_INVALID = -1,
+                       MONTH_NUM_FIRST,
+                       MONTH_NUM_SECOND,
+                       MONTH_NUM_THIRD,
+                       MONTH_NUM_FOURTH,
+                       MONTH_NUM_FIFTH,
+                       MONTH_NUM_LAST,
+                       MONTH_NUM_DAY,
+                       MONTH_NUM_OTHER
+               };
+
+               enum month_day_options {
+                       MONTH_DAY_NTH,
+                       MONTH_DAY_MON,
+                       MONTH_DAY_TUE,
+                       MONTH_DAY_WED,
+                       MONTH_DAY_THU,
+                       MONTH_DAY_FRI,
+                       MONTH_DAY_SAT,
+                       MONTH_DAY_SUN
+               };
+
+               gint month_index = 1;
+               enum month_day_options month_day;
+               enum month_num_options month_num;
+
+               if (n_by_year_day != 0 ||
+                   n_by_week_no != 0 ||
+                   n_by_month != 0 ||
+                   n_by_set_pos > 1)
+                       goto custom;
+
+               if (n_by_month_day == 1) {
+                       gint nth;
+
+                       if (n_by_set_pos != 0)
+                               goto custom;
+
+                       nth = rrule.by_month_day[0];
+                       if (nth < 1 && nth != -1)
+                               goto custom;
+
+                       if (nth == -1) {
+                               struct icaltimetype dtstart;
+
+                               dtstart = icalcomponent_get_dtstart (icalcomp);
+
+                               month_index = dtstart.day;
+                               month_num = MONTH_NUM_LAST;
+                       } else {
+                               month_index = nth;
+                               month_num = MONTH_NUM_DAY;
+                       }
+                       month_day = MONTH_DAY_NTH;
+
+               } else if (n_by_day == 1) {
+                       enum icalrecurrencetype_weekday weekday;
+                       gint pos;
+
+                       /* Outlook 2000 uses BYDAY=TU;BYSETPOS=2, and will not
+                        * accept BYDAY=2TU. So we now use the same as Outlook
+                        * by default. */
+
+                       weekday = icalrecurrencetype_day_day_of_week (rrule.by_day[0]);
+                       pos = icalrecurrencetype_day_position (rrule.by_day[0]);
+
+                       if (pos == 0) {
+                               if (n_by_set_pos != 1)
+                                       goto custom;
+                               pos = rrule.by_set_pos[0];
+                       } else if (pos < 0) {
+                               goto custom;
+                       }
+
+                       switch (weekday) {
+                       case ICAL_MONDAY_WEEKDAY:
+                               month_day = MONTH_DAY_MON;
+                               break;
+
+                       case ICAL_TUESDAY_WEEKDAY:
+                               month_day = MONTH_DAY_TUE;
+                               break;
+
+                       case ICAL_WEDNESDAY_WEEKDAY:
+                               month_day = MONTH_DAY_WED;
+                               break;
+
+                       case ICAL_THURSDAY_WEEKDAY:
+                               month_day = MONTH_DAY_THU;
+                               break;
+
+                       case ICAL_FRIDAY_WEEKDAY:
+                               month_day = MONTH_DAY_FRI;
+                               break;
+
+                       case ICAL_SATURDAY_WEEKDAY:
+                               month_day = MONTH_DAY_SAT;
+                               break;
+
+                       case ICAL_SUNDAY_WEEKDAY:
+                               month_day = MONTH_DAY_SUN;
+                               break;
+
+                       default:
+                               goto custom;
+                       }
+
+                       if (pos == -1)
+                               month_num = MONTH_NUM_LAST;
+                       else
+                               month_num = pos - 1;
+               } else {
+                       goto custom;
+               }
+
+               if (prefixed) {
+                       prefix = g_strdup_printf (
+                               g_dngettext (GETTEXT_PACKAGE,
+                                       "every month",
+                                       "every %d months",
+                                       rrule.interval), rrule.interval);
+               } else {
+                       prefix = g_strdup_printf (
+                               g_dngettext (GETTEXT_PACKAGE,
+                                       "Every month",
+                                       "Every %d months",
+                                       rrule.interval), rrule.interval);
+               }
+
+               switch (month_day) {
+               case MONTH_DAY_NTH:
+                       if (month_num == MONTH_NUM_LAST) {
+                               switch (month_index) {
+                               case 0:
+                                       mid = g_strdup (C_("recur-description", "on the last Sunday"));
+                                       break;
+                               case 1:
+                                       mid = g_strdup (C_("recur-description", "on the last Monday"));
+                                       break;
+                               case 2:
+                                       mid = g_strdup (C_("recur-description", "on the last Tuesday"));
+                                       break;
+                               case 3:
+                                       mid = g_strdup (C_("recur-description", "on the last Wednesday"));
+                                       break;
+                               case 4:
+                                       mid = g_strdup (C_("recur-description", "on the last Thursday"));
+                                       break;
+                               case 5:
+                                       mid = g_strdup (C_("recur-description", "on the last Friday"));
+                                       break;
+                               case 6:
+                                       mid = g_strdup (C_("recur-description", "on the last Saturday"));
+                                       break;
+                               default:
+                                       g_warning ("%s: What is month_index:%d for the last day?", G_STRFUNC, 
month_index);
+                                       break;
+                               }
+                       } else { /* month_num = MONTH_NUM_DAY; */
+                               switch (month_index) {
+                               case 1:
+                                       /* Translators: This is added to a monthly recurrence, forming 
something like "Every month on the Xth day" */
+                                       mid = g_strdup (C_("recur-description", "on the 1st day"));
+                                       break;
+                               case 2:
+                                       /* Translators: This is added to a monthly recurrence, forming 
something like "Every month on the Xth day" */
+                                       mid = g_strdup (C_("recur-description", "on the 2nd day"));
+                                       break;
+                               case 3:
+                                       /* Translators: This is added to a monthly recurrence, forming 
something like "Every month on the Xth day" */
+                                       mid = g_strdup (C_("recur-description", "on the 3rd day"));
+                                       break;
+                               case 4:
+                                       /* Translators: This is added to a monthly recurrence, forming 
something like "Every month on the Xth day" */
+                                       mid = g_strdup (C_("recur-description", "on the 4th day"));
+                                       break;
+                               case 5:
+                                       /* Translators: This is added to a monthly recurrence, forming 
something like "Every month on the Xth day" */
+                                       mid = g_strdup (C_("recur-description", "on the 5th day"));
+                                       break;
+                               case 6:
+                                       /* Translators: This is added to a monthly recurrence, forming 
something like "Every month on the Xth day" */
+                                       mid = g_strdup (C_("recur-description", "on the 6th day"));
+                                       break;
+                               case 7:
+                                       /* Translators: This is added to a monthly recurrence, forming 
something like "Every month on the Xth day" */
+                                       mid = g_strdup (C_("recur-description", "on the 7th day"));
+                                       break;
+                               case 8:
+                                       /* Translators: This is added to a monthly recurrence, forming 
something like "Every month on the Xth day" */
+                                       mid = g_strdup (C_("recur-description", "on the 8th day"));
+                                       break;
+                               case 9:
+                                       /* Translators: This is added to a monthly recurrence, forming 
something like "Every month on the Xth day" */
+                                       mid = g_strdup (C_("recur-description", "on the 9th day"));
+                                       break;
+                               case 10:
+                                       /* Translators: This is added to a monthly recurrence, forming 
something like "Every month on the Xth day" */
+                                       mid = g_strdup (C_("recur-description", "on the 10th day"));
+                                       break;
+                               case 11:
+                                       /* Translators: This is added to a monthly recurrence, forming 
something like "Every month on the Xth day" */
+                                       mid = g_strdup (C_("recur-description", "on the 11th day"));
+                                       break;
+                               case 12:
+                                       /* Translators: This is added to a monthly recurrence, forming 
something like "Every month on the Xth day" */
+                                       mid = g_strdup (C_("recur-description", "on the 12th day"));
+                                       break;
+                               case 13:
+                                       /* Translators: This is added to a monthly recurrence, forming 
something like "Every month on the Xth day" */
+                                       mid = g_strdup (C_("recur-description", "on the 13th day"));
+                                       break;
+                               case 14:
+                                       /* Translators: This is added to a monthly recurrence, forming 
something like "Every month on the Xth day" */
+                                       mid = g_strdup (C_("recur-description", "on the 14th day"));
+                                       break;
+                               case 15:
+                                       /* Translators: This is added to a monthly recurrence, forming 
something like "Every month on the Xth day" */
+                                       mid = g_strdup (C_("recur-description", "on the 15th day"));
+                                       break;
+                               case 16:
+                                       /* Translators: This is added to a monthly recurrence, forming 
something like "Every month on the Xth day" */
+                                       mid = g_strdup (C_("recur-description", "on the 16th day"));
+                                       break;
+                               case 17:
+                                       /* Translators: This is added to a monthly recurrence, forming 
something like "Every month on the Xth day" */
+                                       mid = g_strdup (C_("recur-description", "on the 17th day"));
+                                       break;
+                               case 18:
+                                       /* Translators: This is added to a monthly recurrence, forming 
something like "Every month on the Xth day" */
+                                       mid = g_strdup (C_("recur-description", "on the 18th day"));
+                                       break;
+                               case 19:
+                                       /* Translators: This is added to a monthly recurrence, forming 
something like "Every month on the Xth day" */
+                                       mid = g_strdup (C_("recur-description", "on the 19th day"));
+                                       break;
+                               case 20:
+                                       /* Translators: This is added to a monthly recurrence, forming 
something like "Every month on the Xth day" */
+                                       mid = g_strdup (C_("recur-description", "on the 20th day"));
+                                       break;
+                               case 21:
+                                       /* Translators: This is added to a monthly recurrence, forming 
something like "Every month on the Xth day" */
+                                       mid = g_strdup (C_("recur-description", "on the 21st day"));
+                                       break;
+                               case 22:
+                                       /* Translators: This is added to a monthly recurrence, forming 
something like "Every month on the Xth day" */
+                                       mid = g_strdup (C_("recur-description", "on the 22nd day"));
+                                       break;
+                               case 23:
+                                       /* Translators: This is added to a monthly recurrence, forming 
something like "Every month on the Xth day" */
+                                       mid = g_strdup (C_("recur-description", "on the 23rd day"));
+                                       break;
+                               case 24:
+                                       /* Translators: This is added to a monthly recurrence, forming 
something like "Every month on the Xth day" */
+                                       mid = g_strdup (C_("recur-description", "on the 24th day"));
+                                       break;
+                               case 25:
+                                       /* Translators: This is added to a monthly recurrence, forming 
something like "Every month on the Xth day" */
+                                       mid = g_strdup (C_("recur-description", "on the 25th day"));
+                                       break;
+                               case 26:
+                                       /* Translators: This is added to a monthly recurrence, forming 
something like "Every month on the Xth day" */
+                                       mid = g_strdup (C_("recur-description", "on the 26th day"));
+                                       break;
+                               case 27:
+                                       /* Translators: This is added to a monthly recurrence, forming 
something like "Every month on the Xth day" */
+                                       mid = g_strdup (C_("recur-description", "on the 27th day"));
+                                       break;
+                               case 28:
+                                       /* Translators: This is added to a monthly recurrence, forming 
something like "Every month on the Xth day" */
+                                       mid = g_strdup (C_("recur-description", "on the 28th day"));
+                                       break;
+                               case 29:
+                                       /* Translators: This is added to a monthly recurrence, forming 
something like "Every month on the Xth day" */
+                                       mid = g_strdup (C_("recur-description", "on the 29th day"));
+                                       break;
+                               case 30:
+                                       /* Translators: This is added to a monthly recurrence, forming 
something like "Every month on the Xth day" */
+                                       mid = g_strdup (C_("recur-description", "on the 30th day"));
+                                       break;
+                               case 31:
+                                       /* Translators: This is added to a monthly recurrence, forming 
something like "Every month on the Xth day" */
+                                       mid = g_strdup (C_("recur-description", "on the 31st day"));
+                                       break;
+                               }
+                       }
+                       break;
+               case MONTH_DAY_MON:
+                       switch (month_num) {
+                       case MONTH_NUM_FIRST:
+                               mid = g_strdup (C_("recur-description", "on the first Monday"));
+                               break;
+                       case MONTH_NUM_SECOND:
+                               mid = g_strdup (C_("recur-description", "on the second Monday"));
+                               break;
+                       case MONTH_NUM_THIRD:
+                               mid = g_strdup (C_("recur-description", "on the third Monday"));
+                               break;
+                       case MONTH_NUM_FOURTH:
+                               mid = g_strdup (C_("recur-description", "on the fourth Monday"));
+                               break;
+                       case MONTH_NUM_FIFTH:
+                               mid = g_strdup (C_("recur-description", "on the fifth Monday"));
+                               break;
+                       case MONTH_NUM_LAST:
+                               mid = g_strdup (C_("recur-description", "on the last Monday"));
+                               break;
+                       default:
+                               g_warning ("%s: What is month_num:%d for month_day:%d?", G_STRFUNC, 
month_num, month_day);
+                               break;
+                       }
+                       break;
+               case MONTH_DAY_TUE:
+                       switch (month_num) {
+                       case MONTH_NUM_FIRST:
+                               mid = g_strdup (C_("recur-description", "on the first Tuesday"));
+                               break;
+                       case MONTH_NUM_SECOND:
+                               mid = g_strdup (C_("recur-description", "on the second Tuesday"));
+                               break;
+                       case MONTH_NUM_THIRD:
+                               mid = g_strdup (C_("recur-description", "on the third Tuesday"));
+                               break;
+                       case MONTH_NUM_FOURTH:
+                               mid = g_strdup (C_("recur-description", "on the fourth Tuesday"));
+                               break;
+                       case MONTH_NUM_FIFTH:
+                               mid = g_strdup (C_("recur-description", "on the fifth Tuesday"));
+                               break;
+                       case MONTH_NUM_LAST:
+                               mid = g_strdup (C_("recur-description", "on the last Tuesday"));
+                               break;
+                       default:
+                               g_warning ("%s: What is month_num:%d for month_day:%d?", G_STRFUNC, 
month_num, month_day);
+                               break;
+                       }
+                       break;
+               case MONTH_DAY_WED:
+                       switch (month_num) {
+                       case MONTH_NUM_FIRST:
+                               mid = g_strdup (C_("recur-description", "on the first Wednesday"));
+                               break;
+                       case MONTH_NUM_SECOND:
+                               mid = g_strdup (C_("recur-description", "on the second Wednesday"));
+                               break;
+                       case MONTH_NUM_THIRD:
+                               mid = g_strdup (C_("recur-description", "on the third Wednesday"));
+                               break;
+                       case MONTH_NUM_FOURTH:
+                               mid = g_strdup (C_("recur-description", "on the fourth Wednesday"));
+                               break;
+                       case MONTH_NUM_FIFTH:
+                               mid = g_strdup (C_("recur-description", "on the fifth Wednesday"));
+                               break;
+                       case MONTH_NUM_LAST:
+                               mid = g_strdup (C_("recur-description", "on the last Wednesday"));
+                               break;
+                       default:
+                               g_warning ("%s: What is month_num:%d for month_day:%d?", G_STRFUNC, 
month_num, month_day);
+                               break;
+                       }
+                       break;
+               case MONTH_DAY_THU:
+                       switch (month_num) {
+                       case MONTH_NUM_FIRST:
+                               mid = g_strdup (C_("recur-description", "on the first Thursday"));
+                               break;
+                       case MONTH_NUM_SECOND:
+                               mid = g_strdup (C_("recur-description", "on the second Thursday"));
+                               break;
+                       case MONTH_NUM_THIRD:
+                               mid = g_strdup (C_("recur-description", "on the third Thursday"));
+                               break;
+                       case MONTH_NUM_FOURTH:
+                               mid = g_strdup (C_("recur-description", "on the fourth Thursday"));
+                               break;
+                       case MONTH_NUM_FIFTH:
+                               mid = g_strdup (C_("recur-description", "on the fifth Thursday"));
+                               break;
+                       case MONTH_NUM_LAST:
+                               mid = g_strdup (C_("recur-description", "on the last Thursday"));
+                               break;
+                       default:
+                               g_warning ("%s: What is month_num:%d for month_day:%d?", G_STRFUNC, 
month_num, month_day);
+                               break;
+                       }
+                       break;
+               case MONTH_DAY_FRI:
+                       switch (month_num) {
+                       case MONTH_NUM_FIRST:
+                               mid = g_strdup (C_("recur-description", "on the first Friday"));
+                               break;
+                       case MONTH_NUM_SECOND:
+                               mid = g_strdup (C_("recur-description", "on the second Friday"));
+                               break;
+                       case MONTH_NUM_THIRD:
+                               mid = g_strdup (C_("recur-description", "on the third Friday"));
+                               break;
+                       case MONTH_NUM_FOURTH:
+                               mid = g_strdup (C_("recur-description", "on the fourth Friday"));
+                               break;
+                       case MONTH_NUM_FIFTH:
+                               mid = g_strdup (C_("recur-description", "on the fifth Friday"));
+                               break;
+                       case MONTH_NUM_LAST:
+                               mid = g_strdup (C_("recur-description", "on the last Friday"));
+                               break;
+                       default:
+                               g_warning ("%s: What is month_num:%d for month_day:%d?", G_STRFUNC, 
month_num, month_day);
+                               break;
+                       }
+                       break;
+               case MONTH_DAY_SAT:
+                       switch (month_num) {
+                       case MONTH_NUM_FIRST:
+                               mid = g_strdup (C_("recur-description", "on the first Saturday"));
+                               break;
+                       case MONTH_NUM_SECOND:
+                               mid = g_strdup (C_("recur-description", "on the second Saturday"));
+                               break;
+                       case MONTH_NUM_THIRD:
+                               mid = g_strdup (C_("recur-description", "on the third Saturday"));
+                               break;
+                       case MONTH_NUM_FOURTH:
+                               mid = g_strdup (C_("recur-description", "on the fourth Saturday"));
+                               break;
+                       case MONTH_NUM_FIFTH:
+                               mid = g_strdup (C_("recur-description", "on the fifth Saturday"));
+                               break;
+                       case MONTH_NUM_LAST:
+                               mid = g_strdup (C_("recur-description", "on the last Saturday"));
+                               break;
+                       default:
+                               g_warning ("%s: What is month_num:%d for month_day:%d?", G_STRFUNC, 
month_num, month_day);
+                               break;
+                       }
+                       break;
+               case MONTH_DAY_SUN:
+                       switch (month_num) {
+                       case MONTH_NUM_FIRST:
+                               mid = g_strdup (C_("recur-description", "on the first Sunday"));
+                               break;
+                       case MONTH_NUM_SECOND:
+                               mid = g_strdup (C_("recur-description", "on the second Sunday"));
+                               break;
+                       case MONTH_NUM_THIRD:
+                               mid = g_strdup (C_("recur-description", "on the third Sunday"));
+                               break;
+                       case MONTH_NUM_FOURTH:
+                               mid = g_strdup (C_("recur-description", "on the fourth Sunday"));
+                               break;
+                       case MONTH_NUM_FIFTH:
+                               mid = g_strdup (C_("recur-description", "on the fifth Sunday"));
+                               break;
+                       case MONTH_NUM_LAST:
+                               mid = g_strdup (C_("recur-description", "on the last Sunday"));
+                               break;
+                       default:
+                               g_warning ("%s: What is month_num:%d for month_day:%d?", G_STRFUNC, 
month_num, month_day);
+                               break;
+                       }
+                       break;
+               }
+
+               break;
+       }
+
+       case ICAL_YEARLY_RECURRENCE:
+               if (n_by_day != 0 ||
+                   n_by_month_day != 0 ||
+                   n_by_year_day != 0 ||
+                   n_by_week_no != 0 ||
+                   n_by_month != 0 ||
+                   n_by_set_pos != 0)
+                       goto custom;
+
+               if (rrule.interval > 0) {
+                       if (!rrule.count && !rrule.until.year) {
+                               if (prefixed) {
+                                       result = g_strdup_printf (
+                                               g_dngettext (GETTEXT_PACKAGE,
+                                                       "every year forever",
+                                                       "every %d years forever",
+                                                       rrule.interval), rrule.interval);
+                               } else {
+                                       result = g_strdup_printf (
+                                               g_dngettext (GETTEXT_PACKAGE,
+                                                       "Every year forever",
+                                                       "Every %d years forever",
+                                                       rrule.interval), rrule.interval);
+                               }
+                       } else {
+                               if (prefixed) {
+                                       prefix = g_strdup_printf (
+                                               g_dngettext (GETTEXT_PACKAGE,
+                                                       "every year",
+                                                       "every %d years",
+                                                       rrule.interval), rrule.interval);
+                               } else {
+                                       prefix = g_strdup_printf (
+                                               g_dngettext (GETTEXT_PACKAGE,
+                                                       "Every year",
+                                                       "Every %d years",
+                                                       rrule.interval), rrule.interval);
+                               }
+                       }
+               }
+               break;
+
+       default:
+               break;
+       }
+
+       if (prefix) {
+               if (rrule.count) {
+                       suffix = g_strdup_printf (
+                               g_dngettext (GETTEXT_PACKAGE,
+                                       /* Translators: This is one of the last possible parts of a 
recurrence description.
+                                          The text is appended at the end of the complete recurrence 
description, making it
+                                          for example: "Every 3 days for 10 occurrences" */
+                                       "for one occurrence",
+                                       "for %d occurrences",
+                                       rrule.count), rrule.count);
+               } else if (rrule.until.year) {
+                       struct tm tm;
+                       gchar dt_str[256];
+
+                       dt_str[0] = 0;
+
+                       if (!rrule.until.is_date) {
+                               icaltimezone *from_zone, *to_zone;
+                               struct icaltimetype dtstart;
+
+                               dtstart = icalcomponent_get_dtstart (icalcomp);
+
+                               from_zone = icaltimezone_get_utc_timezone ();
+                               to_zone = (icaltimezone *) dtstart.zone;
+
+                               if (to_zone)
+                                       icaltimezone_convert_time (&rrule.until, from_zone, to_zone);
+
+                               rrule.until.hour = 0;
+                               rrule.until.minute = 0;
+                               rrule.until.second = 0;
+                               rrule.until.is_date = TRUE;
+                       }
+
+                       tm = icaltimetype_to_tm (&rrule.until);
+                       e_time_format_date_and_time (&tm, FALSE, FALSE, FALSE, dt_str, 255);
+
+                       if (*dt_str) {
+                               /* Translators: This is one of the last possible parts of a recurrence 
description.
+                                  The '%s' is replaced with actual date, thus it can create something like
+                                  "until Mon 15.1.2018". The text is appended at the end of the complete
+                                  recurrence description, making it for example: "Every 3 days until Mon 
15.1.2018" */
+                               suffix = g_strdup_printf (C_("recur-description", "until %s"), dt_str);
+                       }
+               } else {
+                       /* Translators: This is one of the last possible parts of a recurrence description.
+                          The text is appended at the end of the complete recurrence description, making it
+                          for example: "Every 2 months on Tuesday, Thursday and Friday forever" */
+                       suffix = g_strdup (C_("recur-description", "forever"));
+               }
+       }
+
+ custom:
+       if (!result && prefix && suffix) {
+               if (mid) {
+                       /* Translators: This constructs a complete recurrence description; the '%1$s' is like 
"Every 2 weeks",
+                          the '%2$s' is like "on Tuesday and Friday" and the '%3$s' is like "for 10 
occurrences", constructing
+                          together one sentence: "Every 2 weeks on Tuesday and Friday for 10 occurrences". */
+                       result = g_strdup_printf (C_("recur-description", "%1$s %2$s %3$s"), prefix, mid, 
suffix);
+               } else {
+                       /* Translators: This constructs a complete recurrence description; the '%1$s' is like 
"Every 2 days",
+                          the '%2$s' is like "for 10 occurrences", constructing together one sentence:
+                          "Every 2 days for 10 occurrences". */
+                       result = g_strdup_printf (C_("recur-description", "%1$s %2$s"), prefix, suffix);
+               }
+       }
+
+       if (result) {
+               gint n_exdates;
+               gchar *tmp;
+
+               n_exdates = icalcomponent_count_properties (icalcomp, ICAL_EXDATE_PROPERTY);
+               if (n_exdates > 0) {
+                       gchar *exdates_str;
+
+                       exdates_str = g_strdup_printf (
+                               g_dngettext (GETTEXT_PACKAGE,
+                               /* Translators: This text is appended at the end of complete recur 
description using "%s%s" in
+                                  context "recur-description" */
+                                       ", with one exception",
+                                       ", with %d exceptions",
+                                       n_exdates), n_exdates);
+
+                       /* Translators: This appends text like ", with 3 exceptions" at the end of complete 
recurrence description.
+                          The "%1$s" is replaced with the recurrence description, the "%2$s" with the text 
about exceptions.
+                          It will form something like: "Every 2 weeks on Tuesday and Friday for 10 
occurrences, with 3 exceptions" */
+                       tmp = g_strdup_printf (C_("recur-description", "%1$s%2$s"), result, exdates_str);
+
+                       g_free (exdates_str);
+                       g_free (result);
+                       result = tmp;
+               }
+
+               if (prefixed) {
+                       const gchar *comp_prefix;
+
+                       if (icalcomponent_isa (icalcomp) == ICAL_VEVENT_COMPONENT) {
+                               if (icalcomponent_count_properties (icalcomp, ICAL_ORGANIZER_PROPERTY) > 0 &&
+                                   icalcomponent_count_properties (icalcomp, ICAL_ATTENDEE_PROPERTY) > 0) {
+                                       comp_prefix = C_("recur-description", "The meeting recurs");
+                               } else {
+                                       comp_prefix = C_("recur-description", "The appointment recurs");
+                               }
+                       } else if (icalcomponent_isa (icalcomp) == ICAL_VTODO_COMPONENT) {
+                               comp_prefix = C_("recur-description", "The task recurs");
+                       } else if (icalcomponent_isa (icalcomp) == ICAL_VJOURNAL_COMPONENT) {
+                               comp_prefix = C_("recur-description", "The memo recurs");
+                       }
+
+                       /* Translators: This adds a prefix in front of the complete recurrence description.
+                          The '%1$s' is replaced with something like "The meeting recurs" and
+                          the '%2$s' with something like "every 2 days forever", thus forming
+                          sentence like "This meeting recurs every 2 days forever" */
+                       tmp = g_strdup_printf (C_("recur-description-prefix", "%1$s %2$s"), comp_prefix, 
result);
+
+                       g_free (result);
+                       result = tmp;
+               }
+       } else if (fallback) {
+               if (icalcomponent_isa (icalcomp) == ICAL_VEVENT_COMPONENT) {
+                       if (icalcomponent_count_properties (icalcomp, ICAL_ORGANIZER_PROPERTY) > 0 &&
+                           icalcomponent_count_properties (icalcomp, ICAL_ATTENDEE_PROPERTY) > 0) {
+                               result = g_strdup (C_("recur-description", "The meeting recurs"));
+                       } else {
+                               result = g_strdup (C_("recur-description", "The appointment recurs"));
+                       }
+               } else if (icalcomponent_isa (icalcomp) == ICAL_VTODO_COMPONENT) {
+                       result = g_strdup (C_("recur-description", "The task recurs"));
+               } else if (icalcomponent_isa (icalcomp) == ICAL_VJOURNAL_COMPONENT) {
+                       result = g_strdup (C_("recur-description", "The memo recurs"));
+               }
+       }
+
+       g_free (prefix);
+       g_free (mid);
+       g_free (suffix);
+
+       return result;
+}
diff --git a/src/calendar/libecal/e-cal-recur.h b/src/calendar/libecal/e-cal-recur.h
index 0798829..025cfdb 100644
--- a/src/calendar/libecal/e-cal-recur.h
+++ b/src/calendar/libecal/e-cal-recur.h
@@ -100,6 +100,29 @@ extern const gchar *e_cal_recur_nth[31];
 
 const gchar *          e_cal_recur_get_localized_nth           (gint nth);
 
+/**
+ * ECalRecurDescribeRecurrenceFlags:
+ * @E_CAL_RECUR_DESCRIBE_RECURRENCE_FLAG_NONE: no extra flags, either returns %NULL or the recurrence 
description,
+ *    something like "Every 2 weeks..."
+ * @E_CAL_RECUR_DESCRIBE_RECURRENCE_FLAG_PREFIXED: either returns %NULL or the recurrence description 
prefixed
+ *    with text like "The meeting recurs", forming something like "The meeting recurs every 2 weeks..."
+ * @E_CAL_RECUR_DESCRIBE_RECURRENCE_FLAG_FALLBACK: returns %NULL only if the component doesn't recur,
+ *    otherwise returns either the recurrence description or at least text like "The meeting recurs"
+ *
+ * Influences behaviour of e_cal_recur_describe_recurrence().
+ *
+ * Since: 3.30
+ **/
+typedef enum {
+       E_CAL_RECUR_DESCRIBE_RECURRENCE_FLAG_NONE       = 0,
+       E_CAL_RECUR_DESCRIBE_RECURRENCE_FLAG_PREFIXED   = (1 << 0),
+       E_CAL_RECUR_DESCRIBE_RECURRENCE_FLAG_FALLBACK   = (1 << 1)
+} ECalRecurDescribeRecurrenceFlags;
+
+gchar *                        e_cal_recur_describe_recurrence         (icalcomponent *icalcomp,
+                                                                GDateWeekday week_start_day,
+                                                                guint32 flags);
+
 G_END_DECLS
 
 #endif
diff --git a/tests/libecal/CMakeLists.txt b/tests/libecal/CMakeLists.txt
index c6878a3..fbf0371 100644
--- a/tests/libecal/CMakeLists.txt
+++ b/tests/libecal/CMakeLists.txt
@@ -73,6 +73,7 @@ set(TESTS
        test-ecal-get-timezone
        test-ecal-add-timezone
        test-ecal-set-default-timezone
+       test-ecal-recur-description
        test-ecal-get-alarm-email-address
        test-ecal-get-cal-address
        test-ecal-get-ldap-attribute
diff --git a/tests/libecal/test-ecal-recur-description.c b/tests/libecal/test-ecal-recur-description.c
new file mode 100644
index 0000000..a92cdbb
--- /dev/null
+++ b/tests/libecal/test-ecal-recur-description.c
@@ -0,0 +1,216 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <libecal/libecal.h>
+#include <libical/ical.h>
+
+/* This is not a real test, it only prints expected and received
+   recurrence descriptions and the most it verifies that something
+   had been returned, instead of nothing and vice versa. The actual
+   comparison of the expected value and the result is for humans. */
+
+typedef struct _EEmptyFixture {
+       gboolean dummy;
+} EEmptyFixture;
+
+static void
+e_empty_test_setup (EEmptyFixture *fixture,
+                   gconstpointer user_data)
+{
+}
+
+static void
+e_empty_test_teardown (EEmptyFixture *fixture,
+                      gconstpointer user_data)
+{
+}
+
+#define EVENT_STR \
+       "BEGIN:VEVENT\r\n" \
+       "UID:1\r\n" \
+       "SUMMARY:test\r\n" \
+       "%s\r\n" \
+       "END:VEVENT\r\n"
+
+#define TASK_STR \
+       "BEGIN:VTODO\r\n" \
+       "UID:1\r\n" \
+       "SUMMARY:test\r\n" \
+       "%s\r\n" \
+       "END:VTODO\r\n"
+
+#define MEMO_STR \
+       "BEGIN:VJOURNAL\r\n" \
+       "UID:1\r\n" \
+       "SUMMARY:test\r\n" \
+       "%s\r\n" \
+       "END:VJOURNAL\r\n"
+
+#define TEXT_EXPECTED TRUE
+#define NULL_EXPECTED FALSE
+#define CUSTOM_RECURRENCE "RRULE:FREQ=MONTHLY;COUNT=2;BYHOUR=1"
+#define MEETING_STR 
"ORGANIZER:MAILTO:organizer@nowhere\r\nATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION:MAILTO:user@no.where\r\n"
+#define EXCEPTION_STR "EXDATE;VALUE=DATE:20180216\r\n"
+#define EXCEPTIONS_STR "EXDATE;VALUE=DATE:20180216\r\nEXDATE;VALUE=DATE:20180218\r\n"
+#define BOTH_FLAGS (E_CAL_RECUR_DESCRIBE_RECURRENCE_FLAG_PREFIXED | 
E_CAL_RECUR_DESCRIBE_RECURRENCE_FLAG_FALLBACK)
+#define FALLBACK_FLAG E_CAL_RECUR_DESCRIBE_RECURRENCE_FLAG_FALLBACK
+#define PREFIXED_FLAG E_CAL_RECUR_DESCRIBE_RECURRENCE_FLAG_PREFIXED
+
+static void
+test_recur_description (EEmptyFixture *fixture,
+                       gconstpointer user_data)
+{
+       struct _tests {
+               const gchar *description;
+               guint32 flags;
+               gboolean expects;
+               const gchar *comp_str;
+               const gchar *add_content;
+       } tests[] = {
+               { "Event without recurrence", 0, NULL_EXPECTED, EVENT_STR, "" },
+               { "Task without recurrence",  0, NULL_EXPECTED, TASK_STR, "" },
+               { "Memo without recurrence",  0, NULL_EXPECTED, MEMO_STR, "" },
+               { "Event without recurrence", PREFIXED_FLAG, NULL_EXPECTED, EVENT_STR, "" },
+               { "Task without recurrence",  PREFIXED_FLAG, NULL_EXPECTED, TASK_STR, "" },
+               { "Memo without recurrence",  PREFIXED_FLAG, NULL_EXPECTED, MEMO_STR, "" },
+               { "Event without recurrence", FALLBACK_FLAG, NULL_EXPECTED, EVENT_STR, "" },
+               { "Task without recurrence",  FALLBACK_FLAG, NULL_EXPECTED, TASK_STR, "" },
+               { "Memo without recurrence",  FALLBACK_FLAG, NULL_EXPECTED, MEMO_STR, "" },
+               { "Event with custom recurrence", 0, NULL_EXPECTED, EVENT_STR, CUSTOM_RECURRENCE },
+               { "Task with custom recurrence",  0, NULL_EXPECTED, TASK_STR, CUSTOM_RECURRENCE },
+               { "Memo with custom recurrence",  0, NULL_EXPECTED, MEMO_STR, CUSTOM_RECURRENCE },
+               { "Event with custom recurrence, with prefixed flag", PREFIXED_FLAG, NULL_EXPECTED, 
EVENT_STR, CUSTOM_RECURRENCE },
+               { "Task with custom recurrence, with prefixed flag",  PREFIXED_FLAG, NULL_EXPECTED, TASK_STR, 
CUSTOM_RECURRENCE },
+               { "Memo with custom recurrence, with prefixed flag",  PREFIXED_FLAG, NULL_EXPECTED, MEMO_STR, 
CUSTOM_RECURRENCE },
+               { "The appointment recurs", FALLBACK_FLAG, TEXT_EXPECTED, EVENT_STR, CUSTOM_RECURRENCE },
+               { "The meeting recurs",  FALLBACK_FLAG, TEXT_EXPECTED, EVENT_STR, MEETING_STR 
CUSTOM_RECURRENCE },
+               { "The task recurs",  FALLBACK_FLAG, TEXT_EXPECTED, TASK_STR, CUSTOM_RECURRENCE },
+               { "The memo recurs",  FALLBACK_FLAG, TEXT_EXPECTED, MEMO_STR, CUSTOM_RECURRENCE },
+               { "The appointment recurs", BOTH_FLAGS, TEXT_EXPECTED, EVENT_STR, CUSTOM_RECURRENCE },
+               { "The meeting recurs", BOTH_FLAGS, TEXT_EXPECTED, EVENT_STR, MEETING_STR CUSTOM_RECURRENCE },
+               { "The task recurs",  BOTH_FLAGS, TEXT_EXPECTED, TASK_STR, CUSTOM_RECURRENCE },
+               { "The memo recurs",  BOTH_FLAGS, TEXT_EXPECTED, MEMO_STR, CUSTOM_RECURRENCE },
+               { "Every 3 days for 2 occurrences", 0, TEXT_EXPECTED, EVENT_STR, 
"RRULE:FREQ=DAILY;COUNT=2;INTERVAL=3" },
+               { "Every 3 days for 2 occurrences", 0, TEXT_EXPECTED, EVENT_STR, MEETING_STR 
"RRULE:FREQ=DAILY;COUNT=2;INTERVAL=3" },
+               { "Every 3 days for 2 occurrences", 0, TEXT_EXPECTED, TASK_STR, 
"RRULE:FREQ=DAILY;COUNT=2;INTERVAL=3" },
+               { "Every 3 days for 2 occurrences", 0, TEXT_EXPECTED, MEMO_STR, 
"RRULE:FREQ=DAILY;COUNT=2;INTERVAL=3" },
+               { "Every 3 days for 2 occurrences", FALLBACK_FLAG, TEXT_EXPECTED, EVENT_STR, 
"RRULE:FREQ=DAILY;COUNT=2;INTERVAL=3" },
+               { "Every 3 days for 2 occurrences", FALLBACK_FLAG, TEXT_EXPECTED, EVENT_STR, MEETING_STR 
"RRULE:FREQ=DAILY;COUNT=2;INTERVAL=3" },
+               { "Every 3 days for 2 occurrences", FALLBACK_FLAG, TEXT_EXPECTED, TASK_STR, 
"RRULE:FREQ=DAILY;COUNT=2;INTERVAL=3" },
+               { "Every 3 days for 2 occurrences", FALLBACK_FLAG, TEXT_EXPECTED, MEMO_STR, 
"RRULE:FREQ=DAILY;COUNT=2;INTERVAL=3" },
+               { "The appointment recurs every 3 days for 2 occurrences", PREFIXED_FLAG, TEXT_EXPECTED, 
EVENT_STR, "RRULE:FREQ=DAILY;COUNT=2;INTERVAL=3" },
+               { "The meeting recurs every 3 days for 2 occurrences", PREFIXED_FLAG, TEXT_EXPECTED, 
EVENT_STR, MEETING_STR "RRULE:FREQ=DAILY;COUNT=2;INTERVAL=3" },
+               { "The task recurs every 3 days for 2 occurrences", PREFIXED_FLAG, TEXT_EXPECTED, TASK_STR, 
"RRULE:FREQ=DAILY;COUNT=2;INTERVAL=3" },
+               { "The memo recurs every 3 days for 2 occurrences", PREFIXED_FLAG, TEXT_EXPECTED, MEMO_STR, 
"RRULE:FREQ=DAILY;COUNT=2;INTERVAL=3" },
+               { "The appointment recurs every 3 days for 2 occurrences", BOTH_FLAGS, TEXT_EXPECTED, 
EVENT_STR, "RRULE:FREQ=DAILY;COUNT=2;INTERVAL=3" },
+               { "The meeting recurs every 3 days for 2 occurrences", BOTH_FLAGS, TEXT_EXPECTED, EVENT_STR, 
MEETING_STR "RRULE:FREQ=DAILY;COUNT=2;INTERVAL=3" },
+               { "The task recurs every 3 days for 2 occurrences", BOTH_FLAGS, TEXT_EXPECTED, TASK_STR, 
"RRULE:FREQ=DAILY;COUNT=2;INTERVAL=3" },
+               { "The memo recurs every 3 days for 2 occurrences", BOTH_FLAGS, TEXT_EXPECTED, MEMO_STR, 
"RRULE:FREQ=DAILY;COUNT=2;INTERVAL=3" },
+               { "Every 4 days until Thu 03/01/2018", 0, TEXT_EXPECTED, EVENT_STR, 
"RRULE:FREQ=DAILY;UNTIL=20180301;INTERVAL=4" },
+               { "Every 5 days forever", 0, TEXT_EXPECTED, EVENT_STR, "RRULE:FREQ=DAILY;INTERVAL=5" },
+               { "Every 2 weeks on Thursday forever", 0, TEXT_EXPECTED, EVENT_STR, 
"RRULE:FREQ=WEEKLY;INTERVAL=2;BYDAY=TH" },
+               { "Every week on Monday and Wednesday for 2 occurrences", 0, TEXT_EXPECTED, EVENT_STR, 
"RRULE:FREQ=WEEKLY;COUNT=2;BYDAY=MO,WE" },
+               { "Every 3 weeks on Tuesday, Friday and Saturday until Thu 03/01/2018", 0, TEXT_EXPECTED, 
EVENT_STR, "RRULE:FREQ=WEEKLY;UNTIL=20180301;INTERVAL=3;BYDAY=TU,FR,SA" },
+               { "Every 4 weeks on Tuesday, Thursday, Friday and Saturday until Thu 03/01/2018", 0, 
TEXT_EXPECTED, EVENT_STR, "RRULE:FREQ=WEEKLY;UNTIL=20180301;INTERVAL=4;BYDAY=TU,TH,FR,SA" },
+               { "Every 5 weeks on Tuesday, Wednesday, Thursday, Friday and Saturday forever", 0, 
TEXT_EXPECTED, EVENT_STR, "RRULE:FREQ=WEEKLY;INTERVAL=5;BYDAY=TU,WE,TH,FR,SA" },
+               { "Every 6 weeks on Monday, Tuesday, Wednesday, Thursday, Friday and Saturday forever", 0, 
TEXT_EXPECTED, EVENT_STR, "RRULE:FREQ=WEEKLY;INTERVAL=6;BYDAY=MO,TU,WE,TH,FR,SA" },
+               { "Every 7 weeks on Monday, Tuesday, Wednesday, Thursday, Friday, Saturday and Sunday 
forever", 0, TEXT_EXPECTED, EVENT_STR, "RRULE:FREQ=WEEKLY;INTERVAL=7;BYDAY=SU,MO,TU,WE,TH,FR,SA" },
+               { "Every week on Monday forever", 0, TEXT_EXPECTED, EVENT_STR, "RRULE:FREQ=WEEKLY;BYDAY=MO" },
+               { "Every week on Tuesday forever", 0, TEXT_EXPECTED, EVENT_STR, "RRULE:FREQ=WEEKLY;BYDAY=TU" 
},
+               { "Every week on Wednesday forever", 0, TEXT_EXPECTED, EVENT_STR, 
"RRULE:FREQ=WEEKLY;BYDAY=WE" },
+               { "Every week on Thursday forever", 0, TEXT_EXPECTED, EVENT_STR, "RRULE:FREQ=WEEKLY;BYDAY=TH" 
},
+               { "Every week on Friday forever", 0, TEXT_EXPECTED, EVENT_STR, "RRULE:FREQ=WEEKLY;BYDAY=FR" },
+               { "Every week on Saturday forever", 0, TEXT_EXPECTED, EVENT_STR, "RRULE:FREQ=WEEKLY;BYDAY=SA" 
},
+               { "Every week on Sunday forever", 0, TEXT_EXPECTED, EVENT_STR, "RRULE:FREQ=WEEKLY;BYDAY=SU" },
+               { "Every week on Thursday until Thu 03/01/2018", 0, TEXT_EXPECTED, EVENT_STR, 
"RRULE:FREQ=WEEKLY;UNTIL=20180301\r\nDTSTART:20180215T150000Z" },
+               { "Every month on the 1st day for one occurrence", 0, TEXT_EXPECTED, EVENT_STR, 
"RRULE:FREQ=MONTHLY;COUNT=1;BYMONTHDAY=1" },
+               { "Every 2 months on the first Monday for 3 occurrences", 0, TEXT_EXPECTED, EVENT_STR, 
"RRULE:FREQ=MONTHLY;COUNT=3;INTERVAL=2;BYDAY=MO;BYSETPOS=1" },
+               { "Every 2 months on the second Tuesday for 3 occurrences", 0, TEXT_EXPECTED, EVENT_STR, 
"RRULE:FREQ=MONTHLY;COUNT=3;INTERVAL=2;BYDAY=TU;BYSETPOS=2" },
+               { "Every 2 months on the third Wednesday for 3 occurrences", 0, TEXT_EXPECTED, EVENT_STR, 
"RRULE:FREQ=MONTHLY;COUNT=3;INTERVAL=2;BYDAY=WE;BYSETPOS=3" },
+               { "Every month on the fourth Thursday until Tue 05/01/2018", 0, TEXT_EXPECTED, EVENT_STR, 
"RRULE:FREQ=MONTHLY;UNTIL=20180501;BYDAY=TH;BYSETPOS=4" },
+               { "Every month on the fifth Friday until Tue 05/01/2018", 0, TEXT_EXPECTED, EVENT_STR, 
"RRULE:FREQ=MONTHLY;UNTIL=20180501;BYDAY=FR;BYSETPOS=5" },
+               { "Every month on the last Saturday until Tue 05/01/2018", 0, TEXT_EXPECTED, EVENT_STR, 
"RRULE:FREQ=MONTHLY;UNTIL=20180501;BYDAY=SA;BYSETPOS=-1" },
+               { "Every month on the third Sunday until Tue 05/01/2018", 0, TEXT_EXPECTED, EVENT_STR, 
"RRULE:FREQ=MONTHLY;UNTIL=20180501;BYDAY=SU;BYSETPOS=3" },
+               { "Every 2 months on the 13th day forever", 0, TEXT_EXPECTED, EVENT_STR, 
"RRULE:FREQ=MONTHLY;INTERVAL=2;BYMONTHDAY=13" },
+               { "Every 3 months on the 26th day forever", 0, TEXT_EXPECTED, EVENT_STR, 
"RRULE:FREQ=MONTHLY;INTERVAL=3;BYMONTHDAY=26" },
+               { "Every 3 years forever", 0, TEXT_EXPECTED, EVENT_STR, "RRULE:FREQ=YEARLY;INTERVAL=3" },
+               { "Every year for 3 occurrences", 0, TEXT_EXPECTED, EVENT_STR, "RRULE:FREQ=YEARLY;COUNT=3" },
+               { "The appointment recurs every day for 2 occurrences", PREFIXED_FLAG, TEXT_EXPECTED, 
EVENT_STR, "RRULE:FREQ=DAILY;COUNT=2" },
+               { "The meeting recurs every 2 weeks on Wednesday and Thursday for 3 occurrences", 
PREFIXED_FLAG, TEXT_EXPECTED, EVENT_STR, MEETING_STR "RRULE:FREQ=WEEKLY;COUNT=3;INTERVAL=2;BYDAY=WE,TH" },
+               { "The task recurs every 2 months on the 21st day for 3 occurrences", PREFIXED_FLAG, 
TEXT_EXPECTED, TASK_STR, "RRULE:FREQ=MONTHLY;COUNT=3;INTERVAL=2;BYMONTHDAY=21" },
+               { "The memo recurs every 2 years for 3 occurrences", PREFIXED_FLAG, TEXT_EXPECTED, MEMO_STR, 
"RRULE:FREQ=YEARLY;COUNT=3;INTERVAL=2" },
+               { "Every 2 years for 3 occurrences, with 2 exceptions", 0, TEXT_EXPECTED, EVENT_STR, 
EXCEPTIONS_STR "RRULE:FREQ=YEARLY;COUNT=3;INTERVAL=2" },
+               { "Every 2 weeks on Thursday and Saturday for 3 occurrences, with one exception", 0, 
TEXT_EXPECTED, EVENT_STR, EXCEPTION_STR "RRULE:FREQ=WEEKLY;COUNT=3;INTERVAL=2;BYDAY=TH,SA" },
+               { "Every 2 years for 3 occurrences, with 2 exceptions", FALLBACK_FLAG, TEXT_EXPECTED, 
EVENT_STR, EXCEPTIONS_STR "RRULE:FREQ=YEARLY;COUNT=3;INTERVAL=2" },
+               { "Every 2 weeks on Thursday and Saturday for 3 occurrences, with one exception", 
FALLBACK_FLAG, TEXT_EXPECTED, EVENT_STR, EXCEPTION_STR "RRULE:FREQ=WEEKLY;COUNT=3;INTERVAL=2;BYDAY=TH,SA" },
+               { "The appointment recurs every 2 years for 3 occurrences, with 2 exceptions", PREFIXED_FLAG, 
TEXT_EXPECTED, EVENT_STR, EXCEPTIONS_STR "RRULE:FREQ=YEARLY;COUNT=3;INTERVAL=2" },
+               { "The appointment recurs every 2 weeks on Thursday and Saturday for 3 occurrences, with one 
exception", PREFIXED_FLAG, TEXT_EXPECTED, EVENT_STR, EXCEPTION_STR 
"RRULE:FREQ=WEEKLY;COUNT=3;INTERVAL=2;BYDAY=TH,SA" },
+               { "The appointment recurs every 2 years for 3 occurrences, with 2 exceptions", BOTH_FLAGS, 
TEXT_EXPECTED, EVENT_STR, EXCEPTIONS_STR "RRULE:FREQ=YEARLY;COUNT=3;INTERVAL=2" },
+               { "The appointment recurs every 2 weeks on Thursday and Saturday for 3 occurrences, with one 
exception", BOTH_FLAGS, TEXT_EXPECTED, EVENT_STR, EXCEPTION_STR 
"RRULE:FREQ=WEEKLY;COUNT=3;INTERVAL=2;BYDAY=TH,SA" }
+       };
+       gint ii;
+
+       for (ii = 0; ii < G_N_ELEMENTS (tests); ii++) {
+               gchar *description;
+               gchar *comp_str;
+               icalcomponent *icalcomp;
+
+               comp_str = g_strdup_printf (tests[ii].comp_str, tests[ii].add_content);
+               icalcomp = icalcomponent_new_from_string (comp_str);
+               g_assert_nonnull (icalcomp);
+               g_free (comp_str);
+
+               description = e_cal_recur_describe_recurrence (icalcomp, G_DATE_MONDAY, tests[ii].flags);
+               if (tests[ii].expects == TEXT_EXPECTED) {
+                       g_assert_nonnull (description);
+
+                       if (g_strcmp0 (tests[ii].description, description) != 0) {
+                               printf ("[%d]: Expected '%s' with %s flags and %s, but got '%s'\n", ii, 
tests[ii].description,
+                                       tests[ii].flags == BOTH_FLAGS ? "BOTH" :
+                                       tests[ii].flags == PREFIXED_FLAG ? "PREFIXED" :
+                                       tests[ii].flags == FALLBACK_FLAG ? "FALLBACK" : "no",
+                                       icalcomponent_isa (icalcomp) == ICAL_VEVENT_COMPONENT ? "VEVENT" :
+                                       icalcomponent_isa (icalcomp) == ICAL_VTODO_COMPONENT ? "VTODO" :
+                                       icalcomponent_isa (icalcomp) == ICAL_VJOURNAL_COMPONENT ? "VJOURNAL" 
: "???",
+                                       description);
+                       }
+               } else {
+                       g_assert_null (description);
+               }
+
+               icalcomponent_free (icalcomp);
+               g_free (description);
+       }
+}
+
+gint
+main (gint argc,
+      gchar **argv)
+{
+       g_test_init (&argc, &argv, NULL);
+       g_test_bug_base ("https://bugzilla.gnome.org/";);
+
+       g_test_add (
+               "/ECal/RecurDescription",
+               EEmptyFixture,
+               NULL,
+               e_empty_test_setup,
+               test_recur_description,
+               e_empty_test_teardown);
+
+       return g_test_run ();
+}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]