docs: public static String convertRFC2445ToText(String rrule,
                                                LocalDate startDate) {
  
          try {
              RRule rule = new RRule(rrule);
              StringBuilder sb = new StringBuilder();
  
              String granularity = ""; //$NON-NLS-1$
              String granularities = ""; //$NON-NLS-1$
  
              switch (rule.getFreq()) {
                  case DAILY:
                      granularity = Messages.getString("Util.DAY"); //$NON-NLS-1$
                      granularities = Messages.getString("Util.DAYS"); //$NON-NLS-1$
                      break;
                  case MONTHLY:
                      granularity = Messages.getString("Util.MONTH"); //$NON-NLS-1$
                      granularities = Messages.getString("Util.MONTHS"); //$NON-NLS-1$
                      break;
                  case WEEKLY:
                      granularity = Messages.getString("Util.WEEK"); //$NON-NLS-1$
                      granularities = Messages.getString("Util.WEEKS"); //$NON-NLS-1$
                      break;
                  case YEARLY:
                      granularity = Messages.getString("Util.YEAR"); //$NON-NLS-1$
                      granularities = Messages.getString("Util.YEARS"); //$NON-NLS-1$
                      break;
                  case HOURLY:
                      granularity = Messages.getString("Util.HOUR"); //$NON-NLS-1$
                      granularities = Messages.getString("Util.HOURS"); //$NON-NLS-1$
                      break;
                  case MINUTELY:
                      granularity = Messages.getString("Util.MINUTE"); //$NON-NLS-1$
                      granularities = Messages.getString("Util.MINUTES"); //$NON-NLS-1$
                      break;
                  case SECONDLY:
                      granularity = Messages.getString("Util.SECOND"); //$NON-NLS-1$
                      granularities = Messages.getString("Util.SECONDS"); //$NON-NLS-1$
                      break;
              }
  
              int interval = rule.getInterval();
              if (interval > 1) {
                  sb.append(MessageFormat.format(Messages.getString("Util.EVERY_PLURAL"), //$NON-NLS-1$
                          rule.getInterval(), granularities));
              } else {
                  sb.append(MessageFormat.format(Messages.getString("Util.EVERY_SINGULAR"), granularity)); //$NON-NLS-1$
              }
  
              /*
               * Weekdays
               */
  
              if (rule.getFreq().equals(Frequency.WEEKLY)) {
                  List<WeekdayNum> byDay = rule.getByDay();
                  if (!byDay.isEmpty()) {
                      sb.append(Messages.getString("Util.ON_WEEKDAY")); //$NON-NLS-1$
                      for (int i = 0; i < byDay.size(); i++) {
                          WeekdayNum num = byDay.get(i);
                          sb.append(makeHuman(num.wday));
                          if (i < byDay.size() - 1) {
                              sb.append(", "); //$NON-NLS-1$
                          }
                      }
                  }
              }
  
              if (rule.getFreq().equals(Frequency.MONTHLY)) {
  
                  if (rule.getByMonthDay().length > 0) {
  
                      int day = rule.getByMonthDay()[0];
                      sb.append(Messages.getString("Util.ON_MONTH_DAY")); //$NON-NLS-1$
                      sb.append(day);
  
                  } else if (!rule.getByDay().isEmpty()) {
  
                      /*
                       * We only support one day.
                       */
                      WeekdayNum num = rule.getByDay().get(0);
  
                      sb.append(MessageFormat.format(Messages.getString("Util.ON_MONTH_WEEKDAY"), //$NON-NLS-1$
                              makeHuman(num.num), makeHuman(num.wday)));
  
                  }
              }
  
              if (rule.getFreq().equals(Frequency.YEARLY)) {
                  sb.append(MessageFormat.format(Messages.getString("Util.ON_DATE"), DateTimeFormatter //$NON-NLS-1$
                          .ofPattern(Messages.getString("Util.MONTH_AND_DAY_FORMAT")).format(startDate))); //$NON-NLS-1$
              }
  
              int count = rule.getCount();
              if (count > 0) {
                  if (count == 1) {
                      return Messages.getString("Util.ONCE"); //$NON-NLS-1$
                  } else {
                      sb.append(MessageFormat.format(Messages.getString("Util.TIMES"), count)); //$NON-NLS-1$
                  }
              } else {
                  DateValue until = rule.getUntil();
                  if (until != null) {
                      LocalDate localDate = LocalDate.of(until.year(),
                              until.month(), until.day());
                      sb.append(
                              MessageFormat.format(Messages.getString("Util.UNTIL_DATE"), //$NON-NLS-1$
                                      DateTimeFormatter
                                              .ofLocalizedDate(FormatStyle.LONG)
                                              .format(localDate)));
                  }
              }
  
              return sb.toString();
          } catch (ParseException e) {
              e.printStackTrace();
              return Messages.getString("Util.INVALID_RULE"); //$NON-NLS-1$
          }
      }