diff --git a/li.strolch.utils/src/main/java/li/strolch/utils/time/PeriodHelper.java b/li.strolch.utils/src/main/java/li/strolch/utils/time/PeriodHelper.java index 768a5e769..0c2518901 100644 --- a/li.strolch.utils/src/main/java/li/strolch/utils/time/PeriodHelper.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/time/PeriodHelper.java @@ -2,20 +2,16 @@ package li.strolch.utils.time; import static java.time.Period.between; +import java.time.Duration; +import java.time.Month; import java.time.Period; import java.time.ZonedDateTime; +import java.util.concurrent.TimeUnit; import li.strolch.utils.dbc.DBC; public class PeriodHelper { - public static double monthsIn(PeriodDuration periodDuration) { - long hours = periodDuration.getDuration().toHours(); - Period period = periodDuration.getPeriod(); - long months = period.toTotalMonths(); - return (months + (period.getDays() / 30.0) + (hours / 24.0 / 30.0)); - } - public static double daysIn(PeriodDuration periodDuration) { return (daysIn(periodDuration.getPeriod()) + (periodDuration.getDuration().toHours() / 24.0)); } @@ -24,14 +20,11 @@ public class PeriodHelper { return (period.getYears() * 365.0) + (period.getMonths() * 30.0) + period.getDays(); } - private static double monthsIn(Period period) { - return (period.getYears() * 12.0) + (period.getMonths()); - } - /** - * Special method to add the given number of months to the given date and making sure that the day always stays the - * same if possible. I.e. If the given date has the 3. day, then this will also be so on the returned date. But for - * the day 29, 30 or 31 the date might change to 28 or 30, depending on the month on which the result lies. + *

Special method to add the given number of months to the given date and making sure that the day always stays + * the same if possible. I.e. If the given date has the 3. day, then this will also be so on the returned date.

+ *

But for the day 28, 29, 30 or 31, if these are the last days of the given month, then the returned month is + * also shifted to the last day

* * @param date * the date to shift @@ -40,59 +33,223 @@ public class PeriodHelper { * * @return the shifted date */ - public static ZonedDateTime shiftMonths(ZonedDateTime date, int nrOfMonths) { + public static ZonedDateTime shiftMonths(ZonedDateTime date, long nrOfMonths) { int selectedDayOfMonth = date.getDayOfMonth(); ZonedDateTime next = date.plusMonths(nrOfMonths); + if (date.toLocalDate().lengthOfMonth() == selectedDayOfMonth) + return next.withDayOfMonth(next.toLocalDate().lengthOfMonth()); return next.withDayOfMonth(Math.min(selectedDayOfMonth, next.toLocalDate().lengthOfMonth())); } /** - * This special function allows us to shift a date by a multiple of the given {@link PeriodDuration} so that is - * before the given to date. It does multiple tries to get as close as possible, due to the inexactness of 30 days - * being one month, and 365 days being one year. + * Shifts the given date by the given period duration. This method doesn't accept a mixture of period and duration, + * one or the other must be zero. Durations may only contain hours and minutes. Periods must only have one part set, + * i.e. either year, month or day. Months are handles special in that the shifting is delegated to {@link + * #shiftMonths(ZonedDateTime, long)}. Furthermore, this method also specially handles weeks, i.e. if shifting is by + * multiple of 7, then this is handled as shifting by weeks * * @param date * the date to shift - * @param to - * the date before which to stop shifting * @param periodDuration - * the period shift in multiples by + * the period duration to shift the date by * * @return the shifted date */ - public static ZonedDateTime shiftByMultipleOfPeriod(ZonedDateTime date, ZonedDateTime to, - PeriodDuration periodDuration) { - DBC.PRE.assertTrue("date must be before to!", date.isBefore(to)); - DBC.PRE.assertFalse("period duration may not be null!", periodDuration.isZero()); + public static ZonedDateTime shiftDate(ZonedDateTime date, PeriodDuration periodDuration) { + DBC.PRE.assertNotNull("date may not be null!", date); + DBC.PRE.assertNotNull("periodDuration may not be null!", periodDuration); - // see if we need to shift by months - long monthsInPeriod = (long) monthsIn(periodDuration); - if (monthsInPeriod > 0) { - Period between = between(date.toLocalDate(), to.toLocalDate()); - double monthsInBetween = monthsIn(between); - long shifts = (long) (monthsInBetween / monthsInPeriod); - if (shifts > 0) { - date = date.plusMonths(shifts); - } - } - - Period between = between(date.toLocalDate(), to.toLocalDate()); - - double daysInBetween = daysIn(between); - double daysInPeriod = daysIn(periodDuration); - long shifts = (long) (daysInBetween / daysInPeriod); - if (shifts < 0) + if (periodDuration.isZero()) return date; - ZonedDateTime shiftedDate = date.plusDays((long) (shifts * daysInPeriod)); + Duration duration = periodDuration.getDuration(); + Period period = periodDuration.getPeriod(); - // see if we are close enough now - between = between(shiftedDate.toLocalDate(), to.toLocalDate()); - daysInBetween = daysIn(between); - shifts = (long) (daysInBetween / daysInPeriod); - if (shifts < 2) - return shiftedDate; + if (!period.isZero() && !duration.isZero()) + throw new UnsupportedOperationException( + "Shifting by Periods and Durations at the same time is not supported!"); - return shiftByMultipleOfPeriod(shiftedDate, to, periodDuration); + if (!period.isZero()) { + + // shift by years + if (period.getYears() > 0) { + + if (period.getMonths() != 0 && period.getDays() != 0) + throw new UnsupportedOperationException( + "Shifting by multi values not supported: " + periodDuration); + + int numberOfYears = period.getYears(); + ZonedDateTime shifted = date.plusYears(numberOfYears); + + // if the given month was on the last day of february, then keep the last day in the new year as well + if (date.getMonth() == Month.FEBRUARY && date.getDayOfMonth() == date.toLocalDate().lengthOfMonth()) + shifted = shifted.withDayOfMonth(shifted.toLocalDate().lengthOfMonth()); + + return shifted; + } + + // shift by months + if (period.getMonths() > 0) { + if (period.getDays() != 0) + throw new UnsupportedOperationException( + "Shifting by multi values not supported: " + periodDuration); + + int numberOfMonths = period.getMonths(); + // use special shift, so we keep end of month if applicable + return shiftMonths(date, numberOfMonths); + } + + // shift by weeks + if (period.getDays() % 7 == 0) { + int numberOfWeeks = period.getDays() / 7; + return date.plusWeeks(numberOfWeeks); + } + + // shift by days + int numberOfDays = period.getDays(); + return date.plusDays(numberOfDays); + } + + // shift by hours + long hours = duration.toHours(); + if (hours > 0) { + date = date.plusHours(hours); + duration = duration.minusHours(hours); + } + + // shift by minutes + long minutes = duration.toMinutes(); + if (minutes > 0) { + date = date.plusMinutes(minutes); + duration = duration.minusMinutes(minutes); + } + + if (duration.toSecondsPart() > 0 || duration.toMillisPart() > 0) + throw new UnsupportedOperationException("Only supporting hours and minutes: " + periodDuration); + + return date; + } + + /** + * This special function allows us end shift a start by a multiple of the given {@link PeriodDuration} so that is + * before the given end start. It does multiple tries end get as close as possible, due end the inexactness of 30 + * days being one month, and 365 days being one year. + * + * @param date + * the start date to shift before the end + * @param end + * the start before which end stop shifting + * @param periodDuration + * the period shift in multiples by + * + * @return the shifted start + */ + public static ZonedDateTime shiftByMultipleOfPeriod(ZonedDateTime date, ZonedDateTime end, + PeriodDuration periodDuration) { + DBC.PRE.assertTrue("date must be before end!", date.isBefore(end)); + DBC.PRE.assertFalse("period duration may not be null!", periodDuration.isZero()); + + Duration duration = periodDuration.getDuration(); + Period period = periodDuration.getPeriod(); + + if (!period.isZero() && !duration.isZero()) + throw new UnsupportedOperationException( + "Shifting by Periods and Durations at the same time is not supported!"); + + Period between = between(date.toLocalDate(), end.toLocalDate()); + if (between.isZero() || daysIn(between) < 1) + return date; + + // see if we need end shift by years + if (period.getYears() > 0 && period.getMonths() == 0 && period.getDays() == 0 && duration.isZero()) { + int numberOfYears = period.getYears(); + + // calculate the number of years to shift + long yearsBetween = between.getYears(); + long shifts = yearsBetween / numberOfYears; + long shiftYears = shifts * numberOfYears; + if (shiftYears == yearsBetween) + shiftYears -= numberOfYears; + + if (shiftYears < numberOfYears) + return date; + + return date.plusYears(shiftYears); + } + + // see if we need end shift by months + if (period.getMonths() > 0 && period.getYears() == 0 && period.getDays() == 0 && duration.isZero()) { + int numberOfMonths = period.getMonths(); + + // calculate the number of months to shift + long monthsBetween = between.toTotalMonths(); + // we will accept 30 days as a full month, and increase as well + if (between.getDays() >= 30) + monthsBetween += 1; + long shifts = monthsBetween / numberOfMonths; + long shiftMonths = shifts * numberOfMonths; + if (shiftMonths == monthsBetween) + shiftMonths -= numberOfMonths; + + if (shiftMonths < numberOfMonths) + return date; + + // use special shift, so we keep end of month if applicable + return shiftMonths(date, shiftMonths); + } + + double daysInPeriod = daysIn(periodDuration); + + // see if we need end shift by weeks + if (period.getDays() % 7 == 0 && daysInPeriod % 7 == 0) { + int numberOfWeeks = period.getDays() / 7; + + // calculate the number of weeks to shift + long daysInBetween = (long) daysIn(between); + long weeksBetween = daysInBetween / 7; + long shifts = weeksBetween / numberOfWeeks; + long shiftWeeks = shifts * numberOfWeeks; + + if (shiftWeeks < numberOfWeeks) + return date; + + return date.plusWeeks(shiftWeeks); + } + + // see if we are shifting simply by single days + // this includes 24h durations + if (daysInPeriod == 1.0) + return date.plus(between.minusDays(1)); + + double daysInBetween = daysIn(between); + + // e.g. period is P70D + if (daysInPeriod > daysInBetween) + return date; + + // if shifting by more than one day e.g. P2D or P75D + if (daysInPeriod > 1.0) { + + long shifts = (long) (daysInBetween / daysInPeriod); + long shiftDays = (long) (shifts * daysInPeriod); + if (shiftDays < 1) + return date; + + return date.plusDays(shiftDays); + } + + if (!period.isZero()) + throw new IllegalStateException( + "Expected period to be zero at this point and only duration to be set: " + periodDuration); + + // shifting by e.g. PT8H + long hoursInPeriod = duration.toHours(); + long hoursInBetween = TimeUnit.DAYS.toHours((long) daysInBetween); + long shifts = hoursInBetween / hoursInPeriod; + long shiftHours = shifts * hoursInPeriod; + if (shiftHours < 24) + return date; + + return date.plusDays(shiftHours / 24); } } diff --git a/li.strolch.utils/src/test/java/li/strolch/utils/time/PeriodHelperTest.java b/li.strolch.utils/src/test/java/li/strolch/utils/time/PeriodHelperTest.java index 6728fc076..9fe9e707d 100644 --- a/li.strolch.utils/src/test/java/li/strolch/utils/time/PeriodHelperTest.java +++ b/li.strolch.utils/src/test/java/li/strolch/utils/time/PeriodHelperTest.java @@ -1,14 +1,12 @@ package li.strolch.utils.time; import static java.time.ZoneId.systemDefault; +import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME; import static li.strolch.utils.time.PeriodHelper.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import java.time.LocalDate; -import java.time.Month; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; +import java.time.*; import org.junit.Test; @@ -70,23 +68,159 @@ public class PeriodHelperTest { } @Test - public void shouldCalcMonths1() { - assertEquals(1, monthsIn(PeriodDuration.parse("P1M")), 0.0); + public void shouldShiftDateYears1() { + ZonedDateTime date = LocalDate.of(2020, 1, 31).atStartOfDay(systemDefault()); + ZonedDateTime expected = LocalDate.of(2021, 1, 31).atStartOfDay(systemDefault()); + ZonedDateTime shifted = shiftDate(date, PeriodDuration.parse("P1Y")); + assertEquals(shifted.toString(), expected, shifted); } @Test - public void shouldCalcMonths2() { - assertEquals(1, monthsIn(PeriodDuration.parse("P30D")), 0.0); + public void shouldShiftDateYears2() { + ZonedDateTime date = LocalDate.of(2020, 1, 31).atStartOfDay(systemDefault()); + ZonedDateTime expected = LocalDate.of(2022, 1, 31).atStartOfDay(systemDefault()); + ZonedDateTime shifted = shiftDate(date, PeriodDuration.parse("P2Y")); + assertEquals(shifted.toString(), expected, shifted); } @Test - public void shouldCalcMonths3() { - assertEquals(2, monthsIn(PeriodDuration.parse("P60D")), 0.0); + public void shouldShiftDateYears3() { + ZonedDateTime date = LocalDate.of(2020, 2, 29).atStartOfDay(systemDefault()); + ZonedDateTime expected = LocalDate.of(2022, 2, 28).atStartOfDay(systemDefault()); + ZonedDateTime shifted = shiftDate(date, PeriodDuration.parse("P2Y")); + assertEquals(shifted.toString(), expected, shifted); } @Test - public void shouldCalcMonths4() { - assertEquals(12, monthsIn(PeriodDuration.parse("P1Y")), 0.0); + public void shouldShiftDateYears4() { + ZonedDateTime date = LocalDate.of(2018, 2, 28).atStartOfDay(systemDefault()); + ZonedDateTime expected = LocalDate.of(2020, 2, 29).atStartOfDay(systemDefault()); + ZonedDateTime shifted = shiftDate(date, PeriodDuration.parse("P2Y")); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldShiftDateMonth1() { + ZonedDateTime date = LocalDate.of(2018, 2, 28).atStartOfDay(systemDefault()); + ZonedDateTime expected = LocalDate.of(2018, 4, 30).atStartOfDay(systemDefault()); + ZonedDateTime shifted = shiftDate(date, PeriodDuration.parse("P2M")); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldShiftDateMonth2() { + ZonedDateTime date = LocalDate.of(2018, 2, 28).atStartOfDay(systemDefault()); + ZonedDateTime expected = LocalDate.of(2018, 5, 31).atStartOfDay(systemDefault()); + ZonedDateTime shifted = shiftDate(date, PeriodDuration.parse("P3M")); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldShiftDateMonth3() { + ZonedDateTime date = LocalDate.of(2018, 5, 3).atStartOfDay(systemDefault()); + ZonedDateTime expected = LocalDate.of(2018, 8, 3).atStartOfDay(systemDefault()); + ZonedDateTime shifted = shiftDate(date, PeriodDuration.parse("P3M")); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldShiftDateDay1() { + ZonedDateTime date = LocalDate.of(2018, 5, 3).atStartOfDay(systemDefault()); + ZonedDateTime expected = LocalDate.of(2018, 5, 6).atStartOfDay(systemDefault()); + ZonedDateTime shifted = shiftDate(date, PeriodDuration.parse("P3D")); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldShiftDateDay2() { + ZonedDateTime date = LocalDate.of(2018, 5, 3).atStartOfDay(systemDefault()); + ZonedDateTime expected = LocalDate.of(2018, 5, 13).atStartOfDay(systemDefault()); + ZonedDateTime shifted = shiftDate(date, PeriodDuration.parse("P10D")); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldShiftDateDay3() { + ZonedDateTime date = LocalDate.of(2018, 5, 29).atStartOfDay(systemDefault()); + ZonedDateTime expected = LocalDate.of(2018, 6, 8).atStartOfDay(systemDefault()); + ZonedDateTime shifted = shiftDate(date, PeriodDuration.parse("P10D")); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldShiftDateWeek1() { + ZonedDateTime date = LocalDate.of(2018, 5, 29).atStartOfDay(systemDefault()); + ZonedDateTime expected = LocalDate.of(2018, 6, 5).atStartOfDay(systemDefault()); + ZonedDateTime shifted = shiftDate(date, PeriodDuration.parse("P7D")); + assertEquals(shifted.toString(), expected, shifted); + assertEquals(shifted.toString(), expected.getDayOfWeek(), date.getDayOfWeek()); + } + + @Test + public void shouldShiftDateWeek2() { + ZonedDateTime date = LocalDate.of(2018, 5, 29).atStartOfDay(systemDefault()); + ZonedDateTime expected = LocalDate.of(2018, 6, 5).atStartOfDay(systemDefault()); + ZonedDateTime shifted = shiftDate(date, PeriodDuration.parse("P1W")); + assertEquals(shifted.toString(), expected, shifted); + assertEquals(shifted.toString(), expected.getDayOfWeek(), date.getDayOfWeek()); + } + + @Test + public void shouldShiftDateWeek3() { + ZonedDateTime date = LocalDate.of(2018, 5, 29).atStartOfDay(systemDefault()); + ZonedDateTime expected = LocalDate.of(2018, 6, 12).atStartOfDay(systemDefault()); + ZonedDateTime shifted = shiftDate(date, PeriodDuration.parse("P2W")); + assertEquals(shifted.toString(), expected, shifted); + assertEquals(shifted.toString(), expected.getDayOfWeek(), date.getDayOfWeek()); + } + + @Test + public void shouldShiftDateWeek4() { + ZonedDateTime date = LocalDate.of(2018, 5, 29).atStartOfDay(systemDefault()); + ZonedDateTime expected = LocalDate.of(2018, 6, 12).atStartOfDay(systemDefault()); + ZonedDateTime shifted = shiftDate(date, PeriodDuration.parse("P14D")); + assertEquals(shifted.toString(), expected, shifted); + assertEquals(shifted.toString(), expected.getDayOfWeek(), date.getDayOfWeek()); + } + + @Test + public void shouldShiftDateHour1() { + ZonedDateTime date = LocalDate.of(2018, 5, 29).atTime(LocalTime.of(10, 0)).atZone(systemDefault()); + ZonedDateTime expected = LocalDate.of(2018, 5, 29).atTime(LocalTime.of(18, 0)).atZone(systemDefault()); + ZonedDateTime shifted = shiftDate(date, PeriodDuration.parse("PT8H")); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldShiftDateHour2() { + ZonedDateTime date = LocalDate.of(2018, 5, 29).atTime(LocalTime.of(22, 0)).atZone(systemDefault()); + ZonedDateTime expected = LocalDate.of(2018, 5, 30).atTime(LocalTime.of(6, 0)).atZone(systemDefault()); + ZonedDateTime shifted = shiftDate(date, PeriodDuration.parse("PT8H")); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldShiftDateHour3() { + ZonedDateTime date = LocalDate.of(2018, 5, 29).atTime(LocalTime.of(22, 0)).atZone(systemDefault()); + ZonedDateTime expected = LocalDate.of(2018, 5, 30).atTime(LocalTime.of(22, 0)).atZone(systemDefault()); + ZonedDateTime shifted = shiftDate(date, PeriodDuration.parse("PT24H")); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldShiftDateMinute1() { + ZonedDateTime date = LocalDate.of(2018, 5, 29).atTime(LocalTime.of(22, 0)).atZone(systemDefault()); + ZonedDateTime expected = LocalDate.of(2018, 5, 29).atTime(LocalTime.of(22, 30)).atZone(systemDefault()); + ZonedDateTime shifted = shiftDate(date, PeriodDuration.parse("PT30M")); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldShiftDateMinute2() { + ZonedDateTime date = LocalDate.of(2018, 5, 29).atTime(LocalTime.of(22, 45)).atZone(systemDefault()); + ZonedDateTime expected = LocalDate.of(2018, 5, 29).atTime(LocalTime.of(23, 0)).atZone(systemDefault()); + ZonedDateTime shifted = shiftDate(date, PeriodDuration.parse("PT15M")); + assertEquals(shifted.toString(), expected, shifted); } @Test @@ -110,118 +244,46 @@ public class PeriodHelperTest { } @Test - public void shouldPlusMonths28() { - int expectedDay = 28; - - ZonedDateTime date = LocalDate.of(2001, Month.JANUARY, expectedDay).atStartOfDay(systemDefault()); - assertEquals(expectedDay, date.getDayOfMonth()); - - // increase by single months - for (int i = 0; i < 5; i++) { - date = shiftMonths(date, 1); - assertEquals(expectedDay, date.getDayOfMonth()); - } - - // also when increase by multiple months - for (int i = 0; i < 5; i++) { - date = shiftMonths(date, 4); - assertEquals(expectedDay, date.getDayOfMonth()); - } - } - - @Test - public void shouldPlusMonths29() { - int expectedDay = 29; + public void shouldPlusMonthsLast() { ZonedDateTime date; // increase by single months - for (int i = 0; i < 5; i++) { - date = LocalDate.of(2001, Month.JANUARY, expectedDay).atStartOfDay(systemDefault()); - date = shiftMonths(date, i); - if (date.getMonth() == Month.FEBRUARY) - assertEquals(28, date.getDayOfMonth()); - else - assertEquals(expectedDay, date.getDayOfMonth()); - } + date = LocalDate.of(2020, 1, 31).atStartOfDay(systemDefault()); + + // february + date = shiftMonths(date, 1); + assertEquals(date.toString(), Month.FEBRUARY, date.getMonth()); + assertEquals(date.toString(), Month.FEBRUARY.length(true), date.getDayOfMonth()); + + // march + date = shiftMonths(date, 1); + assertEquals(date.toString(), Month.MARCH, date.getMonth()); + assertEquals(date.toString(), Month.MARCH.length(true), date.getDayOfMonth()); + + // april + date = shiftMonths(date, 1); + assertEquals(date.toString(), Month.APRIL, date.getMonth()); + assertEquals(date.toString(), Month.APRIL.length(true), date.getDayOfMonth()); + + // may + date = shiftMonths(date, 1); + assertEquals(date.toString(), Month.MAY, date.getMonth()); + assertEquals(date.toString(), Month.MAY.length(true), date.getDayOfMonth()); // also when increase by multiple months - for (int i = 0; i < 5; i++) { - date = LocalDate.of(2001, Month.JANUARY, expectedDay).atStartOfDay(systemDefault()); - date = shiftMonths(date, i); - if (date.getMonth() == Month.FEBRUARY) - assertEquals(28, date.getDayOfMonth()); - else - assertEquals(expectedDay, date.getDayOfMonth()); - } - } + date = LocalDate.of(2020, 1, 31).atStartOfDay(systemDefault()); - @Test - public void shouldPlusMonths30() { - int expectedDay = 30; + // march + date = shiftMonths(date, 2); + assertEquals(date.toString(), Month.MARCH, date.getMonth()); + assertEquals(date.toString(), Month.MARCH.length(true), date.getDayOfMonth()); - ZonedDateTime date = LocalDate.of(2001, Month.JANUARY, expectedDay).atStartOfDay(systemDefault()); - assertEquals(expectedDay, date.getDayOfMonth()); + // june + date = shiftMonths(date, 3); + assertEquals(date.toString(), Month.JUNE, date.getMonth()); + assertEquals(date.toString(), Month.JUNE.length(true), date.getDayOfMonth()); - // increase by single months - for (int i = 0; i < 5; i++) { - date = LocalDate.of(2001, Month.JANUARY, expectedDay).atStartOfDay(systemDefault()); - date = shiftMonths(date, i); - if (date.getMonth() == Month.FEBRUARY) - assertEquals(28, date.getDayOfMonth()); - else - assertEquals(expectedDay, date.getDayOfMonth()); - } - - // also when increase by multiple months - for (int i = 0; i < 5; i++) { - date = LocalDate.of(2001, Month.JANUARY, expectedDay).atStartOfDay(systemDefault()); - date = shiftMonths(date, i); - if (date.getMonth() == Month.FEBRUARY) - assertEquals(28, date.getDayOfMonth()); - else - assertEquals(expectedDay, date.getDayOfMonth()); - } - } - - @Test - public void shouldPlusMonths31() { - int expectedDay = 31; - - ZonedDateTime date = LocalDate.of(2001, Month.JANUARY, expectedDay).atStartOfDay(systemDefault()); - assertEquals(date.getMonth().name(), expectedDay, date.getDayOfMonth()); - - // increase by single months - for (int i = 0; i < 5; i++) { - date = LocalDate.of(2001, Month.JANUARY, expectedDay).atStartOfDay(systemDefault()); - date = shiftMonths(date, i); - if (date.getMonth() == Month.FEBRUARY) { - assertEquals(date.getMonth().name(), 28, date.getDayOfMonth()); - } else if (date.getMonth() == Month.APRIL // - || date.getMonth() == Month.JUNE // - || date.getMonth() == Month.SEPTEMBER // - || date.getMonth() == Month.NOVEMBER) { - assertEquals(date.getMonth().name(), 30, date.getDayOfMonth()); - } else { - assertEquals(date.getMonth().name(), expectedDay, date.getDayOfMonth()); - } - } - - // also when increase by multiple months - for (int i = 0; i < 5; i++) { - date = LocalDate.of(2001, Month.JANUARY, expectedDay).atStartOfDay(systemDefault()); - date = shiftMonths(date, i); - if (date.getMonth() == Month.FEBRUARY) { - assertEquals(date.getMonth().name(), 28, date.getDayOfMonth()); - } else if (date.getMonth() == Month.APRIL // - || date.getMonth() == Month.JUNE // - || date.getMonth() == Month.SEPTEMBER // - || date.getMonth() == Month.NOVEMBER) { - assertEquals(date.getMonth().name(), 30, date.getDayOfMonth()); - } else { - assertEquals(date.getMonth().name(), expectedDay, date.getDayOfMonth()); - } - } } @Test @@ -231,23 +293,19 @@ public class PeriodHelperTest { PeriodDuration periodDuration = PeriodDuration.parse("P1M"); ZonedDateTime shiftedDate = shiftByMultipleOfPeriod(past, now, periodDuration); - assertTrue(shiftedDate.isAfter(now.minusDays(10))); assertTrue(shiftedDate.isBefore(now)); + assertTrue(shiftedDate.toString(), shiftedDate.isAfter(now.minusMonths(2))); } @Test public void shouldCalcShiftDays2() { - ZonedDateTime past = ZonedDateTime - .parse("2007-12-03T10:15:30+01:00", DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(systemDefault())); + ZonedDateTime past = LocalDate.of(2007, 12, 3).atTime(LocalTime.now()).atZone(systemDefault()); ZonedDateTime now = ZonedDateTime.now(); PeriodDuration periodDuration = PeriodDuration.parse("P1M"); ZonedDateTime shiftedDate = shiftByMultipleOfPeriod(past, now, periodDuration); - - // since P1M ist = 30 days, we have a rather inexact match, but it must certainly be - // after now() - P1M + 5 - // before now() - assertTrue(shiftedDate.isAfter(now.minusDays(35))); - assertTrue(shiftedDate.isBefore(now)); + assertEquals(shiftedDate.toString(), 3, shiftedDate.getDayOfMonth()); + assertTrue(shiftedDate.toString(), shiftedDate.isBefore(now)); + assertTrue(shiftedDate.toString(), shiftedDate.isAfter(now.minusMonths(2))); } @Test @@ -257,19 +315,314 @@ public class PeriodHelperTest { PeriodDuration periodDuration = PeriodDuration.parse("P7D"); ZonedDateTime shiftedDate = shiftByMultipleOfPeriod(past, now, periodDuration); - assertTrue(shiftedDate.isAfter(now.minusDays(9))); + assertTrue(shiftedDate.toString(), shiftedDate.isAfter(now.minusDays(9))); assertTrue(shiftedDate.isBefore(now)); + assertEquals(past.getDayOfWeek(), shiftedDate.getDayOfWeek()); } @Test public void shouldCalcShiftDays4() { ZonedDateTime past = ZonedDateTime - .parse("2007-12-03T10:15:30+01:00", DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(systemDefault())); + .parse("2007-12-03T10:15:30+01:00", ISO_OFFSET_DATE_TIME.withZone(systemDefault())); ZonedDateTime now = ZonedDateTime.now(); PeriodDuration periodDuration = PeriodDuration.parse("P7D"); ZonedDateTime shiftedDate = shiftByMultipleOfPeriod(past, now, periodDuration); // since we are many years and months before now, and a year is 356 days and a month is 28 days, we inexact, but at least we must be a more than P7D before now - assertTrue(shiftedDate.isBefore(now)); + assertTrue(shiftedDate.toString(), shiftedDate.isBefore(now)); + } + + @Test + public void shouldCalcShift7Days1Fixed() { + ZonedDateTime date1 = LocalDate.of(2017, 10, 30).atStartOfDay(systemDefault()); + ZonedDateTime date2 = LocalDate.of(2020, 1, 2).atStartOfDay(systemDefault()); + + ZonedDateTime shifted = shiftByMultipleOfPeriod(date1, date2, PeriodDuration.parse("P7D")); + assertEquals(date1.getDayOfWeek(), shifted.getDayOfWeek()); + + ZonedDateTime expected = LocalDate.of(2019, 12, 30).atStartOfDay(ZoneId.systemDefault()); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldCalcShift7Days2Fixed() { + ZonedDateTime date1 = LocalDate.of(2017, 10, 30).atStartOfDay(systemDefault()); + ZonedDateTime date2 = LocalDate.of(2021, 4, 28).atStartOfDay(systemDefault()); + + ZonedDateTime shifted = shiftByMultipleOfPeriod(date1, date2, PeriodDuration.parse("P7D")); + assertEquals(date1.getDayOfWeek(), shifted.getDayOfWeek()); + + ZonedDateTime expected = LocalDate.of(2021, 4, 26).atStartOfDay(ZoneId.systemDefault()); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldCalcShift7Days3Fixed() { + ZonedDateTime date1 = LocalDate.of(2021, 4, 21).atStartOfDay(systemDefault()); + ZonedDateTime date2 = LocalDate.of(2021, 4, 28).atStartOfDay(systemDefault()); + + ZonedDateTime shifted = shiftByMultipleOfPeriod(date1, date2, PeriodDuration.parse("P7D")); + assertEquals(date1.getDayOfWeek(), shifted.getDayOfWeek()); + + ZonedDateTime expected = LocalDate.of(2021, 4, 28).atStartOfDay(ZoneId.systemDefault()); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldCalcShift1Days1Fixed() { + ZonedDateTime date1 = LocalDate.of(2017, 10, 30).atStartOfDay(systemDefault()); + ZonedDateTime date2 = LocalDate.of(2020, 1, 2).atStartOfDay(systemDefault()); + + ZonedDateTime shifted = shiftByMultipleOfPeriod(date1, date2, PeriodDuration.parse("P1D")); + + ZonedDateTime expected = LocalDate.of(2020, 1, 1).atStartOfDay(ZoneId.systemDefault()); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldCalcShift1Days2Fixed() { + ZonedDateTime date1 = LocalDate.of(2017, 10, 30).atStartOfDay(systemDefault()); + ZonedDateTime date2 = LocalDate.of(2021, 4, 28).atStartOfDay(systemDefault()); + + ZonedDateTime shifted = shiftByMultipleOfPeriod(date1, date2, PeriodDuration.parse("P1D")); + + ZonedDateTime expected = LocalDate.of(2021, 4, 27).atStartOfDay(ZoneId.systemDefault()); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldCalcShift1Days3Fixed() { + ZonedDateTime date1 = LocalDate.of(2021, 4, 21).atStartOfDay(systemDefault()); + ZonedDateTime date2 = LocalDate.of(2021, 4, 28).atStartOfDay(systemDefault()); + + ZonedDateTime shifted = shiftByMultipleOfPeriod(date1, date2, PeriodDuration.parse("P1D")); + + ZonedDateTime expected = LocalDate.of(2021, 4, 27).atStartOfDay(ZoneId.systemDefault()); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldCalcShiftYears1Fixed() { + ZonedDateTime d1 = LocalDate.of(2007, 6, 3).atStartOfDay(ZoneId.systemDefault()); + ZonedDateTime d2 = LocalDate.of(2020, 6, 3).atStartOfDay(ZoneId.systemDefault()); + + ZonedDateTime shifted = shiftByMultipleOfPeriod(d1, d2, PeriodDuration.parse("P1Y")); + + ZonedDateTime expected = LocalDate.of(2019, 6, 3).atStartOfDay(ZoneId.systemDefault()); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldCalcShiftYears2Fixed() { + ZonedDateTime d1 = LocalDate.of(2007, 6, 3).atStartOfDay(ZoneId.systemDefault()); + ZonedDateTime d2 = LocalDate.of(2020, 6, 3).atStartOfDay(ZoneId.systemDefault()); + + ZonedDateTime shifted = shiftByMultipleOfPeriod(d1, d2, PeriodDuration.parse("P2Y")); + + ZonedDateTime expected = LocalDate.of(2019, 6, 3).atStartOfDay(ZoneId.systemDefault()); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldCalcShiftMonths1Fixed() { + ZonedDateTime d1 = LocalDate.of(2007, 6, 3).atStartOfDay(ZoneId.systemDefault()); + ZonedDateTime d2 = LocalDate.of(2020, 6, 3).atStartOfDay(ZoneId.systemDefault()); + + ZonedDateTime shifted = shiftByMultipleOfPeriod(d1, d2, PeriodDuration.parse("P1M")); + + ZonedDateTime expected = LocalDate.of(2020, 5, 3).atStartOfDay(ZoneId.systemDefault()); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldCalcShiftMonths2Fixed() { + ZonedDateTime d1 = LocalDate.of(2007, 6, 3).atStartOfDay(ZoneId.systemDefault()); + ZonedDateTime d2 = LocalDate.of(2020, 7, 3).atStartOfDay(ZoneId.systemDefault()); + + ZonedDateTime shifted = shiftByMultipleOfPeriod(d1, d2, PeriodDuration.parse("P1M")); + + ZonedDateTime expected = LocalDate.of(2020, 6, 3).atStartOfDay(ZoneId.systemDefault()); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldCalcShiftMonths3Fixed() { + ZonedDateTime d1 = LocalDate.of(2007, 6, 3).atStartOfDay(ZoneId.systemDefault()); + ZonedDateTime d2 = LocalDate.of(2020, 6, 2).atStartOfDay(ZoneId.systemDefault()); + + ZonedDateTime shifted = shiftByMultipleOfPeriod(d1, d2, PeriodDuration.parse("P1M")); + + ZonedDateTime expected = LocalDate.of(2020, 5, 3).atStartOfDay(ZoneId.systemDefault()); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldCalcShiftMonths4Fixed() { + ZonedDateTime d1 = LocalDate.of(2017, 1, 31).atStartOfDay(ZoneId.systemDefault()); + ZonedDateTime d2 = LocalDate.of(2020, 7, 30).atStartOfDay(ZoneId.systemDefault()); + + ZonedDateTime shifted = shiftByMultipleOfPeriod(d1, d2, PeriodDuration.parse("P1M")); + + ZonedDateTime expected = LocalDate.of(2020, 6, 30).atStartOfDay(ZoneId.systemDefault()); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldCalcShiftMonths5Fixed() { + ZonedDateTime d1 = LocalDate.of(2017, 2, 28).atStartOfDay(ZoneId.systemDefault()); + ZonedDateTime d2 = LocalDate.of(2020, 7, 30).atStartOfDay(ZoneId.systemDefault()); + + ZonedDateTime shifted = shiftByMultipleOfPeriod(d1, d2, PeriodDuration.parse("P1M")); + + ZonedDateTime expected = LocalDate.of(2020, 6, 30).atStartOfDay(ZoneId.systemDefault()); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldCalcShiftMonths6Fixed() { + ZonedDateTime d1 = LocalDate.of(2020, 2, 29).atStartOfDay(ZoneId.systemDefault()); + ZonedDateTime d2 = LocalDate.of(2020, 7, 30).atStartOfDay(ZoneId.systemDefault()); + + ZonedDateTime shifted = shiftByMultipleOfPeriod(d1, d2, PeriodDuration.parse("P1M")); + + ZonedDateTime expected = LocalDate.of(2020, 6, 30).atStartOfDay(ZoneId.systemDefault()); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldCalcShiftMonths7Fixed() { + ZonedDateTime d1 = LocalDate.of(2017, 1, 31).atStartOfDay(ZoneId.systemDefault()); + ZonedDateTime d2 = LocalDate.of(2020, 7, 30).atStartOfDay(ZoneId.systemDefault()); + + ZonedDateTime shifted = shiftByMultipleOfPeriod(d1, d2, PeriodDuration.parse("P3M")); + + ZonedDateTime expected = LocalDate.of(2020, 4, 30).atStartOfDay(ZoneId.systemDefault()); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldCalcShiftWeek1Fixed() { + ZonedDateTime d1 = LocalDate.of(2021, 4, 6).atStartOfDay(ZoneId.systemDefault()); + ZonedDateTime d2 = LocalDate.of(2021, 4, 28).atStartOfDay(ZoneId.systemDefault()); + + ZonedDateTime shifted = shiftByMultipleOfPeriod(d1, d2, PeriodDuration.parse("P1W")); + assertEquals(d1.getDayOfWeek(), shifted.getDayOfWeek()); + + ZonedDateTime expected = LocalDate.of(2021, 4, 27).atStartOfDay(ZoneId.systemDefault()); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldCalcShiftWeek2Fixed() { + ZonedDateTime d1 = LocalDate.of(2021, 4, 6).atStartOfDay(ZoneId.systemDefault()); + ZonedDateTime d2 = LocalDate.of(2021, 4, 27).atStartOfDay(ZoneId.systemDefault()); + + ZonedDateTime shifted = shiftByMultipleOfPeriod(d1, d2, PeriodDuration.parse("P1W")); + assertEquals(d1.getDayOfWeek(), shifted.getDayOfWeek()); + + ZonedDateTime expected = LocalDate.of(2021, 4, 27).atStartOfDay(ZoneId.systemDefault()); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldCalcShiftWeek3Fixed() { + ZonedDateTime d1 = LocalDate.of(2021, 4, 24).atStartOfDay(ZoneId.systemDefault()); + ZonedDateTime d2 = LocalDate.of(2021, 4, 27).atStartOfDay(ZoneId.systemDefault()); + + ZonedDateTime shifted = shiftByMultipleOfPeriod(d1, d2, PeriodDuration.parse("P7D")); + assertEquals(d1.getDayOfWeek(), shifted.getDayOfWeek()); + + ZonedDateTime expected = LocalDate.of(2021, 4, 24).atStartOfDay(ZoneId.systemDefault()); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldCalcShiftWeek4Fixed() { + ZonedDateTime d1 = LocalDate.of(2019, 8, 13).atStartOfDay(ZoneId.systemDefault()); + ZonedDateTime d2 = LocalDate.of(2021, 4, 27).atStartOfDay(ZoneId.systemDefault()); + + ZonedDateTime shifted = shiftByMultipleOfPeriod(d1, d2, PeriodDuration.parse("P2W")); + assertEquals(d1.getDayOfWeek(), shifted.getDayOfWeek()); + + ZonedDateTime expected = LocalDate.of(2021, 4, 20).atStartOfDay(ZoneId.systemDefault()); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldCalcShiftWeek5Fixed() { + ZonedDateTime d1 = LocalDate.of(2021, 4, 24).atStartOfDay(ZoneId.systemDefault()); + ZonedDateTime d2 = LocalDate.of(2021, 4, 27).atStartOfDay(ZoneId.systemDefault()); + + ZonedDateTime shifted = shiftByMultipleOfPeriod(d1, d2, PeriodDuration.parse("P14D")); + assertEquals(d1.getDayOfWeek(), shifted.getDayOfWeek()); + + ZonedDateTime expected = LocalDate.of(2021, 4, 24).atStartOfDay(ZoneId.systemDefault()); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldCalcShiftDay1Fixed() { + ZonedDateTime d1 = LocalDate.of(2021, 4, 24).atStartOfDay(ZoneId.systemDefault()); + ZonedDateTime d2 = LocalDate.of(2021, 4, 27).atStartOfDay(ZoneId.systemDefault()); + + ZonedDateTime shifted = shiftByMultipleOfPeriod(d1, d2, PeriodDuration.parse("P1D")); + + ZonedDateTime expected = LocalDate.of(2021, 4, 26).atStartOfDay(ZoneId.systemDefault()); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldCalcShiftDay2Fixed() { + ZonedDateTime d1 = LocalDate.of(2017, 1, 5).atStartOfDay(ZoneId.systemDefault()); + ZonedDateTime d2 = LocalDate.of(2021, 4, 27).atStartOfDay(ZoneId.systemDefault()); + + ZonedDateTime shifted = shiftByMultipleOfPeriod(d1, d2, PeriodDuration.parse("P1D")); + + ZonedDateTime expected = LocalDate.of(2021, 4, 26).atStartOfDay(ZoneId.systemDefault()); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldCalcShiftDay3Fixed() { + ZonedDateTime d1 = LocalDate.of(2017, 1, 5).atStartOfDay(ZoneId.systemDefault()); + ZonedDateTime d2 = LocalDate.of(2021, 4, 27).atStartOfDay(ZoneId.systemDefault()); + + ZonedDateTime shifted = shiftByMultipleOfPeriod(d1, d2, PeriodDuration.parse("P75D")); + + ZonedDateTime expected = LocalDate.of(2021, 2, 13).atStartOfDay(ZoneId.systemDefault()); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldCalcShiftDay4Fixed() { + ZonedDateTime d1 = LocalDate.of(2018, 1, 5).atStartOfDay(ZoneId.systemDefault()); + ZonedDateTime d2 = LocalDate.of(2021, 4, 26).atStartOfDay(ZoneId.systemDefault()); + + ZonedDateTime shifted = shiftByMultipleOfPeriod(d1, d2, PeriodDuration.parse("P75D")); + + ZonedDateTime expected = LocalDate.of(2021, 4, 19).atStartOfDay(ZoneId.systemDefault()); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldCalcShiftHours1Fixed() { + ZonedDateTime d1 = LocalDate.of(2017, 1, 5).atStartOfDay(ZoneId.systemDefault()); + ZonedDateTime d2 = LocalDate.of(2021, 4, 27).atStartOfDay(ZoneId.systemDefault()); + + ZonedDateTime shifted = shiftByMultipleOfPeriod(d1, d2, PeriodDuration.parse("PT8H")); + + ZonedDateTime expected = LocalDate.of(2021, 4, 26).atStartOfDay(ZoneId.systemDefault()); + assertEquals(shifted.toString(), expected, shifted); + } + + @Test + public void shouldCalcShiftHours2Fixed() { + ZonedDateTime d1 = LocalDate.of(2017, 1, 10).atStartOfDay(ZoneId.systemDefault()); + ZonedDateTime d2 = LocalDate.of(2021, 4, 27).atTime(LocalTime.of(12, 47)).atZone(systemDefault()); + + ZonedDateTime shifted = shiftByMultipleOfPeriod(d1, d2, PeriodDuration.parse("PT8H")); + + ZonedDateTime expected = LocalDate.of(2021, 4, 26).atStartOfDay(ZoneId.systemDefault()); + assertEquals(shifted.toString(), expected, shifted); } }