Skip to content

Commit 893132a

Browse files
authored
Merge pull request #680 from rivaldi8/bugfix-641-exports-weekday-ignored
Bugfix 641: Weekday is ignored in scheduled exports
2 parents 8087978 + 0a5af15 commit 893132a

3 files changed

Lines changed: 110 additions & 8 deletions

File tree

app/src/main/java/org/gnucash/android/model/ScheduledAction.java

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
import java.sql.Timestamp;
2626
import java.text.SimpleDateFormat;
27+
import java.util.Calendar;
2728
import java.util.Date;
2829
import java.util.Locale;
2930
import java.util.TimeZone;
@@ -224,7 +225,7 @@ private long computeNextScheduledExecutionTimeStartingAt(long startTime) {
224225
nextScheduledExecution = nextScheduledExecution.plusDays(multiplier);
225226
break;
226227
case WEEK:
227-
nextScheduledExecution = nextScheduledExecution.plusWeeks(multiplier);
228+
nextScheduledExecution = computeNextWeeklyExecutionStartingAt(nextScheduledExecution);
228229
break;
229230
case MONTH:
230231
nextScheduledExecution = nextScheduledExecution.plusMonths(multiplier);
@@ -236,6 +237,43 @@ private long computeNextScheduledExecutionTimeStartingAt(long startTime) {
236237
return nextScheduledExecution.toDate().getTime();
237238
}
238239

240+
/**
241+
* Computes the next time that this weekly scheduled action is supposed to be
242+
* executed starting at startTime.
243+
*
244+
* @param startTime LocalDateTime to use as start to compute the next schedule.
245+
*
246+
* @return Next run time as a LocalDateTime
247+
*/
248+
@NonNull
249+
private LocalDateTime computeNextWeeklyExecutionStartingAt(LocalDateTime startTime) {
250+
// Look into the week of startTime for another scheduled weekday
251+
for (int weekDay : mRecurrence.getByDays() ) {
252+
int jodaWeekDay = convertCalendarWeekdayToJoda(weekDay);
253+
LocalDateTime candidateNextDueTime = startTime.withDayOfWeek(jodaWeekDay);
254+
if (candidateNextDueTime.isAfter(startTime))
255+
return candidateNextDueTime;
256+
}
257+
258+
// Return the first scheduled weekday from the next due week
259+
int firstScheduledWeekday = convertCalendarWeekdayToJoda(mRecurrence.getByDays().get(0));
260+
return startTime.plusWeeks(mRecurrence.getMultiplier())
261+
.withDayOfWeek(firstScheduledWeekday);
262+
}
263+
264+
/**
265+
* Converts a java.util.Calendar weekday constant to the
266+
* org.joda.time.DateTimeConstants equivalent.
267+
*
268+
* @param calendarWeekday weekday constant from java.util.Calendar
269+
* @return weekday constant equivalent from org.joda.time.DateTimeConstants
270+
*/
271+
private int convertCalendarWeekdayToJoda(int calendarWeekday) {
272+
Calendar cal = Calendar.getInstance();
273+
cal.set(Calendar.DAY_OF_WEEK, calendarWeekday);
274+
return LocalDateTime.fromCalendarFields(cal).getDayOfWeek();
275+
}
276+
239277
/**
240278
* Set time of last execution of the scheduled action
241279
* @param nextRun Timestamp in milliseconds since Epoch

app/src/test/java/org/gnucash/android/test/unit/model/ScheduledActionTest.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,13 @@
2323
import org.junit.Test;
2424

2525
import java.sql.Timestamp;
26+
import java.util.Arrays;
2627
import java.util.Calendar;
28+
import java.util.Collections;
2729

2830
import static org.assertj.core.api.Assertions.assertThat;
31+
32+
2933
/**
3034
* Test scheduled actions
3135
*/
@@ -130,6 +134,48 @@ public void testComputingTimeOfLastSchedule(){
130134

131135
}
132136

137+
/**
138+
* Weekly actions scheduled to run on multiple weekdays should be due
139+
* in each of them in the same week.
140+
*
141+
* For an action scheduled on Mondays and Thursdays, we test that, if
142+
* the last run was on Monday, the next should be due on the Thursday
143+
* of the same week instead of the following week.
144+
*/
145+
@Test
146+
public void multiWeekdayWeeklyActions_shouldBeDueOnEachWeekdaySet() {
147+
ScheduledAction scheduledAction = new ScheduledAction(ScheduledAction.ActionType.BACKUP);
148+
Recurrence recurrence = new Recurrence(PeriodType.WEEK);
149+
recurrence.setByDays(Arrays.asList(Calendar.MONDAY, Calendar.THURSDAY));
150+
scheduledAction.setRecurrence(recurrence);
151+
scheduledAction.setStartTime(new DateTime(2016, 6, 6, 9, 0).getMillis());
152+
scheduledAction.setLastRun(new DateTime(2017, 4, 17, 9, 0).getMillis()); // Monday
153+
154+
long expectedNextDueDate = new DateTime(2017, 4, 20, 9, 0).getMillis(); // Thursday
155+
assertThat(scheduledAction.computeNextTimeBasedScheduledExecutionTime())
156+
.isEqualTo(expectedNextDueDate);
157+
}
158+
159+
/**
160+
* Weekly actions scheduled with multiplier should skip intermediate
161+
* weeks and be due in the specified weekday.
162+
*/
163+
@Test
164+
public void weeklyActionsWithMultiplier_shouldBeDueOnTheWeekdaySet() {
165+
ScheduledAction scheduledAction = new ScheduledAction(ScheduledAction.ActionType.BACKUP);
166+
Recurrence recurrence = new Recurrence(PeriodType.WEEK);
167+
recurrence.setMultiplier(2);
168+
recurrence.setByDays(Collections.singletonList(Calendar.WEDNESDAY));
169+
scheduledAction.setRecurrence(recurrence);
170+
scheduledAction.setStartTime(new DateTime(2016, 6, 6, 9, 0).getMillis());
171+
scheduledAction.setLastRun(new DateTime(2017, 4, 12, 9, 0).getMillis()); // Wednesday
172+
173+
// Wednesday, 2 weeks after the last run
174+
long expectedNextDueDate = new DateTime(2017, 4, 26, 9, 0).getMillis();
175+
assertThat(scheduledAction.computeNextTimeBasedScheduledExecutionTime())
176+
.isEqualTo(expectedNextDueDate);
177+
}
178+
133179
private long getTimeInMillis(int year, int month, int day) {
134180
Calendar calendar = Calendar.getInstance();
135181
calendar.set(year, month, day);

app/src/test/java/org/gnucash/android/test/unit/service/ScheduledActionServiceTest.java

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import org.gnucash.android.util.BookUtils;
4949
import org.gnucash.android.util.TimestampHelper;
5050
import org.joda.time.DateTime;
51+
import org.joda.time.DateTimeConstants;
5152
import org.joda.time.LocalDateTime;
5253
import org.joda.time.Weeks;
5354
import org.junit.After;
@@ -63,6 +64,8 @@
6364
import java.math.BigDecimal;
6465
import java.sql.Timestamp;
6566
import java.util.ArrayList;
67+
import java.util.Calendar;
68+
import java.util.Collections;
6669
import java.util.List;
6770

6871
import javax.xml.parsers.ParserConfigurationException;
@@ -199,8 +202,10 @@ public void missedScheduledTransactions_shouldBeGenerated(){
199202

200203
scheduledAction.setActionUID(mActionUID);
201204

202-
int multiplier = 2;
203-
scheduledAction.setRecurrence(PeriodType.WEEK, multiplier);
205+
Recurrence recurrence = new Recurrence(PeriodType.WEEK);
206+
recurrence.setMultiplier(2);
207+
recurrence.setByDays(Collections.singletonList(Calendar.MONDAY));
208+
scheduledAction.setRecurrence(recurrence);
204209
ScheduledActionDbAdapter.getInstance().addRecord(scheduledAction, DatabaseAdapter.UpdateMethod.insert);
205210

206211
TransactionsDbAdapter transactionsDbAdapter = TransactionsDbAdapter.getInstance();
@@ -249,7 +254,10 @@ public void scheduledTransactionsWithEndTimeInPast_shouldBeExecuted(){
249254
scheduledAction.setStartTime(startTime.getMillis());
250255
scheduledAction.setActionUID(mActionUID);
251256

252-
scheduledAction.setRecurrence(PeriodType.WEEK, 2);
257+
Recurrence recurrence = new Recurrence(PeriodType.WEEK);
258+
recurrence.setMultiplier(2);
259+
recurrence.setByDays(Collections.singletonList(Calendar.MONDAY));
260+
scheduledAction.setRecurrence(recurrence);
253261
scheduledAction.setEndTime(new DateTime(2016, 8, 8, 9, 0).getMillis());
254262
ScheduledActionDbAdapter.getInstance().addRecord(scheduledAction, DatabaseAdapter.UpdateMethod.insert);
255263

@@ -345,11 +353,15 @@ public void scheduledBackups_shouldRunOnlyOnce(){
345353
@Test
346354
public void scheduledBackups_shouldNotRunBeforeNextScheduledExecution(){
347355
ScheduledAction scheduledBackup = new ScheduledAction(ScheduledAction.ActionType.BACKUP);
348-
scheduledBackup.setStartTime(LocalDateTime.now().minusDays(2).toDate().getTime());
356+
scheduledBackup.setStartTime(
357+
LocalDateTime.now().withDayOfWeek(DateTimeConstants.WEDNESDAY).toDate().getTime());
349358
scheduledBackup.setLastRun(scheduledBackup.getStartTime());
350359
long previousLastRun = scheduledBackup.getLastRunTime();
351360
scheduledBackup.setExecutionCount(1);
352-
scheduledBackup.setRecurrence(PeriodType.WEEK, 1);
361+
Recurrence recurrence = new Recurrence(PeriodType.WEEK);
362+
recurrence.setMultiplier(1);
363+
recurrence.setByDays(Collections.singletonList(Calendar.MONDAY));
364+
scheduledBackup.setRecurrence(recurrence);
353365

354366
ExportParams backupParams = new ExportParams(ExportFormat.XML);
355367
backupParams.setExportTarget(ExportParams.ExportTarget.SD_CARD);
@@ -380,7 +392,10 @@ public void scheduledBackups_shouldNotIncludeTransactionsPreviousToTheLastRun()
380392
scheduledBackup.setLastRun(LocalDateTime.now().minusDays(8).toDate().getTime());
381393
long previousLastRun = scheduledBackup.getLastRunTime();
382394
scheduledBackup.setExecutionCount(1);
383-
scheduledBackup.setRecurrence(PeriodType.WEEK, 1);
395+
Recurrence recurrence = new Recurrence(PeriodType.WEEK);
396+
recurrence.setMultiplier(1);
397+
recurrence.setByDays(Collections.singletonList(Calendar.WEDNESDAY));
398+
scheduledBackup.setRecurrence(recurrence);
384399
ExportParams backupParams = new ExportParams(ExportFormat.QIF);
385400
backupParams.setExportTarget(ExportParams.ExportTarget.SD_CARD);
386401
backupParams.setExportStartTime(new Timestamp(scheduledBackup.getStartTime()));
@@ -438,7 +453,10 @@ public void scheduledBackups_shouldIncludeTransactionsAfterTheLastRun() {
438453
scheduledBackup.setLastRun(LocalDateTime.now().minusDays(8).toDate().getTime());
439454
long previousLastRun = scheduledBackup.getLastRunTime();
440455
scheduledBackup.setExecutionCount(1);
441-
scheduledBackup.setRecurrence(PeriodType.WEEK, 1);
456+
Recurrence recurrence = new Recurrence(PeriodType.WEEK);
457+
recurrence.setMultiplier(1);
458+
recurrence.setByDays(Collections.singletonList(Calendar.FRIDAY));
459+
scheduledBackup.setRecurrence(recurrence);
442460
ExportParams backupParams = new ExportParams(ExportFormat.QIF);
443461
backupParams.setExportTarget(ExportParams.ExportTarget.SD_CARD);
444462
backupParams.setExportStartTime(new Timestamp(scheduledBackup.getStartTime()));

0 commit comments

Comments
 (0)