/* * Copyright (c) 2005-2011 Grameen Foundation USA * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. See the License for the specific language governing * permissions and limitations under the License. * * See also http://www.apache.org/licenses/LICENSE-2.0.html for an * explanation of the license and how it is applied. */ package org.mifos.schedule; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.junit.Assert.assertThat; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.joda.time.DateTime; import org.joda.time.DateTimeConstants; import org.joda.time.Days; import org.junit.Before; import org.junit.Test; import org.mifos.application.holiday.business.Holiday; import org.mifos.application.meeting.business.MeetingBO; import org.mifos.calendar.DayOfWeek; import org.mifos.domain.builders.HolidayBuilder; import org.mifos.domain.builders.MeetingBuilder; import org.mifos.domain.builders.ScheduledEventBuilder; import org.mifos.schedule.internal.HolidayAndWorkingDaysAndMoratoriaScheduledDateGeneration; /** * I test {@link HolidayAndWorkingDaysAndMoratoriaScheduledDateGeneration}. * * <p>Tests involve dates starting in June, 2011. To make it easier to construct tests with certain * days of the week/month, the calendar for these months appears below: * * <pre> *********************** * 2011 *********************** * S M T W T F S * 1 2 3 4 5 6 7 May * 8 9 10 11 12 13 14 * 15 16 17 18 19 20 21 * 22 23 24 25 26 27 28 * 29 30 31 * 1 2 3 4 June * 5 6 7 8 9 10 11 * 12 13 14 15 16 17 18 * 19 20 21 22 23 24 25 * 26 27 28 29 30 * 1 2 * 3 4 5 6 7 8 9 July * 10 11 12 13 14 15 16 * 17 18 19 20 21 22 23 * 24 25 26 27 28 29 30 * 31 * 1 2 3 4 5 6 * 7 8 9 10 11 12 13 August * 14 15 16 17 18 19 20 * 21 22 23 24 25 26 27 * 28 29 30 31 * 1 2 3 * 4 5 6 7 8 9 10 September * 11 12 13 14 15 16 17 * 18 19 20 21 22 23 24 * 25 26 27 28 29 30 * 1 * 2 3 4 5 6 7 8 * 9 10 11 12 13 14 15 October * 16 17 18 19 20 21 22 * 23 24 25 26 27 28 29 * 30 31 * 1 2 3 4 5 * 6 7 8 9 10 11 12 November * 13 14 15 16 17 18 19 * 20 21 22 23 24 25 26 * 27 28 29 30 * * 1 2 3 * 4 5 6 7 8 9 10 December * 11 12 13 14 15 16 17 * 18 19 20 21 22 23 24 * 25 26 27 28 29 30 31 *********************** * 2012 (leap year) *********************** * 1 2 3 4 5 6 7 January * 8 9 10 11 12 13 14 * 15 16 17 18 19 20 21 * 22 23 24 25 26 27 28 * 29 30 31 * 1 2 3 4 * 5 6 7 8 9 10 11 February (leap year) * 12 13 14 15 16 17 18 * 19 20 21 22 23 24 25 * 26 27 28 29 * 1 2 3 * 4 5 6 7 8 9 10 March * 11 12 13 14 15 16 17 * 18 19 20 21 22 23 24 * 25 26 27 28 29 30 31 * </pre> * */ public class HolidayAndWorkingDaysAndMoratoriaScheduledDateGenerationTest { private ScheduledDateGeneration scheduleGeneration; private List<Days> workingDays; //private DateTime mon_2011_07_04 = date(2011,7,4); private DateTime mon_2011_06_20 = date(2011,6,20); private DateTime wed_2011_06_22 = date(2011,6,22); private DateTime wed_2011_06_29 = date(2011,6,29); private DateTime mon_2011_07_04 = date(2011,7,4); private DateTime mon_2011_06_27 = date(2011,6,27); private DateTime mon_2011_06_29 = date(2011,6,29); private Holiday sevenDayMoratoriumStartingJuly4th = new HolidayBuilder().from(mon_2011_07_04) .to(mon_2011_07_04.plusDays(6)) .withNextMeetingRule() .withRepaymentMoratoriumRule() .build(); @Before public void setupAndInjectDependencies() { workingDays = Arrays.asList(DayOfWeek.mondayAsDay(), DayOfWeek.tuesdayAsDay(), DayOfWeek .wednesdayAsDay(), DayOfWeek.thursdayAsDay(), DayOfWeek.fridayAsDay()); List<Holiday> upcomingHolidays = new ArrayList<Holiday>(); scheduleGeneration = new HolidayAndWorkingDaysAndMoratoriaScheduledDateGeneration(workingDays, upcomingHolidays); //Dates frequently used in tests } @Test public void canGenerateScheduledDates() { DateTime lastScheduledDate = DayOfWeek.mondayMidnight(); ScheduledEvent recurringEvent = new ScheduledEventBuilder().every(1).weeks().on(DayOfWeek.monday()).build(); List<DateTime> scheduledDates = scheduleGeneration .generateScheduledDates(10, lastScheduledDate, recurringEvent, false); assertThat(scheduledDates.size(), is(notNullValue())); assertThat(scheduledDates.size(), is(10)); } @Test public void canGenerateScheduledDatesThatMatchScheduledEventBasedOnLastScheduledDate() { DateTime lastScheduledDate = DayOfWeek.mondayMidnight(); ScheduledEvent recurringEvent = new ScheduledEventBuilder().every(1).weeks().on(DayOfWeek.monday()).build(); List<DateTime> scheduledDates = scheduleGeneration .generateScheduledDates(10, lastScheduledDate, recurringEvent, false); assertThat(scheduledDates.get(0), is(lastScheduledDate)); assertThat(scheduledDates.get(1), is(DayOfWeek.oneWeekFrom(lastScheduledDate))); } @Test public void canGenerateAllScheduledDatesThatMatchScheduleEvent() { DateTime lastScheduledDate = DayOfWeek.mondayMidnight(); DateTime dayAfterLastScheduledDate = DayOfWeek.tuesdayMidnight(); ScheduledEvent scheduleEvent = new ScheduledEventBuilder().every(1).weeks().on(DayOfWeek.monday()).build(); List<DateTime> scheduledDates = scheduleGeneration.generateScheduledDates(10, dayAfterLastScheduledDate, scheduleEvent, false); DateTime lastDate = lastScheduledDate; for (DateTime generatedDate : scheduledDates) { assertThat(generatedDate, is(DayOfWeek.oneWeekFrom(lastDate))); lastDate = generatedDate; } } @Test public void weeklyScheduledEventWithSecondDateInOneWeekNextMeetingHolidayShouldPushOutSecondDateOneWeek() { Holiday oneWeekNextMeetingHolidayStartingJuly4th = new HolidayBuilder().from(mon_2011_07_04).to(mon_2011_07_04.plusWeeks(1)) .withNextMeetingRule() .build(); validateDates(new ScheduleBuilder().withWeeklyEvent(1, wed_2011_06_29.getDayOfWeek()) .withHolidays(oneWeekNextMeetingHolidayStartingJuly4th) .withStartDate(wed_2011_06_29) .withNumberOfDates(4) .build(), wed_2011_06_29, wed_2011_06_29.plusWeeks(2), wed_2011_06_29.plusWeeks(2), wed_2011_06_29.plusWeeks(3)); } @Test public void weeklyScheduledEventSecondThirdFourthDateInThreeWeekNextMeetingHolidayShouldQuadrupleUpDatesAfterHoliday() { Holiday threeWeekNextMeetngHolidayStartingJuly4th = new HolidayBuilder().from(mon_2011_07_04) .to(mon_2011_07_04.plusWeeks(3)) .withNextMeetingRule() .build(); validateDates(new ScheduleBuilder().withHolidays(threeWeekNextMeetngHolidayStartingJuly4th) .withWeeklyEvent(1, wed_2011_06_29.getDayOfWeek()) .withNumberOfDates(4) .withStartDate(wed_2011_06_29.minusDays(1)) .build(), wed_2011_06_29, wed_2011_06_29.plusWeeks(4), wed_2011_06_29.plusWeeks(4), wed_2011_06_29.plusWeeks(4)); } @Test public void weeklyScheduleOneWeekMoratoriumEnclosesSecondScheduledDateShouldPushOutSecondAndThirdDatesOneWeek() { validateDates(new ScheduleBuilder().withHolidays(sevenDayMoratoriumStartingJuly4th) .withStartDate(wed_2011_06_29.minusDays(1)) .withWeeklyEvent(1, wed_2011_06_29.getDayOfWeek()) .withNumberOfDates(3) .build(), wed_2011_06_29, wed_2011_06_29.plusWeeks(2), wed_2011_06_29.plusWeeks(3)); } @Test public void weeklyScheduleTwoWeekMoratoriumEnclosesSecondAndThirdScheduledDatesShouldPushSecondAndFollowingDatesTwoWeeks() { Holiday twoWeekMoratoriumStartingJuly4th = new HolidayBuilder() .from(mon_2011_07_04) .to(mon_2011_07_04.plusWeeks(2)) .withRepaymentMoratoriumRule() .build(); validateDates(new ScheduleBuilder().withHolidays(twoWeekMoratoriumStartingJuly4th) .withStartDate(wed_2011_06_29.minusDays(1)) .withWeeklyEvent(1, wed_2011_06_29.getDayOfWeek()) .withNumberOfDates(3) .build(), wed_2011_06_29, wed_2011_06_29.plusWeeks(3), wed_2011_06_29.plusWeeks(4)); } @Test public void weeklyScheduledEventWithSecondThirdFourthDateInThreeWeekSameDayHolidayExpectNoAdjustment() { Holiday threeWeekSameDay = new HolidayBuilder() .from(mon_2011_07_04) .to(mon_2011_07_04.plusWeeks(3)) .withSameDayAsRule() .build(); validateDates(new ScheduleBuilder().withHolidays(threeWeekSameDay) .withWeeklyEvent(1, wed_2011_06_29.getDayOfWeek()) .withStartDate(wed_2011_06_29.minusDays(1)) .withNumberOfDates(5) .build(), wed_2011_06_29, wed_2011_06_29.plusWeeks(1), wed_2011_06_29.plusWeeks(2), wed_2011_06_29.plusWeeks(3), wed_2011_06_29.plusWeeks(4)); } @Test public void weeklyScheduledEventWithMoratoriumImmediatelyFollowingNexRepaymentHoliday() { //First holiday extends for 14 consecutive days from 2011/6/27 Holiday twoWeekNextRepaymentHoliday = new HolidayBuilder() .from(mon_2011_06_27) .to(mon_2011_06_27.plusWeeks(2).minusDays(1)) .withNextMeetingRule() .build(); // Next moratorium follows for 14 days Holiday twoWeekMoratorium = new HolidayBuilder() .from(mon_2011_06_27.plusWeeks(2)) .to(mon_2011_06_27.plusWeeks(4).plusDays(-1)) .withRepaymentMoratoriumRule() .build(); /* * Generate a Wednesday schedule from the Monday the week before the first holiday starts, 2011/6/20. * Schedule should start on the next Wednesday, 2011/6/22 (2 days from the starting point). * The second and third dates get shifted into the first week of the moratorium, on the same * date as the fourth date. These and the remaining schedule get shifted two mor weeks * past the moratorium. */ validateDates(new ScheduleBuilder().withHolidays(twoWeekNextRepaymentHoliday, twoWeekMoratorium) .withWeeklyEvent(1, DayOfWeek.wednesday()) .withNumberOfDates(6) .withStartDate(mon_2011_06_20) .build(), wed_2011_06_22, wed_2011_06_22.plusWeeks(5), wed_2011_06_22.plusWeeks(5), wed_2011_06_22.plusWeeks(5), wed_2011_06_22.plusWeeks(6), wed_2011_06_22.plusWeeks(7)); } @Test public void weeklyScheduledEventWithMoratoriumImmediatelyFollowingSameDayHoliday() { //First holiday extends for 14 consecutive days Holiday twoWeekSameDayHoliday = new HolidayBuilder() .from(mon_2011_06_27) .to(mon_2011_06_27.plusWeeks(2).minusDays(1)) .withSameDayAsRule() .build(); // Next moratorium follows for 14 days Holiday twoWeekMoratorium = new HolidayBuilder() .from(mon_2011_06_27.plusWeeks(2)) .to(mon_2011_06_27.plusWeeks(4).minusDays(1)) .withRepaymentMoratoriumRule() .build(); /* * Generate a Wednesday schedule from the Monday the week before the first holiday starts, 2011/6/20. * Schedule should start on the next Wednesday, 2011/6/22 (2 days from the starting point). * The second and third dates don't move (same-day holiday) * Fourth and fifth dates get shifted two weeks past the moratorium. */ validateDates(new ScheduleBuilder().withHolidays(twoWeekSameDayHoliday, twoWeekMoratorium) .withWeeklyEvent(1, DayOfWeek.wednesday()) .withNumberOfDates(6) .withStartDate(mon_2011_06_20) .build(), wed_2011_06_22, wed_2011_06_22.plusWeeks(1), wed_2011_06_22.plusWeeks(2), wed_2011_06_22.plusWeeks(5), wed_2011_06_22.plusWeeks(6), wed_2011_06_22.plusWeeks(7)); } @Test public void weeklyScheduledEventWithMoratoriumImmediatelyFollowingNextWorkingDayHoliday() { Holiday twoWeekNextWorkingDayHoliday = new HolidayBuilder() .from(mon_2011_06_27) .to(mon_2011_06_27.plusWeeks(2).minusDays(1)) .withNextWorkingDayRule() .build(); Holiday twoWeekMoratorium = new HolidayBuilder() .from(mon_2011_06_27.plusWeeks(2)) .to(mon_2011_06_27.plusWeeks(4).minusDays(1)) .withRepaymentMoratoriumRule() .build(); /* * Schedule should start on the next Wednesday (2 days from the starting point). * The second and third unadjusted dates, being in the next-working-day holiday shift to the * first Monday after the holiday and moratorium. Because the fourth unadjusted date is in the moratorium, * it and the remaining schedule are shifted two more weeks past the moratorium. */ validateDates(new ScheduleBuilder().withHolidays(twoWeekNextWorkingDayHoliday, twoWeekMoratorium) .withWeeklyEvent(1, DayOfWeek.wednesday()) .withNumberOfDates(6) .withStartDate(mon_2011_06_20) .build(), wed_2011_06_22, wed_2011_06_22.plusWeeks(5).withDayOfWeek(DayOfWeek.monday()), wed_2011_06_22.plusWeeks(5).withDayOfWeek(DayOfWeek.monday()), wed_2011_06_22.plusWeeks(5), wed_2011_06_22.plusWeeks(6), wed_2011_06_22.plusWeeks(7)); } @Test public void biWeeklyScheduleNoHoliday() { validateDates(new ScheduleBuilder().withHolidays() .withWeeklyEvent(2, DateTimeConstants.MONDAY) .withStartDate(date(2011,6,27)) .withNumberOfDates(3) .build(), date(2011, 7, 4), date(2011, 7, 18), date(2011, 8, 1)); } @Test public void biWeeklyScheduleSecondDateInNextMeetingHolidayShouldShiftSecondDateByTwoWeeks() { Holiday twoWeekNextWorkingDayHoliday = new HolidayBuilder() .from(date(2011,7,11)) .to(date(2011,7,17)) .withNextMeetingRule() .build(); validateDates(new ScheduleBuilder().withHolidays(twoWeekNextWorkingDayHoliday) .withWeeklyEvent(2, DateTimeConstants.MONDAY) .withStartDate(date(2011,6,27)) .withNumberOfDates(3) .build(), date(2011, 7, 4), date(2011, 7, 18), date(2011, 8, 1)); } @Test public void biWeeklyScheduleSecondDateHitsThreeDayMoratoriumShouldPushOutSecondAndThirdDates() { Holiday independenceDay = new HolidayBuilder() .from(date(2011,7,4)) .to(date(2011,7,6)) .withRepaymentMoratoriumRule() .build(); validateDates(new ScheduleBuilder().withHolidays(independenceDay) .withWeeklyEvent(2, DateTimeConstants.MONDAY) .withStartDate(date(2011,6,20)) .withNumberOfDates(3) .build(), date(2011, 6, 27), date(2011,7,11), date(2011,7 ,25)); } @Test public void weeklyScheduleMoratoriumSpansNextWorkingDayHolidayShouldIgnoreNextWorkingDayHoliday() { Holiday twoWeekNextWorkingDayHoliday = new HolidayBuilder() .from(mon_2011_06_27) .to(mon_2011_06_27.plusWeeks(2).minusDays(1)) .withNextWorkingDayRule() .build(); Holiday twoWeekMoratorium = new HolidayBuilder() .from(mon_2011_06_27) .to(mon_2011_06_27.plusWeeks(2).minusDays(1)) .withRepaymentMoratoriumRule() .build(); /* * Schedule should start on the next Wednesday (2 days from the starting point). * The second and third unadjusted dates, being in the moratorium shift them * and the remaining schedule are shifted two weeks past the moratorium. Next working day is ignored. */ validateDates(new ScheduleBuilder().withHolidays(twoWeekNextWorkingDayHoliday, twoWeekMoratorium) .withWeeklyEvent(1, DayOfWeek.wednesday()) .withNumberOfDates(6) .withStartDate(mon_2011_06_20) .build(), wed_2011_06_22, wed_2011_06_22.plusWeeks(3), wed_2011_06_22.plusWeeks(4), wed_2011_06_22.plusWeeks(5), wed_2011_06_22.plusWeeks(6), wed_2011_06_22.plusWeeks(7)); } @Test public void weeklyScheduleMoratoriumOverlapsNextWorkingDayHolidayAndIncludesOneScheduledDateShouldIgnoreNextWorkingDayHoliday() { Holiday oneWeekNextWorkingDayHoliday = new HolidayBuilder() .from(mon_2011_06_27) .to(mon_2011_06_27.plusWeeks(1).minusDays(1)) .withNextWorkingDayRule() .build(); Holiday twoWeekMoratorium = new HolidayBuilder() .from(mon_2011_06_29) .to(mon_2011_06_29) .withRepaymentMoratoriumRule() .build(); /* * Schedule should start on the next Wednesday (2 days from the starting point). * The second date is in both holidays, so it and remaining dates are shifted one week. The * second date is now past the normal holiday. */ validateDates(new ScheduleBuilder().withHolidays(oneWeekNextWorkingDayHoliday, twoWeekMoratorium) .withWeeklyEvent(1, DayOfWeek.wednesday()) .withNumberOfDates(6) .withStartDate(mon_2011_06_20) .build(), wed_2011_06_22, wed_2011_06_22.plusWeeks(2), wed_2011_06_22.plusWeeks(3), wed_2011_06_22.plusWeeks(4), wed_2011_06_22.plusWeeks(5), wed_2011_06_22.plusWeeks(6)); } @Test public void weeklyScheduleMoratoriumOverlapsNextWorkingDayHolidayDoesNotIncludeOneScheduledDateShouldShiftTheDate() { // Second date, 6/29, falls in next-working-day holiday Holiday oneWeekNextWorkingDayHoliday = new HolidayBuilder() .from(mon_2011_06_27) .to(mon_2011_06_27.plusWeeks(1).minusDays(1)) .withNextWorkingDayRule() .build(); // Moratorium starts on 6/30, overlapping above holiday but does not include the second date. Holiday twoWeekMoratorium = new HolidayBuilder() .from(mon_2011_06_27.plusDays(3)) // 6/30 .to(mon_2011_06_27.plusWeeks(1)) // Monday 7/4 .withRepaymentMoratoriumRule() .build(); /* * Schedule should start on the next Wednesday (2 days from the starting point). * The second date is in both holidays, so it and remaining dates are shifted one week. The * second date is now past the normal holiday. */ validateDates(new ScheduleBuilder().withHolidays(oneWeekNextWorkingDayHoliday, twoWeekMoratorium) .withWeeklyEvent(1, DayOfWeek.wednesday()) .withNumberOfDates(6) .withStartDate(mon_2011_06_20) .build(), wed_2011_06_22, wed_2011_06_22.plusWeeks(2).withDayOfWeek(DayOfWeek.tuesday()), // Tuesday 7/5 (next working day after moratorium) wed_2011_06_22.plusWeeks(2), // Wednesday, 7/6 wed_2011_06_22.plusWeeks(3), wed_2011_06_22.plusWeeks(4), wed_2011_06_22.plusWeeks(5)); } /************************************************** * Tests for schedules recurring monthly by day of month **************************************************/ @Test public void monthlyByDayScheduleNoHolidaysShouldNotShiftAnyDates() { //July & August dates shifted one month past moratorium, then adjusted for working day validateDates(new ScheduleBuilder().withHolidays() .withDayOfMonthEvent(1, 6) .withStartDate(date(2011,6,6)) .withNumberOfDates(10) .build(), date(2011, 6, 6), date(2011, 7, 6), date(2011, 8, 8), date(2011, 9, 6), date(2011, 10, 6), date(2011, 11, 7), date(2011, 12, 6), date(2012, 1, 6), date(2012, 2, 6), date(2012, 3, 6)); } @Test public void monthlyByDayScheduleSecondDateInMoratoriumShouldShiftSecondAndThirdDatesbyOneMonth() { validateDates(new ScheduleBuilder().withHolidays(sevenDayMoratoriumStartingJuly4th) .withDayOfMonthEvent(1, 6) .withStartDate(date(2011,6,6)) .withNumberOfDates(3) .build(), date(2011, 6, 6), date(2011, 8, 8), date(2011, 9, 6)); } @Test public void monthlyByDayScheduleSecondDateInNextWorkingDayHolidayShouldShiftSecondDateToNextWorkingDay() { Holiday twoWeekNextWorkingDayHoliday = new HolidayBuilder() .from(date(2011,7,4)) .to(date(2011,7,17)) .withNextWorkingDayRule() .build(); validateDates(new ScheduleBuilder().withHolidays(twoWeekNextWorkingDayHoliday) .withDayOfMonthEvent(1, 6) .withStartDate(date(2011,6,6)) .withNumberOfDates(3) .build(), date(2011, 6, 6), date(2011, 7, 18), date(2011, 8, 8)); } @Test public void monthlyByDayScheduleSecondDateInNextMeetingHolidayShouldShiftSecondDateByOneMonth() { Holiday twoWeekNextWorkingDayHoliday = new HolidayBuilder() .from(date(2011,7,4)) .to(date(2011,7,17)) .withNextMeetingRule() .build(); validateDates(new ScheduleBuilder().withHolidays(twoWeekNextWorkingDayHoliday) .withDayOfMonthEvent(1, 6) .withStartDate(date(2011,6,6)) .withNumberOfDates(3) .build(), date(2011, 6, 6), date(2011, 8, 8), date(2011, 8, 8)); } @Test public void monthlyByDayScheduleSecondDateInSameDayHolidayShouldNotShiftAnyDates() { Holiday twoWeekNextWorkingDayHoliday = new HolidayBuilder() .from(date(2011,7,4)) .to(date(2011,7,17)) .withSameDayAsRule() .build(); validateDates(new ScheduleBuilder().withHolidays(twoWeekNextWorkingDayHoliday) .withDayOfMonthEvent(1, 6) .withStartDate(date(2011,6,6)) .withNumberOfDates(3) .build(), date(2011, 6, 6), date(2011, 7, 6), date(2011, 8, 8)); } @Test public void monthlyByDayScheduleSecondDateMissesMoratoriumShouldNotShiftAnyDates() { //Moratorium does not include 13th of month -- no shifts except to adjust for next working day in August validateDates(new ScheduleBuilder().withHolidays(sevenDayMoratoriumStartingJuly4th) .withDayOfMonthEvent(1, 13) .withStartDate(date(2011,6,13)) .withNumberOfDates(3) .build(), date(2011, 6, 13), date(2011, 7, 13), date(2011, 8, 15)); } @Test public void biMonthlyByDayScheduleNoHolidaysShouldNotShiftAnyDates() { //July & August dates shifted one month past moratorium, then adjusted for working day validateDates(new ScheduleBuilder().withHolidays() .withDayOfMonthEvent(2, 6) .withStartDate(date(2011, 5, 6)) .withNumberOfDates(4) .build(), date(2011, 6, 6), date(2011, 8, 8), date(2011, 10, 6), date(2011, 12, 6)); } @Test public void biMonthlyByDayScheduleSecondDateInMoratoriumShouldShiftSecondAndThirdDatesbyTwoMonths() { validateDates(new ScheduleBuilder().withHolidays(sevenDayMoratoriumStartingJuly4th) .withDayOfMonthEvent(2, 6) .withStartDate(date(2011,5,6)) .withNumberOfDates(3) .build(), date(2011, 6, 6), date(2011, 8, 8), date(2011, 10, 6)); } @Test public void biMonthlyByDayScheduleSecondDateInNextWorkingDayHolidayShouldShiftSecondDateToNextWorkingDay() { Holiday twoWeekNextWorkingDayHoliday = new HolidayBuilder() .from(date(2011,7,4)) .to(date(2011,7,17)) .withNextWorkingDayRule() .build(); validateDates(new ScheduleBuilder().withHolidays(twoWeekNextWorkingDayHoliday) .withDayOfMonthEvent(2, 6) .withStartDate(date(2011,5,6)) .withNumberOfDates(3) .build(), date(2011, 6, 6), date(2011, 8, 8), date(2011, 10, 6)); } @Test public void biMonthlyByDayScheduleSecondDateInNextMeetingHolidayShouldShiftSecondDateByOneMonth() { Holiday twoWeekNextWorkingDayHoliday = new HolidayBuilder() .from(date(2011,7,4)) .to(date(2011,7,17)) .withNextMeetingRule() .build(); validateDates(new ScheduleBuilder().withHolidays(twoWeekNextWorkingDayHoliday) .withDayOfMonthEvent(2, 6) .withStartDate(date(2011,5,6)) .withNumberOfDates(3) .build(), date(2011, 6, 6), date(2011, 8, 8), date(2011, 10, 6)); } @Test public void biMonthlyByDayScheduleSecondDateInSameDayHolidayShouldNotShiftAnyDates() { Holiday twoWeekNextWorkingDayHoliday = new HolidayBuilder() .from(date(2011,7,4)) .to(date(2011,7,17)) .withSameDayAsRule() .build(); validateDates(new ScheduleBuilder().withHolidays(twoWeekNextWorkingDayHoliday) .withDayOfMonthEvent(2, 6) .withStartDate(date(2011,5,6)) .withNumberOfDates(3) .build(), date(2011, 6, 6), date(2011, 8, 8), date(2011, 10, 6)); } @Test public void biMonthlyByDayScheduleSecondDateMissesMoratoriumShouldNotShiftAnyDates() { //Moratorium does not include 13th of month -- no shifts except to adjust for next working day in August validateDates(new ScheduleBuilder().withHolidays(sevenDayMoratoriumStartingJuly4th) .withDayOfMonthEvent(1, 13) .withStartDate(date(2011,6,13)) .withNumberOfDates(3) .build(), date(2011, 6, 13), date(2011, 7, 13), date(2011, 8, 15)); } @Test public void shouldNotGoIntoRecursiveLoopWhenThroughDateOccursMoreThanTenPeriodsFromStartDate() { MeetingBO meeting = new MeetingBuilder().customerMeeting().weekly().every(1).build(); ScheduledEvent scheduledEvent = ScheduledEventFactory.createScheduledEventFrom(meeting); DateTime startDate = new DateTime().withYear(2010).withMonthOfYear(4).withDayOfMonth(1).toDateMidnight() .toDateTime(); DateTime throughDate = new DateTime().withYear(2010).withMonthOfYear(6).withDayOfMonth(21).toDateMidnight() .toDateTime(); scheduleGeneration.generateScheduledDatesThrough(startDate, throughDate, scheduledEvent, false); } @Test public void shouldNotGoIntoRecursiveLoopWhenThroughDateOccursExactlyTenPeriodsFromStartDate() { MeetingBO meeting = new MeetingBuilder().customerMeeting().weekly().every(1).build(); ScheduledEvent scheduledEvent = ScheduledEventFactory.createScheduledEventFrom(meeting); DateTime startDate = new DateTime().withYear(2010).withMonthOfYear(4).withDayOfMonth(1).toDateMidnight() .toDateTime(); DateTime throughDate = new DateTime().withYear(2010).withMonthOfYear(6).withDayOfMonth(7).toDateMidnight() .toDateTime(); scheduleGeneration.generateScheduledDatesThrough(startDate, throughDate, scheduledEvent, false); } /******************************* * Helper methods *******************************/ private void validateDates (List<DateTime> actualDates, DateTime... expectedDates) { assertThat (actualDates.size(), is (expectedDates.length)); for (short i = 0; i < actualDates.size(); i++) { assertThat (actualDates.get(i), is (expectedDates[i])); } } private DateTime date (int year, int month, int day) { return new DateTime().withDate(year, month, day).toDateMidnight().toDateTime(); } private class ScheduleBuilder { private List<Holiday> holidays = new ArrayList<Holiday>(); private List<Days> defaultWorkingDays = Arrays.asList(DayOfWeek.mondayAsDay(), DayOfWeek.tuesdayAsDay(), DayOfWeek.wednesdayAsDay(), DayOfWeek.thursdayAsDay(), DayOfWeek.fridayAsDay()); private List<Days> workingDays = defaultWorkingDays; private ScheduledEvent scheduledEvent; private int numberOfDatesToGenerate; private DateTime startDate; public List<DateTime> build() { HolidayAndWorkingDaysAndMoratoriaScheduledDateGeneration generator = new HolidayAndWorkingDaysAndMoratoriaScheduledDateGeneration(workingDays, holidays); return generator.generateScheduledDates(numberOfDatesToGenerate, startDate, scheduledEvent, false); } public ScheduleBuilder withWeeklyEvent (int recurrence, int dayOfWeek) { scheduledEvent = new ScheduledEventBuilder().every(recurrence).weeks().on(dayOfWeek).build(); return this; } public ScheduleBuilder withDayOfMonthEvent (int recurrence, int dayOfMonth) { scheduledEvent = new ScheduledEventBuilder().every(recurrence).months().onDayOfMonth(dayOfMonth).build(); return this; } public ScheduleBuilder withHolidays(Holiday...upComingHolidays) { holidays = Arrays.asList(upComingHolidays); return this; } public ScheduleBuilder withWorkingDays (List<Days> days) { workingDays = days; return this; } public ScheduleBuilder withStartDate (DateTime date) { this.startDate = date; return this; } public ScheduleBuilder withNumberOfDates (int dateCount) { this.numberOfDatesToGenerate = dateCount; return this; } } }