Skip to content

Commit

Permalink
Refactor roll forward in CronField
Browse files Browse the repository at this point in the history
Before this commit, CronField.Type::rollForward added temporal units
to reach the higher order field. This caused issues with DST, where
the added amount of hours was either too small or too large.

This commit refactors the implementation so that it now adds one to the
higher order field, and reset the current field to the minimum value.

Closes gh-28095
  • Loading branch information
poutsma committed Feb 24, 2022
1 parent 453c6d4 commit 7e2106b
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 19 deletions.
Expand Up @@ -18,6 +18,7 @@

import java.time.DateTimeException;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.time.temporal.Temporal;
import java.time.temporal.ValueRange;
import java.util.function.BiFunction;
Expand Down Expand Up @@ -168,22 +169,25 @@ protected static <T extends Temporal & Comparable<? super T>> T cast(Temporal te
* day-of-month, month, day-of-week.
*/
protected enum Type {
NANO(ChronoField.NANO_OF_SECOND),
SECOND(ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND),
MINUTE(ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND),
HOUR(ChronoField.HOUR_OF_DAY, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND),
DAY_OF_MONTH(ChronoField.DAY_OF_MONTH, ChronoField.HOUR_OF_DAY, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND),
MONTH(ChronoField.MONTH_OF_YEAR, ChronoField.DAY_OF_MONTH, ChronoField.HOUR_OF_DAY, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND),
DAY_OF_WEEK(ChronoField.DAY_OF_WEEK, ChronoField.HOUR_OF_DAY, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND);
NANO(ChronoField.NANO_OF_SECOND, ChronoUnit.SECONDS),
SECOND(ChronoField.SECOND_OF_MINUTE, ChronoUnit.MINUTES, ChronoField.NANO_OF_SECOND),
MINUTE(ChronoField.MINUTE_OF_HOUR, ChronoUnit.HOURS, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND),
HOUR(ChronoField.HOUR_OF_DAY, ChronoUnit.DAYS, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND),
DAY_OF_MONTH(ChronoField.DAY_OF_MONTH, ChronoUnit.MONTHS, ChronoField.HOUR_OF_DAY, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND),
MONTH(ChronoField.MONTH_OF_YEAR, ChronoUnit.YEARS, ChronoField.DAY_OF_MONTH, ChronoField.HOUR_OF_DAY, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND),
DAY_OF_WEEK(ChronoField.DAY_OF_WEEK, ChronoUnit.WEEKS, ChronoField.HOUR_OF_DAY, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND);


private final ChronoField field;

private final ChronoUnit higherOrder;

private final ChronoField[] lowerOrders;


Type(ChronoField field, ChronoField... lowerOrders) {
Type(ChronoField field, ChronoUnit higherOrder, ChronoField... lowerOrders) {
this.field = field;
this.higherOrder = higherOrder;
this.lowerOrders = lowerOrders;
}

Expand Down Expand Up @@ -266,17 +270,9 @@ public <T extends Temporal & Comparable<? super T>> T elapseUntil(T temporal, in
* @return the rolled forward temporal
*/
public <T extends Temporal & Comparable<? super T>> T rollForward(T temporal) {
int current = get(temporal);
ValueRange range = temporal.range(this.field);
long amount = range.getMaximum() - current + 1;
T result = this.field.getBaseUnit().addTo(temporal, amount);
current = get(result);
range = result.range(this.field);
// adjust for daylight savings
if (current != range.getMinimum()) {
result = this.field.adjustInto(result, range.getMinimum());
}
return result;
T result = this.higherOrder.addTo(temporal, 1);
ValueRange range = result.range(this.field);
return this.field.adjustInto(result, range.getMinimum());
}

/**
Expand Down
Expand Up @@ -1344,6 +1344,14 @@ public void daylightSaving() {
actual = cronExpression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);

cronExpression = CronExpression.parse("0 5 0 * * *");

last = ZonedDateTime.parse("2019-10-27T01:05+02:00[Europe/Amsterdam]");
expected = ZonedDateTime.parse("2019-10-28T00:05+01:00[Europe/Amsterdam]");
actual = cronExpression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
}

@Test
Expand Down

0 comments on commit 7e2106b

Please sign in to comment.