Use ical4j in RepeatRuleToString

pull/1340/head
Alex Baker 5 years ago
parent cc09150141
commit e6fbb92912

@ -1,16 +1,25 @@
package org.tasks.repeats package org.tasks.repeats
import androidx.test.InstrumentationRegistry import com.todoroo.astrid.data.Task.Companion.withoutRRULE
import androidx.test.ext.junit.runners.AndroidJUnit4 import dagger.hilt.android.testing.HiltAndroidTest
import com.google.ical.values.RRule import dagger.hilt.android.testing.UninstallModules
import net.fortuna.ical4j.model.property.RRule
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.tasks.TestUtilities.withTZ
import org.tasks.analytics.Firebase
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.locale.Locale import org.tasks.locale.Locale
import java.text.ParseException import java.text.ParseException
import java.util.*
import javax.inject.Inject
@UninstallModules(ProductionModule::class)
@HiltAndroidTest
class RepeatRuleToStringTest : InjectingTestCase() {
@Inject lateinit var firebase: Firebase
@RunWith(AndroidJUnit4::class)
class RepeatRuleToStringTest {
@Test @Test
fun daily() { fun daily() {
assertEquals("Repeats daily", toString("RRULE:FREQ=DAILY")) assertEquals("Repeats daily", toString("RRULE:FREQ=DAILY"))
@ -47,6 +56,60 @@ class RepeatRuleToStringTest {
toString("de", "RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=SA,SU")) toString("de", "RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=SA,SU"))
} }
@Test
fun everyFifthTuesday() {
assertEquals(
"Repeats monthly on every fifth Tuesday",
toString("RRULE:FREQ=MONTHLY;INTERVAL=1;BYDAY=5TU")
)
}
@Test
fun everyLastWednesday() {
assertEquals(
"Repeats monthly on every last Wednesday",
toString("RRULE:FREQ=MONTHLY;INTERVAL=1;BYDAY=-1WE")
)
}
@Test
fun everyFirstThursday() {
assertEquals(
"Repeats every 2 months on every first Thursday",
toString("RRULE:FREQ=MONTHLY;INTERVAL=2;BYDAY=1TH")
)
}
@Test
fun repeatUntilPositiveOffset() {
withTZ(BERLIN) {
assertEquals(
"Repeats daily until February 23",
toString("RRULE:FREQ=DAILY;UNTIL=20210223;INTERVAL=1")
)
}
}
@Test
fun repeatUntilNoOffset() {
withTZ(LONDON) {
assertEquals(
"Repeats daily until February 23",
toString("RRULE:FREQ=DAILY;UNTIL=20210223;INTERVAL=1")
)
}
}
@Test
fun repeatUntilNegativeOffset() {
withTZ(NEW_YORK) {
assertEquals(
"Repeats daily until February 23",
toString("RRULE:FREQ=DAILY;UNTIL=20210223;INTERVAL=1")
)
}
}
private fun toString(rrule: String): String { private fun toString(rrule: String): String {
return toString(null, rrule) return toString(null, rrule)
} }
@ -54,10 +117,16 @@ class RepeatRuleToStringTest {
private fun toString(language: String?, rrule: String): String { private fun toString(language: String?, rrule: String): String {
return try { return try {
val locale = Locale(java.util.Locale.getDefault(), language) val locale = Locale(java.util.Locale.getDefault(), language)
RepeatRuleToString(locale.createConfigurationContext(InstrumentationRegistry.getTargetContext()), locale) RepeatRuleToString(locale.createConfigurationContext(context), locale, firebase)
.toString(RRule(rrule)) .toString(RRule(rrule.withoutRRULE()))
} catch (e: ParseException) { } catch (e: ParseException) {
throw RuntimeException(e) throw RuntimeException(e)
} }
} }
companion object {
private val BERLIN = TimeZone.getTimeZone("Europe/Berlin")
private val LONDON = TimeZone.getTimeZone("Europe/London")
private val NEW_YORK = TimeZone.getTimeZone("America/New_York")
}
} }

@ -614,5 +614,8 @@ class Task : Parcelable {
fun String?.isRepeatAfterCompletion() = this?.contains("FROM=COMPLETION") ?: false fun String?.isRepeatAfterCompletion() = this?.contains("FROM=COMPLETION") ?: false
fun String?.withoutFrom(): String? = this?.replace(";?FROM=[^;]*".toRegex(), "") fun String?.withoutFrom(): String? = this?.replace(";?FROM=[^;]*".toRegex(), "")
@JvmStatic
fun String?.withoutRRULE(): String? = this?.replace("^RRULE:".toRegex(), "")
} }
} }

@ -145,7 +145,7 @@ class RepeatControlSet : TaskEditControlFragment() {
displayView.text = null displayView.text = null
repeatTypeContainer.visibility = View.GONE repeatTypeContainer.visibility = View.GONE
} else { } else {
displayView.text = repeatRuleToString.toString(it) displayView.text = repeatRuleToString.toString(it.toIcal())
repeatTypeContainer.visibility = View.VISIBLE repeatTypeContainer.visibility = View.VISIBLE
} }
} }

@ -1,6 +1,7 @@
package org.tasks.caldav; package org.tasks.caldav;
import static com.todoroo.andlib.utility.DateUtilities.now; import static com.todoroo.andlib.utility.DateUtilities.now;
import static com.todoroo.astrid.data.Task.withoutRRULE;
import static org.tasks.caldav.iCalendar.getLocal; import static org.tasks.caldav.iCalendar.getLocal;
import static org.tasks.date.DateTimeUtils.newDateTime; import static org.tasks.date.DateTimeUtils.newDateTime;
import static org.tasks.time.DateTime.UTC; import static org.tasks.time.DateTime.UTC;
@ -102,7 +103,7 @@ public class CaldavConverter {
} }
if (task.isRecurring()) { if (task.isRecurring()) {
try { try {
RRule rrule = new RRule(task.getRecurrenceWithoutFrom().replace("RRULE:", "")); RRule rrule = new RRule(withoutRRULE(task.getRecurrenceWithoutFrom()));
long repeatUntil = task.getRepeatUntil(); long repeatUntil = task.getRepeatUntil();
rrule rrule
.getRecur() .getRecur()

@ -202,7 +202,7 @@ class iCalendar @Inject constructor(
dt.timeZone ?: if (dt.isUtc) UTC else TimeZone.getDefault() dt.timeZone ?: if (dt.isUtc) UTC else TimeZone.getDefault()
) )
} else { } else {
org.tasks.time.DateTime(property.date.time).let { it.minusMillis(it.offset) } org.tasks.time.DateTime.from(property.date)
} }
return dateTime?.toLocal()?.millis ?: 0 return dateTime?.toLocal()?.millis ?: 0
} }

@ -225,7 +225,7 @@ class TaskDefaults : InjectingPreferenceFragment() {
?.takeIf { it.isNotBlank() } ?.takeIf { it.isNotBlank() }
?.let { ?.let {
try { try {
repeatRuleToString.toString(RRule(it)) repeatRuleToString.toString(it)
} catch (e: Exception) { } catch (e: Exception) {
null null
} }

@ -77,7 +77,7 @@ public class BasicRecurrenceDialog extends DialogFragment {
new SingleCheckedArrayAdapter(context, repeatOptions); new SingleCheckedArrayAdapter(context, repeatOptions);
int selected = 0; int selected = 0;
if (customPicked) { if (customPicked) {
adapter.insert(repeatRuleToString.toString(rrule), 0); adapter.insert(repeatRuleToString.toString(rule), 0);
} else if (rrule != null) { } else if (rrule != null) {
switch (rrule.getFreq()) { switch (rrule.getFreq()) {
case DAILY: case DAILY:

@ -1,23 +1,27 @@
package org.tasks.repeats; package org.tasks.repeats;
import static com.google.ical.values.Frequency.MONTHLY; import static com.todoroo.astrid.data.Task.withoutRRULE;
import static com.google.ical.values.Frequency.WEEKLY; import static net.fortuna.ical4j.model.Recur.Frequency.MONTHLY;
import static net.fortuna.ical4j.model.Recur.Frequency.WEEKLY;
import android.content.Context; import android.content.Context;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import com.google.ical.values.Frequency;
import com.google.ical.values.RRule;
import com.google.ical.values.Weekday;
import com.google.ical.values.WeekdayNum;
import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.andlib.utility.DateUtilities;
import dagger.hilt.android.qualifiers.ApplicationContext; import dagger.hilt.android.qualifiers.ApplicationContext;
import java.text.DateFormatSymbols; import java.text.DateFormatSymbols;
import java.text.ParseException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Calendar; import java.util.Calendar;
import java.util.List; import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import net.fortuna.ical4j.model.Recur;
import net.fortuna.ical4j.model.Recur.Frequency;
import net.fortuna.ical4j.model.WeekDay;
import net.fortuna.ical4j.model.WeekDay.Day;
import net.fortuna.ical4j.model.property.RRule;
import org.tasks.R; import org.tasks.R;
import org.tasks.analytics.Firebase;
import org.tasks.locale.Locale; import org.tasks.locale.Locale;
import org.tasks.time.DateTime; import org.tasks.time.DateTime;
@ -25,24 +29,40 @@ public class RepeatRuleToString {
private final Context context; private final Context context;
private final Locale locale; private final Locale locale;
private final List<Weekday> weekdays = Arrays.asList(Weekday.values()); private final Firebase firebase;
private final List<Day> weekdays = Arrays.asList(Day.values());
@Inject @Inject
public RepeatRuleToString(@ApplicationContext Context context, Locale locale) { public RepeatRuleToString(
@ApplicationContext Context context,
Locale locale,
Firebase firebase
) {
this.context = context; this.context = context;
this.locale = locale; this.locale = locale;
this.firebase = firebase;
} }
public String toString(RRule rrule) { public String toString(String rrule) {
try {
return toString(new RRule(withoutRRULE(rrule)));
} catch (ParseException e) {
firebase.reportException(e);
}
return null;
}
public String toString(RRule r) {
Recur rrule = r.getRecur();
int interval = rrule.getInterval(); int interval = rrule.getInterval();
Frequency frequency = rrule.getFreq(); Frequency frequency = rrule.getFrequency();
DateTime repeatUntil = rrule.getUntil() == null ? null : DateTime.from(rrule.getUntil()); DateTime repeatUntil = rrule.getUntil() == null ? null : DateTime.from(rrule.getUntil());
int count = rrule.getCount(); int count = rrule.getCount();
String countString = String countString =
count > 0 ? context.getResources().getQuantityString(R.plurals.repeat_times, count) : ""; count > 0 ? context.getResources().getQuantityString(R.plurals.repeat_times, count) : "";
if (interval <= 1) { if (interval <= 1) {
String frequencyString = context.getString(getSingleFrequencyResource(frequency)); String frequencyString = context.getString(getSingleFrequencyResource(frequency));
if ((frequency == WEEKLY || frequency == MONTHLY) && !rrule.getByDay().isEmpty()) { if ((frequency == WEEKLY || frequency == MONTHLY) && !rrule.getDayList().isEmpty()) {
String dayString = getDayString(rrule); String dayString = getDayString(rrule);
if (count > 0) { if (count > 0) {
return context.getString( return context.getString(
@ -74,7 +94,7 @@ public class RepeatRuleToString {
} else { } else {
int plural = getFrequencyPlural(frequency); int plural = getFrequencyPlural(frequency);
String frequencyPlural = context.getResources().getQuantityString(plural, interval, interval); String frequencyPlural = context.getResources().getQuantityString(plural, interval, interval);
if ((frequency == WEEKLY || frequency == MONTHLY) && !rrule.getByDay().isEmpty()) { if ((frequency == WEEKLY || frequency == MONTHLY) && !rrule.getDayList().isEmpty()) {
String dayString = getDayString(rrule); String dayString = getDayString(rrule);
if (count > 0) { if (count > 0) {
return context.getString( return context.getString(
@ -106,23 +126,23 @@ public class RepeatRuleToString {
} }
} }
private String getDayString(RRule rrule) { private String getDayString(Recur rrule) {
DateFormatSymbols dfs = new DateFormatSymbols(locale.getLocale()); DateFormatSymbols dfs = new DateFormatSymbols(locale.getLocale());
if (rrule.getFreq() == WEEKLY) { if (rrule.getFrequency() == WEEKLY) {
String[] shortWeekdays = dfs.getShortWeekdays(); String[] shortWeekdays = dfs.getShortWeekdays();
List<String> days = new ArrayList<>(); List<String> days = new ArrayList<>();
for (WeekdayNum weekday : rrule.getByDay()) { for (WeekDay weekday : rrule.getDayList()) {
days.add(shortWeekdays[weekdays.indexOf(weekday.wday) + 1]); days.add(shortWeekdays[weekdays.indexOf(weekday.getDay()) + 1]);
} }
return Joiner.on(context.getString(R.string.list_separator_with_space)).join(days); return Joiner.on(context.getString(R.string.list_separator_with_space)).join(days);
} else if (rrule.getFreq() == MONTHLY) { } else if (rrule.getFrequency() == MONTHLY) {
String[] longWeekdays = dfs.getWeekdays(); String[] longWeekdays = dfs.getWeekdays();
WeekdayNum weekdayNum = rrule.getByDay().get(0); WeekDay weekdayNum = rrule.getDayList().get(0);
String weekday; String weekday;
Calendar dayOfWeekCalendar = Calendar.getInstance(locale.getLocale()); Calendar dayOfWeekCalendar = Calendar.getInstance(locale.getLocale());
dayOfWeekCalendar.set(Calendar.DAY_OF_WEEK, weekdayToCalendarDay(weekdayNum.wday)); dayOfWeekCalendar.set(Calendar.DAY_OF_WEEK, weekdayToCalendarDay(weekdayNum.getDay()));
weekday = longWeekdays[dayOfWeekCalendar.get(Calendar.DAY_OF_WEEK)]; weekday = longWeekdays[dayOfWeekCalendar.get(Calendar.DAY_OF_WEEK)];
if (weekdayNum.num == -1) { if (weekdayNum.getOffset() == -1) {
return context.getString( return context.getString(
R.string.repeat_monthly_every_day_of_nth_week, R.string.repeat_monthly_every_day_of_nth_week,
context.getString(R.string.repeat_monthly_last_week), context.getString(R.string.repeat_monthly_last_week),
@ -138,7 +158,7 @@ public class RepeatRuleToString {
}; };
return context.getString( return context.getString(
R.string.repeat_monthly_every_day_of_nth_week, R.string.repeat_monthly_every_day_of_nth_week,
context.getString(resources[weekdayNum.num - 1]), context.getString(resources[weekdayNum.getOffset() - 1]),
weekday); weekday);
} }
} else { } else {
@ -146,7 +166,7 @@ public class RepeatRuleToString {
} }
} }
private int weekdayToCalendarDay(Weekday weekday) { private int weekdayToCalendarDay(Day weekday) {
switch (weekday) { switch (weekday) {
case SU: case SU:
return Calendar.SUNDAY; return Calendar.SUNDAY;

@ -85,6 +85,11 @@ public class DateTime {
this(calendar.getTimeInMillis(), calendar.getTimeZone()); this(calendar.getTimeInMillis(), calendar.getTimeZone());
} }
public static DateTime from(Date date) {
DateTime dateTime = new DateTime(date.getTime());
return dateTime.minusMillis(dateTime.getOffset());
}
public static DateTime from(DateValue dateValue) { public static DateTime from(DateValue dateValue) {
if (dateValue == null) { if (dateValue == null) {
return new DateTime(0); return new DateTime(0);

Loading…
Cancel
Save