/******************************************************************************* * Copyright (c) 2010 Denis Solonenko. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v2.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * * Contributors: * Denis Solonenko - initial API and implementation ******************************************************************************/ package ru.orangesoftware.financisto2.utils; import java.text.DateFormat; import java.util.Calendar; import java.util.Date; import java.util.EnumSet; import java.util.HashMap; import java.util.LinkedList; import ru.orangesoftware.financisto2.R; import ru.orangesoftware.financisto2.datetime.DateUtils; import ru.orangesoftware.financisto2.datetime.Period; import ru.orangesoftware.financisto2.datetime.PeriodType; import android.content.Context; public class RecurUtils { private static final long DAY_IN_MS = 24*60*60*1000L; public interface Layoutable { int getLayoutId(); } public enum RecurInterval implements Layoutable, LocalizableEnum { NO_RECUR(0, R.string.recur_interval_no_recur), EVERY_X_DAY(R.layout.recur_every_x_day, R.string.recur_interval_every_x_day), DAILY(0, R.string.recur_interval_daily), WEEKLY(R.layout.recur_weekly, R.string.recur_interval_weekly){ @Override public Period next(long startDate) { Calendar c = Calendar.getInstance(); c.setTimeInMillis(startDate); startDate = DateUtils.startOfDay(c).getTimeInMillis(); c.add(Calendar.DAY_OF_MONTH, 6); long endDate = DateUtils.endOfDay(c).getTimeInMillis(); return new Period(PeriodType.CUSTOM, startDate, endDate); } }, MONTHLY(0, R.string.recur_interval_monthly){ @Override public Period next(long startDate) { Calendar c = Calendar.getInstance(); c.setTimeInMillis(startDate); startDate = DateUtils.startOfDay(c).getTimeInMillis(); c.add(Calendar.MONTH, 1); c.add(Calendar.DAY_OF_MONTH, -1); long endDate = DateUtils.endOfDay(c).getTimeInMillis(); return new Period(PeriodType.CUSTOM, startDate, endDate); } }, SEMI_MONTHLY(R.layout.recur_semi_monthly, R.string.recur_interval_semi_monthly), YEARLY(0, R.string.recur_interval_yearly); private final int layoutId; private final int titleId; private RecurInterval(int layoutId, int titleId) { this.layoutId = layoutId; this.titleId = titleId; } @Override public int getLayoutId() { return layoutId; } @Override public int getTitleId() { return titleId; } public Period next(long startDate) { throw new UnsupportedOperationException(); } } public enum RecurPeriod implements Layoutable, LocalizableEnum { STOPS_ON_DATE(R.layout.recur_stops_on_date, R.string.recur_stops_on_date){ @Override public String toSummary(Context context, long param) { DateFormat df = DateUtils.getShortDateFormat(context); return String.format(context.getString(R.string.recur_stops_on_date_summary), df.format(new Date(param))); } @Override public Period[] repeat(RecurInterval interval, long startDate, long periodParam) { long endDate = 0; LinkedList<Period> periods = new LinkedList<Period>(); while (endDate < periodParam) { Period p = interval.next(startDate); startDate = p.end+1; endDate = p.end; periods.add(p); } return periods.toArray(new Period[periods.size()]); } }, // INDEFINETELY(0, R.string.recur_indefinitely){ // @Override // public String toSummary(Context context, long param) { // return context.getString(R.string.recur_indefinitely); // } // }, EXACTLY_TIMES(R.layout.recur_exactly_n_times, R.string.recur_exactly_n_times){ @Override public String toSummary(Context context, long param) { return String.format(context.getString(R.string.recur_exactly_n_times_summary), param); } @Override public Period[] repeat(RecurInterval interval, long startDate, long periodParam) { LinkedList<Period> periods = new LinkedList<Period>(); while (periodParam-- > 0) { Period p = interval.next(startDate); startDate = p.end+1; periods.add(p); } return periods.toArray(new Period[periods.size()]); } }; private final int layoutId; private final int titleId; private RecurPeriod(int layoutId, int titleId) { this.layoutId = layoutId; this.titleId = titleId; } @Override public int getLayoutId() { return layoutId; } @Override public int getTitleId() { return titleId; } public abstract String toSummary(Context context, long param); public abstract Period[] repeat(RecurInterval interval, long startDate, long periodParam); } public static abstract class Recur implements Cloneable { public final RecurInterval interval; public long startDate; public RecurPeriod period; public long periodParam; protected Recur(RecurInterval interval, HashMap<String, String> values) { this.interval = interval; this.startDate = getLong(values, "startDate"); this.period = RecurPeriod.valueOf(values.get("period")); this.periodParam = getLong(values, "periodParam"); } public Recur(RecurInterval interval) { this.interval = interval; this.startDate = System.currentTimeMillis(); this.period = RecurPeriod.EXACTLY_TIMES; this.periodParam = 1; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(interval.name()).append(","); sb.append("startDate=").append(startDate).append(","); sb.append("period=").append(period.name()).append(","); sb.append("periodParam=").append(periodParam).append(","); toString(sb); return sb.toString(); } protected void toString(StringBuilder sb) { // do nothing } @Override public Recur clone() { try { return (Recur)super.clone(); } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } } public String toString(Context context) { DateFormat df = DateUtils.getShortDateFormat(context); StringBuilder sb = new StringBuilder(); sb.append(context.getString(R.string.recur_repeat_starts_on)).append(" "); sb.append(df.format(new Date(startDate))).append(", "); sb.append(context.getString(interval.titleId)).append(", "); sb.append(period.toSummary(context, periodParam)); return sb.toString(); } //public abstract long getNextRecur(long currentDate); } public static class NoRecur extends Recur { protected NoRecur(HashMap<String, String> values) { super(RecurInterval.NO_RECUR, values); } public NoRecur() { super(RecurInterval.NO_RECUR); } } public static class EveryXDay extends Recur { public int days; protected EveryXDay(HashMap<String, String> values) { super(RecurInterval.EVERY_X_DAY, values); this.days = getInt(values, "days"); } public EveryXDay() { super(RecurInterval.EVERY_X_DAY); this.days = 1; } @Override protected void toString(StringBuilder sb) { sb.append("days=").append(days); } //@Override public long getNextRecur(long currentDate) { if (currentDate <= startDate) { return startDate; } long period = days*DAY_IN_MS; long delta = currentDate - startDate; long n = delta/period; long next = n*period; if (next > currentDate) { return next; } else { return next+period; } } } public static class Daily extends Recur { protected Daily(HashMap<String, String> values) { super(RecurInterval.DAILY, values); } public Daily() { super(RecurInterval.DAILY); } } public static enum DayOfWeek { SUN(R.id.daySun), MON(R.id.dayMon), TUE(R.id.dayTue), WED(R.id.dayWed), THR(R.id.dayThr), FRI(R.id.dayFri), SAT(R.id.daySat); public final int checkboxId; private DayOfWeek(int checkboxId) { this.checkboxId = checkboxId; } } public static class Weekly extends Recur { private final EnumSet<DayOfWeek> days; protected Weekly(HashMap<String, String> values) { super(RecurInterval.WEEKLY, values); this.days = EnumSet.noneOf(DayOfWeek.class); String s = values.get("days"); if (!Utils.isEmpty(s)) { String[] a = s.split(","); for (String d : a) { days.add(DayOfWeek.valueOf(d)); } } } public Weekly() { super(RecurInterval.WEEKLY); this.days = EnumSet.allOf(DayOfWeek.class); days.remove(DayOfWeek.SAT); days.remove(DayOfWeek.SUN); } @Override protected void toString(StringBuilder sb) { sb.append("days="); boolean appendComma = false; for (DayOfWeek d : days) { if (appendComma) { sb.append(","); } sb.append(d.name()); appendComma = true; } } public boolean isSet(DayOfWeek d) { return days.contains(d); } public void set(DayOfWeek d) { days.add(d); } public void unset(DayOfWeek d) { days.remove(d); } } public static class SemiMonthly extends Recur { public int firstDay; public int secondDay; protected SemiMonthly(HashMap<String, String> values) { super(RecurInterval.SEMI_MONTHLY, values); this.firstDay = getInt(values, "firstDay"); this.secondDay = getInt(values, "secondDay"); } public SemiMonthly() { super(RecurInterval.SEMI_MONTHLY); this.firstDay = 15; this.secondDay = 30; } @Override protected void toString(StringBuilder sb) { sb.append("firstDay=").append(firstDay).append(","); sb.append("secondDay=").append(secondDay).append(","); } } public static class Monthly extends Recur { protected Monthly(HashMap<String, String> values) { super(RecurInterval.MONTHLY, values); } public Monthly() { super(RecurInterval.MONTHLY); } } public static class Yearly extends Recur { protected Yearly(HashMap<String, String> values) { super(RecurInterval.YEARLY, values); } public Yearly() { super(RecurInterval.YEARLY); } } public static Recur createFromExtraString(String extra) { if (Utils.isEmpty(extra)) { return new NoRecur(); } String[] a = extra.split(","); RecurInterval interval = RecurInterval.valueOf(a[0]); HashMap<String, String> values = toMap(a); switch (interval) { case NO_RECUR: return new NoRecur(values); case EVERY_X_DAY: return new EveryXDay(values); case DAILY: return new Daily(values); case WEEKLY: return new Weekly(values); case SEMI_MONTHLY: return new SemiMonthly(values); case MONTHLY: return new Monthly(values); case YEARLY: return new Yearly(values); } return null; } public static Recur createRecur(RecurInterval interval) { switch (interval) { case NO_RECUR: return new NoRecur(); case EVERY_X_DAY: return new EveryXDay(); case DAILY: return new Daily(); case WEEKLY: return new Weekly(); case SEMI_MONTHLY: return new SemiMonthly(); case MONTHLY: return new Monthly(); case YEARLY: return new Yearly(); } return null; } private static HashMap<String, String> toMap(String[] a) { HashMap<String, String> map = new HashMap<String, String>(); for (String s : a) { String[] kv = s.split("="); if (kv.length > 1) { map.put(kv[0], kv[1]); } } return map; } private static long getLong(HashMap<String, String> values, String string) { return Long.parseLong(values.get(string)); } private static int getInt(HashMap<String, String> values, String string) { return Integer.parseInt(values.get(string)); } public static Recur createDefaultRecur() { Calendar c = Calendar.getInstance(); NoRecur m = new NoRecur(); m.startDate = DateUtils.startOfDay(c).getTimeInMillis(); m.period = RecurPeriod.STOPS_ON_DATE; c.add(Calendar.MONTH, 1); m.periodParam = DateUtils.endOfDay(c).getTimeInMillis(); return m; } public static Period[] periods(Recur recur) { RecurInterval interval = recur.interval; RecurPeriod period = recur.period; if (interval == RecurInterval.NO_RECUR) { if (period != RecurPeriod.STOPS_ON_DATE) { return new Period[]{PeriodType.THIS_MONTH.calculatePeriod()}; } return new Period[]{new Period(PeriodType.CUSTOM, recur.startDate, recur.periodParam)}; } else { return period.repeat(interval, recur.startDate, recur.periodParam); } } }