Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix 641: Weekday is ignored in scheduled exports #680

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion app/src/main/java/org/gnucash/android/model/ScheduledAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
Expand Down Expand Up @@ -224,7 +225,7 @@ private long computeNextScheduledExecutionTimeStartingAt(long startTime) {
nextScheduledExecution = nextScheduledExecution.plusDays(multiplier);
break;
case WEEK:
nextScheduledExecution = nextScheduledExecution.plusWeeks(multiplier);
nextScheduledExecution = computeNextWeeklyExecutionStartingAt(nextScheduledExecution);
break;
case MONTH:
nextScheduledExecution = nextScheduledExecution.plusMonths(multiplier);
Expand All @@ -236,6 +237,43 @@ private long computeNextScheduledExecutionTimeStartingAt(long startTime) {
return nextScheduledExecution.toDate().getTime();
}

/**
* Computes the next time that this weekly scheduled action is supposed to be
* executed starting at startTime.
*
* @param startTime LocalDateTime to use as start to compute the next schedule.
*
* @return Next run time as a LocalDateTime
*/
@NonNull
private LocalDateTime computeNextWeeklyExecutionStartingAt(LocalDateTime startTime) {
// Look into the week of startTime for another scheduled weekday
for (int weekDay : mRecurrence.getByDays() ) {
int jodaWeekDay = convertCalendarWeekdayToJoda(weekDay);
LocalDateTime candidateNextDueTime = startTime.withDayOfWeek(jodaWeekDay);
if (candidateNextDueTime.isAfter(startTime))
return candidateNextDueTime;
}

// Return the first scheduled weekday from the next due week
int firstScheduledWeekday = convertCalendarWeekdayToJoda(mRecurrence.getByDays().get(0));
return startTime.plusWeeks(mRecurrence.getMultiplier())
.withDayOfWeek(firstScheduledWeekday);
}

/**
* Converts a java.util.Calendar weekday constant to the
* org.joda.time.DateTimeConstants equivalent.
*
* @param calendarWeekday weekday constant from java.util.Calendar
* @return weekday constant equivalent from org.joda.time.DateTimeConstants
*/
private int convertCalendarWeekdayToJoda(int calendarWeekday) {
Calendar cal = Calendar.getInstance();
cal.set(Calendar.DAY_OF_WEEK, calendarWeekday);
return LocalDateTime.fromCalendarFields(cal).getDayOfWeek();
}

/**
* Set time of last execution of the scheduled action
* @param nextRun Timestamp in milliseconds since Epoch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,13 @@
import org.junit.Test;

import java.sql.Timestamp;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;

import static org.assertj.core.api.Assertions.assertThat;


/**
* Test scheduled actions
*/
Expand Down Expand Up @@ -130,6 +134,48 @@ public void testComputingTimeOfLastSchedule(){

}

/**
* Weekly actions scheduled to run on multiple weekdays should be due
* in each of them in the same week.
*
* For an action scheduled on Mondays and Thursdays, we test that, if
* the last run was on Monday, the next should be due on the Thursday
* of the same week instead of the following week.
*/
@Test
public void multiWeekdayWeeklyActions_shouldBeDueOnEachWeekdaySet() {
ScheduledAction scheduledAction = new ScheduledAction(ScheduledAction.ActionType.BACKUP);
Recurrence recurrence = new Recurrence(PeriodType.WEEK);
recurrence.setByDays(Arrays.asList(Calendar.MONDAY, Calendar.THURSDAY));
scheduledAction.setRecurrence(recurrence);
scheduledAction.setStartTime(new DateTime(2016, 6, 6, 9, 0).getMillis());
scheduledAction.setLastRun(new DateTime(2017, 4, 17, 9, 0).getMillis()); // Monday

long expectedNextDueDate = new DateTime(2017, 4, 20, 9, 0).getMillis(); // Thursday
assertThat(scheduledAction.computeNextTimeBasedScheduledExecutionTime())
.isEqualTo(expectedNextDueDate);
}

/**
* Weekly actions scheduled with multiplier should skip intermediate
* weeks and be due in the specified weekday.
*/
@Test
public void weeklyActionsWithMultiplier_shouldBeDueOnTheWeekdaySet() {
ScheduledAction scheduledAction = new ScheduledAction(ScheduledAction.ActionType.BACKUP);
Recurrence recurrence = new Recurrence(PeriodType.WEEK);
recurrence.setMultiplier(2);
recurrence.setByDays(Collections.singletonList(Calendar.WEDNESDAY));
scheduledAction.setRecurrence(recurrence);
scheduledAction.setStartTime(new DateTime(2016, 6, 6, 9, 0).getMillis());
scheduledAction.setLastRun(new DateTime(2017, 4, 12, 9, 0).getMillis()); // Wednesday

// Wednesday, 2 weeks after the last run
long expectedNextDueDate = new DateTime(2017, 4, 26, 9, 0).getMillis();
assertThat(scheduledAction.computeNextTimeBasedScheduledExecutionTime())
.isEqualTo(expectedNextDueDate);
}

private long getTimeInMillis(int year, int month, int day) {
Calendar calendar = Calendar.getInstance();
calendar.set(year, month, day);
Expand Down