org.gnucash.android.model.Recurrence.java Source code

Java tutorial

Introduction

Here is the source code for org.gnucash.android.model.Recurrence.java

Source

/*
 * Copyright (c) 2015 Ngewi Fet <ngewif@gmail.com>
 *
 * Licensed 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.gnucash.android.model;

import android.content.Context;
import android.support.annotation.NonNull;

import org.gnucash.android.R;
import org.gnucash.android.app.GnuCashApplication;
import org.gnucash.android.ui.util.RecurrenceParser;
import org.joda.time.DateTime;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.joda.time.Months;
import org.joda.time.ReadablePeriod;
import org.joda.time.Weeks;
import org.joda.time.Years;

import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Model for recurrences in the database
 * <p>Basically a wrapper around {@link PeriodType}</p>
 */
public class Recurrence extends BaseModel {

    private PeriodType mPeriodType;

    /**
     * Start time of the recurrence
     */
    private Timestamp mPeriodStart;

    /**
     * End time of this recurrence
     * <p>This value is not persisted to the database</p>
     */
    private Timestamp mPeriodEnd;

    /**
     * Describes which day on which to run the recurrence
     */
    private String mByDay;

    public Recurrence(@NonNull PeriodType periodType) {
        setPeriodType(periodType);
        mPeriodStart = new Timestamp(System.currentTimeMillis());
    }

    /**
     * Return the PeriodType for this recurrence
     * @return PeriodType for the recurrence
     */
    public PeriodType getPeriodType() {
        return mPeriodType;
    }

    /**
     * Sets the period type for the recurrence
     * @param periodType PeriodType
     */
    public void setPeriodType(PeriodType periodType) {
        this.mPeriodType = periodType;
    }

    /**
     * Return the start time for this recurrence
     * @return Timestamp of start of recurrence
     */
    public Timestamp getPeriodStart() {
        return mPeriodStart;
    }

    /**
     * Set the start time of this recurrence
     * @param periodStart {@link Timestamp} of recurrence
     */
    public void setPeriodStart(Timestamp periodStart) {
        this.mPeriodStart = periodStart;
    }

    /**
     * Returns an approximate period for this recurrence
     * <p>The period is approximate because months do not all have the same number of days,
     * but that is assumed</p>
     * @return Milliseconds since Epoch representing the period
     * @deprecated Do not use in new code. Uses fixed period values for months and years (which have variable units of time)
     */
    public long getPeriod() {
        long baseMillis = 0;
        switch (mPeriodType) {
        case DAY:
            baseMillis = RecurrenceParser.DAY_MILLIS;
            break;
        case WEEK:
            baseMillis = RecurrenceParser.WEEK_MILLIS;
            break;
        case MONTH:
            baseMillis = RecurrenceParser.MONTH_MILLIS;
            break;
        case YEAR:
            baseMillis = RecurrenceParser.YEAR_MILLIS;
            break;
        }
        return mPeriodType.getMultiplier() * baseMillis;
    }

    /**
     * Returns the event schedule (start, end and recurrence)
     * @return String description of repeat schedule
     */
    public String getRepeatString() {
        StringBuilder repeatBuilder = new StringBuilder(mPeriodType.getFrequencyRepeatString());
        Context context = GnuCashApplication.getAppContext();

        String dayOfWeek = new SimpleDateFormat("EEEE", GnuCashApplication.getDefaultLocale())
                .format(new Date(mPeriodStart.getTime()));
        if (mPeriodType == PeriodType.WEEK) {
            repeatBuilder.append(" ").append(context.getString(R.string.repeat_on_weekday, dayOfWeek));
        }

        if (mPeriodEnd != null) {
            String endDateString = SimpleDateFormat.getDateInstance().format(new Date(mPeriodEnd.getTime()));
            repeatBuilder.append(", ").append(context.getString(R.string.repeat_until_date, endDateString));
        }
        return repeatBuilder.toString();
    }

    /**
     * Creates an RFC 2445 string which describes this recurring event.
     * <p>See http://recurrance.sourceforge.net/</p>
     * <p>The output of this method is not meant for human consumption</p>
     * @return String describing event
     */
    public String getRuleString() {
        String separator = ";";

        StringBuilder ruleBuilder = new StringBuilder();

        //        =======================================================================
        //This section complies with the formal rules, but the betterpickers library doesn't like/need it

        //        SimpleDateFormat startDateFormat = new SimpleDateFormat("'TZID'=zzzz':'yyyyMMdd'T'HHmmss", Locale.US);
        //        ruleBuilder.append("DTSTART;");
        //        ruleBuilder.append(startDateFormat.format(new Date(mStartDate)));
        //            ruleBuilder.append("\n");
        //        ruleBuilder.append("RRULE:");
        //        ========================================================================

        ruleBuilder.append("FREQ=").append(mPeriodType.getFrequencyDescription()).append(separator);
        ruleBuilder.append("INTERVAL=").append(mPeriodType.getMultiplier()).append(separator);
        if (getCount() > 0)
            ruleBuilder.append("COUNT=").append(getCount()).append(separator);
        ruleBuilder.append(mPeriodType.getByParts(mPeriodStart.getTime())).append(separator);

        return ruleBuilder.toString();
    }

    /**
     * Return the number of days left in this period
     * @return Number of days left in period
     */
    public int getDaysLeftInCurrentPeriod() {
        LocalDate startDate = new LocalDate(System.currentTimeMillis());
        int interval = mPeriodType.getMultiplier() - 1;
        LocalDate endDate = null;
        switch (mPeriodType) {
        case DAY:
            endDate = new LocalDate(System.currentTimeMillis()).plusDays(interval);
            break;
        case WEEK:
            endDate = startDate.dayOfWeek().withMaximumValue().plusWeeks(interval);
            break;
        case MONTH:
            endDate = startDate.dayOfMonth().withMaximumValue().plusMonths(interval);
            break;
        case YEAR:
            endDate = startDate.dayOfYear().withMaximumValue().plusYears(interval);
            break;
        }

        return Days.daysBetween(startDate, endDate).getDays();
    }

    /**
     * Returns the number of periods from the start date of this recurrence until the end of the
     * interval multiplier specified in the {@link PeriodType}
     * //fixme: Improve the documentation
     * @return Number of periods in this recurrence
     */
    public int getNumberOfPeriods(int numberOfPeriods) {
        LocalDate startDate = new LocalDate(mPeriodStart.getTime());
        LocalDate endDate;
        int interval = mPeriodType.getMultiplier();
        //// TODO: 15.08.2016 Why do we add the number of periods. maybe rename method or param
        switch (mPeriodType) {

        case DAY:
            return 1;
        case WEEK:
            endDate = startDate.dayOfWeek().withMaximumValue().plusWeeks(numberOfPeriods);
            return Weeks.weeksBetween(startDate, endDate).getWeeks() / interval;
        case MONTH:
            endDate = startDate.dayOfMonth().withMaximumValue().plusMonths(numberOfPeriods);
            return Months.monthsBetween(startDate, endDate).getMonths() / interval;
        case YEAR:
            endDate = startDate.dayOfYear().withMaximumValue().plusYears(numberOfPeriods);
            return Years.yearsBetween(startDate, endDate).getYears() / interval;
        }

        return 0;
    }

    /**
     * Return the name of the current period
     * @return String of current period
     */
    public String getTextOfCurrentPeriod(int periodNum) {
        LocalDate startDate = new LocalDate(mPeriodStart.getTime());
        switch (mPeriodType) {

        case DAY:
            return startDate.dayOfWeek().getAsText();
        case WEEK:
            return startDate.weekOfWeekyear().getAsText();
        case MONTH:
            return startDate.monthOfYear().getAsText();
        case YEAR:
            return startDate.year().getAsText();
        }
        return "Period " + periodNum;
    }

    /**
     * Sets the string which determines on which day the recurrence will be run
     * @param byDay Byday string of recurrence rule (RFC 2445)
     */
    public void setByDay(String byDay) {
        this.mByDay = byDay;
    }

    /**
     * Return the byDay string of recurrence rule (RFC 2445)
     * @return String with by day specification
     */
    public String getByDay() {
        return mByDay;
    }

    /**
     * Computes the number of occurrences of this recurrences between start and end date
     * <p>If there is no end date, it returns -1</p>
     * @return Number of occurrences, or -1 if there is no end date
     */
    public int getCount() {
        if (mPeriodEnd == null)
            return -1;

        int multiple = mPeriodType.getMultiplier();
        ReadablePeriod jodaPeriod;
        switch (mPeriodType) {
        case DAY:
            jodaPeriod = Days.days(multiple);
            break;
        case WEEK:
            jodaPeriod = Weeks.weeks(multiple);
            break;
        case MONTH:
            jodaPeriod = Months.months(multiple);
            break;
        case YEAR:
            jodaPeriod = Years.years(multiple);
            break;
        default:
            jodaPeriod = Months.months(multiple);
        }
        int count = 0;
        LocalDateTime startTime = new LocalDateTime(mPeriodStart.getTime());
        while (startTime.toDateTime().getMillis() < mPeriodEnd.getTime()) {
            ++count;
            startTime = startTime.plus(jodaPeriod);
        }
        return count;

        /*
                //this solution does not use looping, but is not very accurate
            
                int multiplier = mPeriodType.getMultiplier();
                LocalDateTime startDate = new LocalDateTime(mPeriodStart.getTime());
                LocalDateTime endDate = new LocalDateTime(mPeriodEnd.getTime());
                switch (mPeriodType){
        case DAY:
            return Days.daysBetween(startDate, endDate).dividedBy(multiplier).getDays();
        case WEEK:
            return Weeks.weeksBetween(startDate, endDate).dividedBy(multiplier).getWeeks();
        case MONTH:
            return Months.monthsBetween(startDate, endDate).dividedBy(multiplier).getMonths();
        case YEAR:
            return Years.yearsBetween(startDate, endDate).dividedBy(multiplier).getYears();
        default:
            return -1;
                }
        */
    }

    /**
     * Sets the end time of this recurrence by specifying the number of occurences
     * @param numberOfOccurences Number of occurences from the start time
     */
    public void setPeriodEnd(int numberOfOccurences) {
        LocalDateTime localDate = new LocalDateTime(mPeriodStart.getTime());
        LocalDateTime endDate;
        int occurrenceDuration = numberOfOccurences * mPeriodType.getMultiplier();
        switch (mPeriodType) {
        case DAY:
            endDate = localDate.plusDays(occurrenceDuration);
            break;
        case WEEK:
            endDate = localDate.plusWeeks(occurrenceDuration);
            break;
        default:
        case MONTH:
            endDate = localDate.plusMonths(occurrenceDuration);
            break;
        case YEAR:
            endDate = localDate.plusYears(occurrenceDuration);
            break;
        }
        mPeriodEnd = new Timestamp(endDate.toDateTime().getMillis());
    }

    /**
     * Return the end date of the period in milliseconds
     * @return End date of the recurrence period
     */
    public Timestamp getPeriodEnd() {
        return mPeriodEnd;
    }

    /**
     * Set period end date
     * @param endTimestamp End time in milliseconds
     */
    public void setPeriodEnd(Timestamp endTimestamp) {
        mPeriodEnd = endTimestamp;
    }
}