diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Task.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Task.kt index 60bbf5406..12b69613f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Task.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Task.kt @@ -368,12 +368,13 @@ open class Task : RealmObject, BaseMainObject, Parcelable, BaseTask { */ fun getNextReminderOccurrences( remindersItem: RemindersItem?, - occurrences: Int + occurrences: Int, + today: ZonedDateTime? = null ): List? { if (remindersItem == null) return null val reminderTime = remindersItem.time?.parseToZonedDateTime() ?: return null - var dateTimeOccurenceToSchedule: ZonedDateTime + var nextOccurence: ZonedDateTime val occurrencesList = mutableListOf() // If the reminder is a todo, only schedule sole dueDate/time occurrence. Otherwise, schedule multiple occurrences in advance @@ -385,7 +386,7 @@ open class Task : RealmObject, BaseMainObject, Parcelable, BaseTask { ) return occurrencesList } - val now = ZonedDateTime.now().withZoneSameInstant(ZoneId.systemDefault()) + val now = today ?: ZonedDateTime.now().withZoneSameInstant(ZoneId.systemDefault()) var startDate = this.startDate?.toInstant()?.atZone(ZoneId.systemDefault()) ?: return null val weekInMonth = getWeeksOfMonth()?.firstOrNull()?.toLong() val weekdayInMonth = startDate.dayOfWeek @@ -396,52 +397,52 @@ open class Task : RealmObject, BaseMainObject, Parcelable, BaseTask { } val repeatDays = this.repeat startDate = startDate.withHour(reminderTime.hour).withMinute(reminderTime.minute) - dateTimeOccurenceToSchedule = startDate + nextOccurence = startDate if (frequency == Frequency.MONTHLY) { daysOfMonth?.let { if (it.isEmpty()) return@let - dateTimeOccurenceToSchedule = dateTimeOccurenceToSchedule.withDayOfMonth(it.first()) - if (dateTimeOccurenceToSchedule.isBefore(startDate)) { - dateTimeOccurenceToSchedule = dateTimeOccurenceToSchedule.plusMonths(1) + nextOccurence = nextOccurence.withDayOfMonth(it.first()) + if (nextOccurence.isBefore(startDate)) { + nextOccurence = nextOccurence.plusMonths(1) } } } while (occurrencesList.size < occurrences) { // Increment currentDate based on the frequency - dateTimeOccurenceToSchedule = + nextOccurence = when (frequency) { Frequency.DAILY -> { - while (dateTimeOccurenceToSchedule.isBefore(now)) { - dateTimeOccurenceToSchedule = dateTimeOccurenceToSchedule.plusDays(everyX) + while (nextOccurence.isBefore(now)) { + nextOccurence = nextOccurence.plusDays(everyX) } - val todayWithTime = dateTimeOccurenceToSchedule + val todayWithTime = nextOccurence .withHour(reminderTime.hour).withMinute(reminderTime.minute) - dateTimeOccurenceToSchedule = if (occurrencesList.isEmpty() && todayWithTime.isAfter(now)) { + nextOccurence = if (occurrencesList.isEmpty() && todayWithTime.isAfter(now)) { todayWithTime } else { - dateTimeOccurenceToSchedule.plusDays(everyX) + nextOccurence.plusDays(everyX) .withHour(reminderTime.hour).withMinute(reminderTime.minute) } - dateTimeOccurenceToSchedule + nextOccurence } Frequency.WEEKLY -> { // Set to start date if current date is earlier if (repeatDays?.hasAnyDaySelected() == true) { - if (dateTimeOccurenceToSchedule.isBefore(now)) { - dateTimeOccurenceToSchedule = - dateTimeOccurenceToSchedule.plusDays(1) - while (!dateTimeOccurenceToSchedule.matchesRepeatDays(repeatDays)) { - dateTimeOccurenceToSchedule = - dateTimeOccurenceToSchedule.plusDays(1) + if (nextOccurence.isBefore(now)) { + var occurenceNowWeekday = nextOccurence.minusDays((nextOccurence.dayOfWeek.value - now.dayOfWeek.value).toLong()) + while (occurenceNowWeekday.isBefore(now) && occurenceNowWeekday.dayOfYear != now.dayOfYear) { + occurenceNowWeekday = occurenceNowWeekday.plusDays(everyX * 7) + } + nextOccurence = occurenceNowWeekday + while (!nextOccurence.matchesRepeatDays(repeatDays)) { + nextOccurence = + nextOccurence.plusDays(1) } } - while (dateTimeOccurenceToSchedule.isBefore(now)) { - dateTimeOccurenceToSchedule = dateTimeOccurenceToSchedule.plusDays(everyX * 7) - } var nextDueDate = - dateTimeOccurenceToSchedule.withHour(reminderTime.hour) + nextOccurence.withHour(reminderTime.hour) .withMinute(reminderTime.minute) // If the next due date already happened for today, increment it by one day. Otherwise, it will be scheduled for today. if (nextDueDate.isBefore(now) && occurrencesList.size == 0) { @@ -475,58 +476,58 @@ open class Task : RealmObject, BaseMainObject, Parcelable, BaseTask { } } - dateTimeOccurenceToSchedule = nextDueDate + nextOccurence = nextDueDate } // Set time to the reminder time - dateTimeOccurenceToSchedule.withHour(reminderTime.hour) + nextOccurence.withHour(reminderTime.hour) .withMinute(reminderTime.minute) } Frequency.MONTHLY -> { - while (dateTimeOccurenceToSchedule.isBefore(now)) { - dateTimeOccurenceToSchedule = dateTimeOccurenceToSchedule.plusMonths(everyX) + while (nextOccurence.isBefore(now)) { + nextOccurence = nextOccurence.plusMonths(everyX) if (weekInMonth != null && weekdayInMonth != null) { - dateTimeOccurenceToSchedule = dateTimeOccurenceToSchedule.withDayOfMonth(1) - while (dateTimeOccurenceToSchedule.dayOfWeek != weekdayInMonth) { - dateTimeOccurenceToSchedule = dateTimeOccurenceToSchedule.plusDays(1) + nextOccurence = nextOccurence.withDayOfMonth(1) + while (nextOccurence.dayOfWeek != weekdayInMonth) { + nextOccurence = nextOccurence.plusDays(1) } - dateTimeOccurenceToSchedule = dateTimeOccurenceToSchedule.plusWeeks(weekInMonth) + nextOccurence = nextOccurence.plusWeeks(weekInMonth) } } - val todayWithTime = dateTimeOccurenceToSchedule + val todayWithTime = nextOccurence .withHour(reminderTime.hour).withMinute(reminderTime.minute) if (occurrencesList.isEmpty() && todayWithTime.isAfter(now)) { todayWithTime } else if (weekInMonth != null && weekdayInMonth != null) { - dateTimeOccurenceToSchedule = dateTimeOccurenceToSchedule.plusMonths(everyX) + nextOccurence = nextOccurence.plusMonths(everyX) .withHour(reminderTime.hour).withMinute(reminderTime.minute) .withDayOfMonth(1) - while (dateTimeOccurenceToSchedule.dayOfWeek != weekdayInMonth) { - dateTimeOccurenceToSchedule = dateTimeOccurenceToSchedule.plusDays(1) + while (nextOccurence.dayOfWeek != weekdayInMonth) { + nextOccurence = nextOccurence.plusDays(1) } - dateTimeOccurenceToSchedule.plusWeeks(weekInMonth) + nextOccurence.plusWeeks(weekInMonth) } else { - dateTimeOccurenceToSchedule.plusMonths(everyX) + nextOccurence.plusMonths(everyX) .withHour(reminderTime.hour).withMinute(reminderTime.minute) } } Frequency.YEARLY -> { - while (dateTimeOccurenceToSchedule.isBefore(now)) { - dateTimeOccurenceToSchedule = dateTimeOccurenceToSchedule.plusYears(everyX) + while (nextOccurence.isBefore(now)) { + nextOccurence = nextOccurence.plusYears(everyX) } - val todayWithTime = dateTimeOccurenceToSchedule + val todayWithTime = nextOccurence .withHour(reminderTime.hour).withMinute(reminderTime.minute) if (occurrencesList.isEmpty() && todayWithTime.isAfter(now)) { todayWithTime } else { - dateTimeOccurenceToSchedule.plusYears(everyX) + nextOccurence.plusYears(everyX) .withHour(reminderTime.hour).withMinute(reminderTime.minute) } } } - occurrencesList.add(dateTimeOccurenceToSchedule) + occurrencesList.add(nextOccurence) } return occurrencesList diff --git a/Habitica/src/test/java/com/habitrpg/android/habitica/models/tasks/TaskTest.kt b/Habitica/src/test/java/com/habitrpg/android/habitica/models/tasks/TaskTest.kt index 89285e212..2eab46fd2 100644 --- a/Habitica/src/test/java/com/habitrpg/android/habitica/models/tasks/TaskTest.kt +++ b/Habitica/src/test/java/com/habitrpg/android/habitica/models/tasks/TaskTest.kt @@ -8,286 +8,304 @@ import io.kotest.core.spec.style.WordSpec import io.kotest.matchers.shouldBe import java.time.format.DateTimeFormatter import java.util.Calendar +import java.util.Date class TaskTest : WordSpec({ - "getNextReminderOccurrences" When { - var daily = Task() - var reminder = RemindersItem() - var calendar = Calendar.getInstance() - beforeEach { - daily = Task() - daily.type = TaskType.DAILY - - reminder = RemindersItem() - - calendar = Calendar.getInstance() - } - "dailies repeating daily" should { + listOf( + Date(), + Calendar.getInstance().apply { + set(2025, 1, 1) + }.time, + Calendar.getInstance().apply { + set(2024, Calendar.DECEMBER, 23) + }.time + ).forEach { day -> + "getNextReminderOccurrences on $day" When { + var daily = Task() + var reminder = RemindersItem() + var calendar = Calendar.getInstance() + calendar.time = day + val fakeZDT = day.toZonedDateTime() beforeEach { - daily.frequency = Frequency.DAILY + daily = Task() + daily.type = TaskType.DAILY + + reminder = RemindersItem() + + calendar = Calendar.getInstance() + calendar.time = day } - "return occurrences according to everyX = 2" { - calendar.add(Calendar.DATE, -2) - daily.startDate = calendar.time - daily.everyX = 2 - calendar.add(Calendar.DATE, 4) - reminder.time = calendar.time.toZonedDateTime()?.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) - - val occurrences = daily.getNextReminderOccurrences(reminder, 4) - occurrences?.size shouldBe 4 - occurrences?.forEach { - it.dayOfYear shouldBe calendar.get(Calendar.DAY_OF_YEAR) - calendar.add(Calendar.DATE, 2) + "dailies repeating daily" should { + beforeEach { + daily.frequency = Frequency.DAILY } - } + "return occurrences according to everyX = 2" { + calendar.add(Calendar.DATE, -2) + daily.startDate = calendar.time + daily.everyX = 2 + calendar.add(Calendar.DATE, 4) + reminder.time = fakeZDT?.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) - "return occurrences according to everyX = 1" { - calendar.add(Calendar.DATE, -2) - daily.startDate = calendar.time - daily.everyX = 1 - calendar.add(Calendar.DATE, 3) - reminder.time = calendar.time.toZonedDateTime()?.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) - - val occurrences = daily.getNextReminderOccurrences(reminder, 4) - occurrences?.size shouldBe 4 - occurrences?.forEach { - it.dayOfYear shouldBe calendar.get(Calendar.DAY_OF_YEAR) - calendar.add(Calendar.DATE, 1) + val occurrences = daily.getNextReminderOccurrences(reminder, 4, fakeZDT) + occurrences?.size shouldBe 4 + occurrences?.forEach { + it.dayOfYear shouldBe calendar.get(Calendar.DAY_OF_YEAR) + calendar.add(Calendar.DATE, 2) + } } - } - "return occurrences according to everyX = 5" { - calendar.add(Calendar.DATE, -33) - daily.startDate = calendar.time - daily.everyX = 5 - reminder.time = calendar.time.toZonedDateTime()?.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + "return occurrences according to everyX = 1" { + calendar.add(Calendar.DATE, -2) + daily.startDate = calendar.time + daily.everyX = 1 + calendar.add(Calendar.DATE, 3) + reminder.time = fakeZDT?.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) - val occurrences = daily.getNextReminderOccurrences(reminder, 4) - occurrences?.size shouldBe 4 - calendar.add(Calendar.DATE, 35) - occurrences?.forEach { - it.dayOfYear shouldBe calendar.get(Calendar.DAY_OF_YEAR) - calendar.add(Calendar.DATE, 5) - } - } - } - "dailies repeating weekly" should { - beforeEach { - daily.frequency = Frequency.WEEKLY - } - "return occurrences if active every day" { - daily.startDate = calendar.time - daily.repeat = Days() - daily.everyX = 1 - - reminder.time = calendar.time.toZonedDateTime()?.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) - - val occurrences = daily.getNextReminderOccurrences(reminder, 4) - occurrences?.size shouldBe 4 - calendar.add(Calendar.DATE, 1) - occurrences?.forEach { - it.dayOfYear shouldBe calendar.get(Calendar.DAY_OF_YEAR) - calendar.add(Calendar.DATE, 1) - } - } - - "return occurrences if active tuesdays" { - calendar.add(Calendar.DATE, -63) - daily.startDate = calendar.time - daily.repeat = Days(t = true, default = false) - daily.everyX = 1 - - reminder.time = calendar.time.toZonedDateTime()?.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) - - val occurrences = daily.getNextReminderOccurrences(reminder, 4) - occurrences?.size shouldBe 4 - calendar.add(Calendar.DATE, 63) - while (calendar.get(Calendar.DAY_OF_WEEK) != Calendar.TUESDAY) { - calendar.add(Calendar.DATE, 1) - } - occurrences?.forEach { - it.dayOfYear shouldBe calendar.get(Calendar.DAY_OF_YEAR) - calendar.add(Calendar.DATE, 7) - } - } - - "return occurrences if active every second friday" { - calendar.add(Calendar.DATE, -23) - daily.startDate = calendar.time - daily.repeat = Days(f = true, default = false) - daily.everyX = 2 - - reminder.time = calendar.time.toZonedDateTime()?.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) - - val occurrences = daily.getNextReminderOccurrences(reminder, 4) - occurrences?.size shouldBe 4 - while (calendar.get(Calendar.DAY_OF_WEEK) != Calendar.FRIDAY) { - calendar.add(Calendar.DATE, 1) - } - calendar.add(Calendar.DATE, 28) - occurrences?.forEach { - it.dayOfYear shouldBe calendar.get(Calendar.DAY_OF_YEAR) - calendar.add(Calendar.DATE, 14) - } - } - - "return occurrences if active multiple days a week" { - calendar.add(Calendar.DATE, -23) - daily.startDate = calendar.time - daily.repeat = Days(t = true, f = true, s = true, default = false) - daily.everyX = 1 - - reminder.time = calendar.time.toZonedDateTime()?.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) - - val occurrences = daily.getNextReminderOccurrences(reminder, 4) - occurrences?.size shouldBe 4 - calendar.add(Calendar.DATE, 23) - occurrences?.forEach { - while (!calendar.matchesRepeatDays(daily.repeat)) { + val occurrences = daily.getNextReminderOccurrences(reminder, 4, fakeZDT) + occurrences?.size shouldBe 4 + occurrences?.forEach { + it.dayOfYear shouldBe calendar.get(Calendar.DAY_OF_YEAR) calendar.add(Calendar.DATE, 1) } - it.dayOfYear shouldBe calendar.get(Calendar.DAY_OF_YEAR) - calendar.add(Calendar.DATE, 1) + } + + "return occurrences according to everyX = 5" { + calendar.add(Calendar.DATE, -33) + daily.startDate = calendar.time + daily.everyX = 5 + reminder.time = fakeZDT?.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + + val occurrences = daily.getNextReminderOccurrences(reminder, 4, fakeZDT) + occurrences?.size shouldBe 4 + calendar.add(Calendar.DATE, 35) + occurrences?.forEach { + it.dayOfYear shouldBe calendar.get(Calendar.DAY_OF_YEAR) + calendar.add(Calendar.DATE, 5) + } } } - } + "dailies repeating weekly" should { + beforeEach { + daily.frequency = Frequency.WEEKLY + } + "return occurrences if active every day" { + daily.startDate = calendar.time + daily.repeat = Days() + daily.everyX = 1 - "dailies repeating monthly" should { - beforeEach { - daily.frequency = Frequency.MONTHLY - } + reminder.time = fakeZDT?.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) - "return occurrences if active every 10th of the month" { - daily.startDate = calendar.time - daily.everyX = 1 - daily.setDaysOfMonth(listOf(10)) - - reminder.time = calendar.time.toZonedDateTime()?.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) - - val occurrences = daily.getNextReminderOccurrences(reminder, 4) - occurrences?.size shouldBe 4 - occurrences?.forEach { - while (calendar.get(Calendar.DAY_OF_MONTH) != 10) { + val occurrences = daily.getNextReminderOccurrences(reminder, 4, fakeZDT) + occurrences?.size shouldBe 4 + occurrences?.forEach { + it.dayOfYear shouldBe calendar.get(Calendar.DAY_OF_YEAR) calendar.add(Calendar.DATE, 1) } - it.dayOfYear shouldBe calendar.get(Calendar.DAY_OF_YEAR) - it.year shouldBe calendar.get(Calendar.YEAR) - calendar.add(Calendar.DATE, 1) } - } - "return occurrences if active every third month on the 5th" { - calendar.add(Calendar.MONDAY, -8) - daily.startDate = calendar.time - daily.everyX = 3 - daily.setDaysOfMonth(listOf(5)) + "return occurrences if active tuesdays" { + calendar.add(Calendar.DATE, -63) + daily.startDate = calendar.time + daily.repeat = Days(t = true, default = false) + daily.everyX = 1 - reminder.time = calendar.time.toZonedDateTime()?.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + reminder.time = fakeZDT?.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) - val occurrences = daily.getNextReminderOccurrences(reminder, 4) - calendar.add(Calendar.MONTH, 9) - occurrences?.size shouldBe 4 - occurrences?.forEach { - while (calendar.get(Calendar.DAY_OF_MONTH) != 5) { - calendar.add(Calendar.DATE, 1) - } - it.dayOfYear shouldBe calendar.get(Calendar.DAY_OF_YEAR) - it.year shouldBe calendar.get(Calendar.YEAR) - calendar.add(Calendar.MONTH, 3) - } - } - - "return occurrences if active every month on the third tuesday" { - calendar.set(Calendar.YEAR, 2025) - calendar.set(Calendar.MONTH, Calendar.JANUARY) - calendar.set(Calendar.DAY_OF_MONTH, 21) - daily.startDate = calendar.time - daily.everyX = 1 - daily.setWeeksOfMonth(listOf(2)) - - reminder.time = calendar.time.toZonedDateTime()?.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) - - val occurrences = daily.getNextReminderOccurrences(reminder, 4) - occurrences?.size shouldBe 4 - calendar.set(Calendar.DAY_OF_MONTH, 1) - occurrences?.forEach { + val occurrences = daily.getNextReminderOccurrences(reminder, 4, fakeZDT) + occurrences?.size shouldBe 4 + calendar.add(Calendar.DATE, 63) while (calendar.get(Calendar.DAY_OF_WEEK) != Calendar.TUESDAY) { calendar.add(Calendar.DATE, 1) } - calendar.add(Calendar.DATE, 14) - it.dayOfYear shouldBe calendar.get(Calendar.DAY_OF_YEAR) - it.year shouldBe calendar.get(Calendar.YEAR) - calendar.add(Calendar.MONTH, 1) - calendar.set(Calendar.DAY_OF_MONTH, 1) + occurrences?.forEach { + it.dayOfYear shouldBe calendar.get(Calendar.DAY_OF_YEAR) + calendar.add(Calendar.DATE, 7) + } + } + + "return occurrences if active every second friday" { + daily.startDate = calendar.time + daily.repeat = Days(f = true, default = false) + daily.everyX = 2 + + reminder.time = fakeZDT?.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + + val occurrences = daily.getNextReminderOccurrences(reminder, 4, fakeZDT) + occurrences?.size shouldBe 4 + while (calendar.get(Calendar.DAY_OF_WEEK) != Calendar.FRIDAY) { + calendar.add(Calendar.DATE, 1) + } + occurrences?.forEach { + it.dayOfMonth shouldBe calendar.get(Calendar.DAY_OF_MONTH) + it.dayOfYear shouldBe calendar.get(Calendar.DAY_OF_YEAR) + calendar.add(Calendar.DATE, 14) + } + } + + "return occurrences if active multiple days a week" { + calendar.add(Calendar.DATE, -23) + daily.startDate = calendar.time + daily.repeat = Days(t = true, f = true, s = true, default = false) + daily.everyX = 1 + + reminder.time = fakeZDT?.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + + val occurrences = daily.getNextReminderOccurrences(reminder, 4, fakeZDT) + occurrences?.size shouldBe 4 + // One day in the future since the time for today is already too late + calendar.add(Calendar.DATE, 23) + occurrences?.forEach { + while (!calendar.matchesRepeatDays(daily.repeat)) { + calendar.add(Calendar.DATE, 1) + } + it.dayOfYear shouldBe calendar.get(Calendar.DAY_OF_YEAR) + calendar.add(Calendar.DATE, 1) + } } } - "return occurrences if active every fifth month on the second wednesday" { - calendar.add(Calendar.MONTH, -8) - calendar.set(Calendar.DAY_OF_MONTH, 1) - while (calendar.get(Calendar.DAY_OF_WEEK) != Calendar.WEDNESDAY) { - calendar.add(Calendar.DATE, 1) + "dailies repeating monthly" should { + beforeEach { + daily.frequency = Frequency.MONTHLY } - calendar.add(Calendar.DATE, 7) - daily.startDate = calendar.time - daily.everyX = 5 - daily.setWeeksOfMonth(listOf(1)) - reminder.time = calendar.time.toZonedDateTime()?.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + "return occurrences if active every 10th of the month" { + daily.startDate = calendar.time + daily.everyX = 1 + daily.setDaysOfMonth(listOf(10)) - val occurrences = daily.getNextReminderOccurrences(reminder, 4) - occurrences?.size shouldBe 4 - calendar.add(Calendar.MONTH, 10) - calendar.set(Calendar.DAY_OF_MONTH, 1) - occurrences?.forEach { + reminder.time = fakeZDT?.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + + val occurrences = daily.getNextReminderOccurrences(reminder, 4, fakeZDT) + occurrences?.size shouldBe 4 + occurrences?.forEach { + while (calendar.get(Calendar.DAY_OF_MONTH) != 10) { + calendar.add(Calendar.DATE, 1) + } + it.dayOfYear shouldBe calendar.get(Calendar.DAY_OF_YEAR) + it.year shouldBe calendar.get(Calendar.YEAR) + calendar.add(Calendar.DATE, 1) + } + } + + "return occurrences if active every third month on the 5th" { + calendar.add(Calendar.MONDAY, -8) + daily.startDate = calendar.time + daily.everyX = 3 + daily.setDaysOfMonth(listOf(5)) + + reminder.time = fakeZDT?.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + + val occurrences = daily.getNextReminderOccurrences(reminder, 4, fakeZDT) + calendar.add(Calendar.MONTH, 9) + occurrences?.size shouldBe 4 + occurrences?.forEach { + while (calendar.get(Calendar.DAY_OF_MONTH) != 5) { + calendar.add(Calendar.DATE, 1) + } + it.dayOfYear shouldBe calendar.get(Calendar.DAY_OF_YEAR) + it.year shouldBe calendar.get(Calendar.YEAR) + calendar.add(Calendar.MONTH, 3) + } + } + + "return occurrences if active every month on the third tuesday" { + val storedTime = calendar.time + calendar.set(Calendar.YEAR, 2024) + calendar.set(Calendar.MONTH, Calendar.DECEMBER) + calendar.set(Calendar.DAY_OF_MONTH, 17) + daily.startDate = calendar.time + daily.everyX = 1 + daily.setWeeksOfMonth(listOf(2)) + calendar.time = storedTime + if (calendar.get(Calendar.DAY_OF_MONTH) > 17) { + calendar.add(Calendar.MONTH, 1) + } + + reminder.time = fakeZDT?.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + + val occurrences = daily.getNextReminderOccurrences(reminder, 4, fakeZDT) + occurrences?.size shouldBe 4 + calendar.set(Calendar.DAY_OF_MONTH, 1) + occurrences?.forEach { + while (calendar.get(Calendar.DAY_OF_WEEK) != Calendar.TUESDAY) { + calendar.add(Calendar.DATE, 1) + } + calendar.add(Calendar.DATE, 14) + it.dayOfYear shouldBe calendar.get(Calendar.DAY_OF_YEAR) + it.year shouldBe calendar.get(Calendar.YEAR) + calendar.add(Calendar.MONTH, 1) + calendar.set(Calendar.DAY_OF_MONTH, 1) + } + } + + "return occurrences if active every fifth month on the second wednesday" { + calendar.add(Calendar.MONTH, -8) + calendar.set(Calendar.DAY_OF_MONTH, 1) while (calendar.get(Calendar.DAY_OF_WEEK) != Calendar.WEDNESDAY) { calendar.add(Calendar.DATE, 1) } calendar.add(Calendar.DATE, 7) - print(daily.startDate) - print(it) - println(calendar.time) - it.dayOfYear shouldBe calendar.get(Calendar.DAY_OF_YEAR) - it.year shouldBe calendar.get(Calendar.YEAR) - calendar.add(Calendar.MONTH, 5) + daily.startDate = calendar.time + daily.everyX = 5 + daily.setWeeksOfMonth(listOf(1)) + + reminder.time = fakeZDT?.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + + val occurrences = daily.getNextReminderOccurrences(reminder, 4, fakeZDT) + occurrences?.size shouldBe 4 + calendar.add(Calendar.MONTH, 10) calendar.set(Calendar.DAY_OF_MONTH, 1) + occurrences?.forEach { + while (calendar.get(Calendar.DAY_OF_WEEK) != Calendar.WEDNESDAY) { + calendar.add(Calendar.DATE, 1) + } + calendar.add(Calendar.DATE, 7) + print(daily.startDate) + print(it) + println(calendar.time) + it.dayOfYear shouldBe calendar.get(Calendar.DAY_OF_YEAR) + it.year shouldBe calendar.get(Calendar.YEAR) + calendar.add(Calendar.MONTH, 5) + calendar.set(Calendar.DAY_OF_MONTH, 1) + } } } - } - "dailies repeating yearly" should { - beforeEach { - daily.frequency = Frequency.YEARLY - } - "return occurrences if active every year" { - daily.startDate = calendar.time - daily.everyX = 1 - reminder.time = calendar.time.toZonedDateTime()?.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + "dailies repeating yearly" should { + beforeEach { + daily.frequency = Frequency.YEARLY + } + "return occurrences if active every year" { + daily.startDate = calendar.time + daily.everyX = 1 + reminder.time = fakeZDT?.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) - val occurrences = daily.getNextReminderOccurrences(reminder, 4) - occurrences?.size shouldBe 4 - calendar.add(Calendar.YEAR, 1) - occurrences?.forEach { - it.dayOfYear shouldBe calendar.get(Calendar.DAY_OF_YEAR) - it.year shouldBe calendar.get(Calendar.YEAR) + val occurrences = daily.getNextReminderOccurrences(reminder, 4, fakeZDT) + occurrences?.size shouldBe 4 calendar.add(Calendar.YEAR, 1) + occurrences?.forEach { + it.dayOfYear shouldBe calendar.get(Calendar.DAY_OF_YEAR) + it.year shouldBe calendar.get(Calendar.YEAR) + calendar.add(Calendar.YEAR, 1) + } } - } - "return occurrences if active every 3 years" { - calendar.add(Calendar.YEAR, -3) - calendar.add(Calendar.DATE, 1) - daily.startDate = calendar.time - daily.everyX = 3 - reminder.time = calendar.time.toZonedDateTime()?.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + "return occurrences if active every 3 years" { + calendar.add(Calendar.YEAR, -3) + calendar.add(Calendar.DATE, 1) + daily.startDate = calendar.time + daily.everyX = 3 + reminder.time = fakeZDT?.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) - calendar.add(Calendar.YEAR, 3) - val occurrences = daily.getNextReminderOccurrences(reminder, 4) - occurrences?.size shouldBe 4 - occurrences?.forEach { - it.dayOfYear shouldBe calendar.get(Calendar.DAY_OF_YEAR) - it.year shouldBe calendar.get(Calendar.YEAR) calendar.add(Calendar.YEAR, 3) + val occurrences = daily.getNextReminderOccurrences(reminder, 4, fakeZDT) + occurrences?.size shouldBe 4 + occurrences?.forEach { + it.dayOfYear shouldBe calendar.get(Calendar.DAY_OF_YEAR) + it.year shouldBe calendar.get(Calendar.YEAR) + calendar.add(Calendar.YEAR, 3) + } } } }