org.apache.fineract.portfolio.calendar.service.CalendarUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.fineract.portfolio.calendar.service.CalendarUtils.java

Source

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.fineract.portfolio.calendar.service;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;

import net.fortuna.ical4j.model.Date;
import net.fortuna.ical4j.model.DateList;
import net.fortuna.ical4j.model.DateTime;
import net.fortuna.ical4j.model.Recur;
import net.fortuna.ical4j.model.ValidationException;
import net.fortuna.ical4j.model.WeekDay;
import net.fortuna.ical4j.model.WeekDayList;
import net.fortuna.ical4j.model.parameter.Value;
import net.fortuna.ical4j.model.property.RRule;

import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
import org.apache.fineract.organisation.workingdays.service.WorkingDaysUtil;
import org.apache.fineract.portfolio.calendar.domain.Calendar;
import org.apache.fineract.portfolio.calendar.domain.CalendarFrequencyType;
import org.apache.fineract.portfolio.calendar.domain.CalendarWeekDaysType;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

public class CalendarUtils {

    static {
        System.setProperty("net.fortuna.ical4j.timezone.date.floating", "true");
    }

    public static LocalDate getNextRecurringDate(final String recurringRule, final LocalDate seedDate,
            final LocalDate startDate) {
        final Recur recur = CalendarUtils.getICalRecur(recurringRule);
        if (recur == null) {
            return null;
        }
        LocalDate nextDate = getNextRecurringDate(recur, seedDate, startDate);
        nextDate = adjustDate(nextDate, seedDate, getMeetingPeriodFrequencyType(recurringRule));
        return nextDate;
    }

    public static LocalDate adjustDate(final LocalDate date, final LocalDate seedDate,
            final PeriodFrequencyType frequencyType) {
        LocalDate adjustedVal = date;
        if (frequencyType.isMonthly() && seedDate.getDayOfMonth() > 28) {
            switch (date.getMonthOfYear()) {
            case 2:
                if (date.year().isLeap()) {
                    adjustedVal = date.dayOfMonth().setCopy(29);
                }
                break;
            case 4:
            case 6:
            case 9:
            case 11:
                if (seedDate.getDayOfMonth() > 30) {
                    adjustedVal = date.dayOfMonth().setCopy(30);
                } else {
                    adjustedVal = date.dayOfMonth().setCopy(seedDate.getDayOfMonth());
                }
                break;
            case 1:
            case 3:
            case 5:
            case 7:
            case 8:
            case 10:
            case 12:
                adjustedVal = date.dayOfMonth().setCopy(seedDate.getDayOfMonth());
                break;
            }
        }
        return adjustedVal;
    }

    private static LocalDate getNextRecurringDate(final Recur recur, final LocalDate seedDate,
            final LocalDate startDate) {
        final DateTime periodStart = new DateTime(startDate.toDate());
        final Date seed = convertToiCal4JCompatibleDate(seedDate);
        final Date nextRecDate = recur.getNextDate(seed, periodStart);
        return nextRecDate == null ? null : new LocalDate(nextRecDate);
    }

    private static Date convertToiCal4JCompatibleDate(final LocalDate inputDate) {
        // Date format in iCal4J is hard coded
        Date formattedDate = null;
        final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
        final String seedDateStr = df.format(inputDate.toDateTimeAtStartOfDay().toDate());
        try {
            formattedDate = new Date(seedDateStr, "yyyy-MM-dd");
        } catch (final ParseException e) {
            e.printStackTrace();
        }
        return formattedDate;
    }

    public static Collection<LocalDate> getRecurringDates(final String recurringRule, final LocalDate seedDate,
            final LocalDate endDate) {

        final LocalDate periodStartDate = DateUtils.getLocalDateOfTenant();
        final LocalDate periodEndDate = (endDate == null) ? DateUtils.getLocalDateOfTenant().plusYears(5) : endDate;
        return getRecurringDates(recurringRule, seedDate, periodStartDate, periodEndDate);
    }

    public static Collection<LocalDate> getRecurringDatesFrom(final String recurringRule, final LocalDate seedDate,
            final LocalDate startDate) {
        final LocalDate periodStartDate = (startDate == null) ? DateUtils.getLocalDateOfTenant() : startDate;
        final LocalDate periodEndDate = DateUtils.getLocalDateOfTenant().plusYears(5);
        return getRecurringDates(recurringRule, seedDate, periodStartDate, periodEndDate);
    }

    public static Collection<LocalDate> getRecurringDates(final String recurringRule, final LocalDate seedDate,
            final LocalDate periodStartDate, final LocalDate periodEndDate) {
        final int maxCount = 10;// Default number of recurring dates
        return getRecurringDates(recurringRule, seedDate, periodStartDate, periodEndDate, maxCount);
    }

    public static Collection<LocalDate> getRecurringDates(final String recurringRule, final LocalDate seedDate,
            final LocalDate periodStartDate, final LocalDate periodEndDate, final int maxCount) {

        final Recur recur = CalendarUtils.getICalRecur(recurringRule);

        return getRecurringDates(recur, seedDate, periodStartDate, periodEndDate, maxCount);
    }

    private static Collection<LocalDate> getRecurringDates(final Recur recur, final LocalDate seedDate,
            final LocalDate periodStartDate, final LocalDate periodEndDate, final int maxCount) {
        if (recur == null) {
            return null;
        }
        final Date seed = convertToiCal4JCompatibleDate(seedDate);
        final DateTime periodStart = new DateTime(periodStartDate.toDate());
        final DateTime periodEnd = new DateTime(periodEndDate.toDate());

        final Value value = new Value(Value.DATE.getValue());
        final DateList recurringDates = recur.getDates(seed, periodStart, periodEnd, value, maxCount);
        return convertToLocalDateList(recurringDates, seedDate, getMeetingPeriodFrequencyType(recur));
    }

    private static Collection<LocalDate> convertToLocalDateList(final DateList dates, final LocalDate seedDate,
            final PeriodFrequencyType frequencyType) {

        final Collection<LocalDate> recurringDates = new ArrayList<>();

        for (@SuppressWarnings("rawtypes")
        final Iterator iterator = dates.iterator(); iterator.hasNext();) {
            final Date date = (Date) iterator.next();
            recurringDates.add(adjustDate(new LocalDate(date), seedDate, frequencyType));
        }

        return recurringDates;
    }

    public static Recur getICalRecur(final String recurringRule) {

        // Construct RRule
        try {
            final RRule rrule = new RRule(recurringRule);
            rrule.validate();

            final Recur recur = rrule.getRecur();

            return recur;
        } catch (final ParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (final ValidationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return null;
    }

    public static String getRRuleReadable(final LocalDate startDate, final String recurringRule) {

        String humanReadable = "";

        RRule rrule;
        Recur recur = null;
        try {
            rrule = new RRule(recurringRule);
            rrule.validate();
            recur = rrule.getRecur();
        } catch (final ValidationException e) {
            throw new PlatformDataIntegrityException("error.msg.invalid.recurring.rule",
                    "The Recurring Rule value: " + recurringRule + " is not valid.", "recurrence", recurringRule);
        } catch (final ParseException e) {
            throw new PlatformDataIntegrityException("error.msg.recurring.rule.parsing.error",
                    "Error in pasring the Recurring Rule value: " + recurringRule, "recurrence", recurringRule);
        }

        if (recur == null) {
            return humanReadable;
        }

        if (recur.getFrequency().equals(Recur.DAILY)) {
            if (recur.getInterval() == 1) {
                humanReadable = "Daily";
            } else {
                humanReadable = "Every " + recur.getInterval() + " days";
            }
        } else if (recur.getFrequency().equals(Recur.WEEKLY)) {
            if (recur.getInterval() == 1 || recur.getInterval() == -1) {
                humanReadable = "Weekly";
            } else {
                humanReadable = "Every " + recur.getInterval() + " weeks";
            }

            humanReadable += " on ";
            final WeekDayList weekDayList = recur.getDayList();

            for (@SuppressWarnings("rawtypes")
            final Iterator iterator = weekDayList.iterator(); iterator.hasNext();) {
                final WeekDay weekDay = (WeekDay) iterator.next();
                humanReadable += DayNameEnum.from(weekDay.getDay()).getCode();
            }

        } else if (recur.getFrequency().equals(Recur.MONTHLY)) {
            if (recur.getInterval() == 1) {
                humanReadable = "Monthly on day " + startDate.getDayOfMonth();
            } else {
                humanReadable = "Every " + recur.getInterval() + " months on day " + startDate.getDayOfMonth();
            }
        } else if (recur.getFrequency().equals(Recur.YEARLY)) {
            if (recur.getInterval() == 1) {
                humanReadable = "Annually on " + startDate.toString("MMM") + " " + startDate.getDayOfMonth();
            } else {
                humanReadable = "Every " + recur.getInterval() + " years on " + startDate.toString("MMM") + " "
                        + startDate.getDayOfMonth();
            }
        }

        if (recur.getCount() > 0) {
            if (recur.getCount() == 1) {
                humanReadable = "Once";
            }
            humanReadable += ", " + recur.getCount() + " times";
        }

        final Date endDate = recur.getUntil();
        final LocalDate date = new LocalDate(endDate);
        final DateTimeFormatter fmt = DateTimeFormat.forPattern("dd MMMM YY");
        final String formattedDate = date.toString(fmt);
        if (endDate != null) {
            humanReadable += ", until " + formattedDate;
        }

        return humanReadable;
    }

    public static boolean isValidRedurringDate(final String recurringRule, final LocalDate seedDate,
            final LocalDate date) {

        final Recur recur = CalendarUtils.getICalRecur(recurringRule);
        if (recur == null) {
            return false;
        }

        return isValidRecurringDate(recur, seedDate, date);
    }

    public static boolean isValidRecurringDate(final Recur recur, final LocalDate seedDate, final LocalDate date) {

        final Collection<LocalDate> recurDate = getRecurringDates(recur, seedDate, date, date.plusDays(1), 1);
        return (recurDate == null || recurDate.isEmpty()) ? false : true;
    }

    public static enum DayNameEnum {
        MO(1, "Monday"), TU(2, "Tuesday"), WE(3, "Wednesday"), TH(4, "Thursday"), FR(5, "Friday"), SA(6,
                "Saturday"), SU(7, "Sunday");

        private final String code;
        private final Integer value;

        private DayNameEnum(final Integer value, final String code) {
            this.value = value;
            this.code = code;
        }

        public String getCode() {
            return this.code;
        }

        public int getValue() {
            return this.value;
        }

        public static DayNameEnum from(final String name) {
            for (final DayNameEnum dayName : DayNameEnum.values()) {
                if (dayName.toString().equals(name)) {
                    return dayName;
                }
            }
            return DayNameEnum.MO;// Default it to Monday
        }
    }

    public static PeriodFrequencyType getMeetingPeriodFrequencyType(final String recurringRule) {
        final Recur recur = CalendarUtils.getICalRecur(recurringRule);
        return getMeetingPeriodFrequencyType(recur);
    }

    private static PeriodFrequencyType getMeetingPeriodFrequencyType(final Recur recur) {
        PeriodFrequencyType meetingFrequencyType = PeriodFrequencyType.INVALID;
        if (recur.getFrequency().equals(Recur.DAILY)) {
            meetingFrequencyType = PeriodFrequencyType.DAYS;
        } else if (recur.getFrequency().equals(Recur.WEEKLY)) {
            meetingFrequencyType = PeriodFrequencyType.WEEKS;
        } else if (recur.getFrequency().equals(Recur.MONTHLY)) {
            meetingFrequencyType = PeriodFrequencyType.MONTHS;
        } else if (recur.getFrequency().equals(Recur.YEARLY)) {
            meetingFrequencyType = PeriodFrequencyType.YEARS;
        }
        return meetingFrequencyType;
    }

    public static String getMeetingFrequencyFromPeriodFrequencyType(final PeriodFrequencyType periodFrequency) {
        String frequency = null;
        if (periodFrequency.equals(PeriodFrequencyType.DAYS)) {
            frequency = Recur.DAILY;
        } else if (periodFrequency.equals(PeriodFrequencyType.WEEKS)) {
            frequency = Recur.WEEKLY;
        } else if (periodFrequency.equals(PeriodFrequencyType.MONTHS)) {
            frequency = Recur.MONTHLY;
        } else if (periodFrequency.equals(PeriodFrequencyType.YEARS)) {
            frequency = Recur.YEARLY;
        }
        return frequency;
    }

    public static int getInterval(final String recurringRule) {
        final Recur recur = CalendarUtils.getICalRecur(recurringRule);
        return recur.getInterval();
    }

    public static CalendarFrequencyType getFrequency(final String recurringRule) {
        final Recur recur = CalendarUtils.getICalRecur(recurringRule);
        return CalendarFrequencyType.fromString(recur.getFrequency());
    }

    public static CalendarWeekDaysType getRepeatsOnDay(final String recurringRule) {
        final Recur recur = CalendarUtils.getICalRecur(recurringRule);
        final WeekDayList weekDays = recur.getDayList();
        if (weekDays.isEmpty())
            return CalendarWeekDaysType.INVALID;
        // supports only one day
        WeekDay weekDay = (WeekDay) weekDays.get(0);
        return CalendarWeekDaysType.fromString(weekDay.getDay());
    }

    public static LocalDate getFirstRepaymentMeetingDate(final Calendar calendar, final LocalDate disbursementDate,
            final Integer loanRepaymentInterval, final String frequency) {
        final Recur recur = CalendarUtils.getICalRecur(calendar.getRecurrence());
        if (recur == null) {
            return null;
        }
        LocalDate startDate = disbursementDate;
        final LocalDate seedDate = calendar.getStartDateLocalDate();
        if (isValidRedurringDate(calendar.getRecurrence(), seedDate, startDate)) {
            startDate = startDate.plusDays(1);
        }
        // Recurring dates should follow loanRepaymentInterval.
        // e.g.
        // for weekly meeting interval is 1
        // where as for loan product with fortnightly frequency interval is 2
        // to generate currect set of meeting dates reset interval same as loan
        // repayment interval.
        recur.setInterval(loanRepaymentInterval);

        // Recurring dates should follow loanRepayment frequency.
        // e.g.
        // daily meeting frequency should support all loan products with any
        // frequency type.
        // to generate currect set of meeting dates reset frequency same as loan
        // repayment frequency.
        if (recur.getFrequency().equals(Recur.DAILY)) {
            recur.setFrequency(frequency);
        }

        final LocalDate firstRepaymentDate = getNextRecurringDate(recur, seedDate, startDate);

        return firstRepaymentDate;
    }

    public static LocalDate getNewRepaymentMeetingDate(final String recurringRule, final LocalDate seedDate,
            final LocalDate oldRepaymentDate, final Integer loanRepaymentInterval, final String frequency,
            final WorkingDays workingDays) {
        final Recur recur = CalendarUtils.getICalRecur(recurringRule);
        if (recur == null) {
            return null;
        }
        if (isValidRecurringDate(recur, seedDate, oldRepaymentDate)) {
            return oldRepaymentDate;
        }
        return getNextRepaymentMeetingDate(recurringRule, seedDate, oldRepaymentDate, loanRepaymentInterval,
                frequency, workingDays);
    }

    public static LocalDate getNextRepaymentMeetingDate(final String recurringRule, final LocalDate seedDate,
            final LocalDate repaymentDate, final Integer loanRepaymentInterval, final String frequency,
            final WorkingDays workingDays) {

        final Recur recur = CalendarUtils.getICalRecur(recurringRule);
        if (recur == null) {
            return null;
        }
        LocalDate tmpDate = repaymentDate;
        if (isValidRecurringDate(recur, seedDate, repaymentDate)) {
            tmpDate = repaymentDate.plusDays(1);
        }
        /*
         * Recurring dates should follow loanRepaymentInterval.
         * 
         * e.g. The weekly meeting will have interval of 1, if the loan product
         * with fortnightly frequency will have interval of 2, to generate right
         * set of meeting dates reset interval same as loan repayment interval.
         */
        recur.setInterval(loanRepaymentInterval);

        /*
         * Recurring dates should follow loanRepayment frequency. //e.g. daily
         * meeting frequency should support all loan products with any type of
         * frequency. to generate right set of meeting dates reset frequency
         * same as loan repayment frequency.
         */
        if (recur.getFrequency().equals(Recur.DAILY)) {
            recur.setFrequency(frequency);
        }

        LocalDate newRepaymentDate = getNextRecurringDate(recur, seedDate, tmpDate);
        final LocalDate nextRepaymentDate = getNextRecurringDate(recur, seedDate, newRepaymentDate);

        newRepaymentDate = WorkingDaysUtil.getOffSetDateIfNonWorkingDay(newRepaymentDate, nextRepaymentDate,
                workingDays);

        return newRepaymentDate;
    }

    public static boolean isFrequencySame(final String oldRRule, final String newRRule) {
        final Recur oldRecur = getICalRecur(oldRRule);
        final Recur newRecur = getICalRecur(newRRule);

        if (oldRecur == null || oldRecur.getFrequency() == null || newRecur == null
                || newRecur.getFrequency() == null) {
            return false;
        }
        return oldRecur.getFrequency().equals(newRecur.getFrequency());
    }

    public static boolean isIntervalSame(final String oldRRule, final String newRRule) {
        final Recur oldRecur = getICalRecur(oldRRule);
        final Recur newRecur = getICalRecur(newRRule);

        if (oldRecur == null || oldRecur.getFrequency() == null || newRecur == null
                || newRecur.getFrequency() == null) {
            return false;
        }
        return (oldRecur.getInterval() == newRecur.getInterval());
    }

    public static List<Integer> createIntegerListFromQueryParameter(final String calendarTypeQuery) {
        final List<Integer> calendarTypeOptions = new ArrayList<>();
        // adding all calendar Types if query parameter is "all"
        if (calendarTypeQuery.equalsIgnoreCase("all")) {
            calendarTypeOptions.add(1);
            calendarTypeOptions.add(2);
            calendarTypeOptions.add(3);
            calendarTypeOptions.add(4);
            return calendarTypeOptions;
        }
        // creating a list of calendar type options from the comma separated
        // query parameter.
        final List<String> calendarTypeOptionsInQuery = new ArrayList<>();
        final StringTokenizer st = new StringTokenizer(calendarTypeQuery, ",");
        while (st.hasMoreElements()) {
            calendarTypeOptionsInQuery.add(st.nextElement().toString());
        }

        for (final String calType : calendarTypeOptionsInQuery) {
            if (calType.equalsIgnoreCase("collection")) {
                calendarTypeOptions.add(1);
            } else if (calType.equalsIgnoreCase("training")) {
                calendarTypeOptions.add(2);
            } else if (calType.equalsIgnoreCase("audit")) {
                calendarTypeOptions.add(3);
            } else if (calType.equalsIgnoreCase("general")) {
                calendarTypeOptions.add(4);
            }
        }

        return calendarTypeOptions;
    }

    /**
     * function returns a comma separated list of calendar_type_enum values ex.
     * 1,2,3,4
     * 
     * @param calendarTypeOptions
     * @return
     */
    public static String getSqlCalendarTypeOptionsInString(final List<Integer> calendarTypeOptions) {
        String sqlCalendarTypeOptions = "";
        final int size = calendarTypeOptions.size();
        for (int i = 0; i < size - 1; i++) {
            sqlCalendarTypeOptions += calendarTypeOptions.get(i).toString() + ",";
        }
        sqlCalendarTypeOptions += calendarTypeOptions.get(size - 1).toString();
        return sqlCalendarTypeOptions;
    }

    public static LocalDate getRecentEligibleMeetingDate(final String recurringRule, final LocalDate seedDate) {
        LocalDate currentDate = DateUtils.getLocalDateOfTenant();
        final Recur recur = CalendarUtils.getICalRecur(recurringRule);
        if (recur == null) {
            return null;
        }

        if (isValidRecurringDate(recur, seedDate, currentDate)) {
            return currentDate;
        }

        if (recur.getFrequency().equals(Recur.DAILY)) {
            currentDate = currentDate.plusDays(recur.getInterval());
        } else if (recur.getFrequency().equals(Recur.WEEKLY)) {
            currentDate = currentDate.plusWeeks(recur.getInterval());
        } else if (recur.getFrequency().equals(Recur.MONTHLY)) {
            currentDate = currentDate.plusMonths(recur.getInterval());
        } else if (recur.getFrequency().equals(Recur.YEARLY)) {
            currentDate = currentDate.plusYears(recur.getInterval());
        }

        return getNextRecurringDate(recur, seedDate, currentDate);
    }

    public static LocalDate getNextScheduleDate(final Calendar calendar, final LocalDate startDate) {
        final Recur recur = CalendarUtils.getICalRecur(calendar.getRecurrence());
        if (recur == null) {
            return null;
        }
        LocalDate date = startDate;
        final LocalDate seedDate = calendar.getStartDateLocalDate();
        /**
         * if (isValidRedurringDate(calendar.getRecurrence(), seedDate, date)) {
         * date = date.plusDays(1); }
         **/

        final LocalDate scheduleDate = getNextRecurringDate(recur, seedDate, date);

        return scheduleDate;
    }
}