What is the equivalent of Calendar.roll in java.time?

I was studying the old Calendar API to see how bad it was, and I found out that Calendar has a roll method. Unlike the add method, roll does not change the values of bigger calendar fields.

For example, the calendar instance c represents the date 2019-08-31. Calling c.roll(Calendar.MONTH, 13) adds 13 to the month field, but does not change the year, so the result is 2019-09-30. Note that the day of month changes, because it is a smaller field.

Related

I tried to find such a method in the modern java.time API. I thought such a method has to be in LocalDate or LocalDateTime, but I found nothing of the sort.

So I tried to write my own roll method:

public static LocalDateTime roll(LocalDateTime ldt, TemporalField unit, long amount) {
    LocalDateTime newLdt = ldt.plus(amount, unit.getBaseUnit());
    return ldt.with(unit, newLdt.get(unit));
}

However, this only works for some cases, but not others. For example, it does not work for the case described in the documentation here:

Consider a GregorianCalendar originally set to Sunday June 6, 1999.
Calling roll(Calendar.WEEK_OF_MONTH, -1) sets the calendar to Tuesday
June 1, 1999, whereas calling add(Calendar.WEEK_OF_MONTH, -1) sets the
calendar to Sunday May 30, 1999. This is because the roll rule imposes
an additional constraint: The MONTH must not change when the
WEEK_OF_MONTH is rolled. Taken together with add rule 1, the resultant
date must be between Tuesday June 1 and Saturday June 5. According to
add rule 2, the DAY_OF_WEEK, an invariant when changing the
WEEK_OF_MONTH, is set to Tuesday, the closest possible value to Sunday
(where Sunday is the first day of the week).

My code:

System.out.println(roll(
        LocalDate.of(1999, 6, 6).atStartOfDay(),
        ChronoField.ALIGNED_WEEK_OF_MONTH, -1
));

outputs 1999-07-04T00:00, whereas using Calendar:

Calendar c = new GregorianCalendar(1999, 5, 6);
c.roll(Calendar.WEEK_OF_MONTH, -1);
System.out.println(c.getTime().toInstant());

outputs 1999-05-31T23:00:00Z, which is 1999-06-01 in my timezone.

What is an equivalent of roll in the java.time API? If there isn’t one, how can I write a method to mimic it?

2
Leave a Reply

avatar
2 Comment threads
0 Thread replies
0 Followers
 
Most reacted comment
Hottest comment thread
1 Comment authors
Jason Recent comment authors
  Subscribe  
newest oldest most voted
Notify of
Jason
Guest

First, I cannot remember having seen any useful application of Calendar.roll. Second, I don’t think that the functionality is very well specified in corner cases. And the corner cases would be the interesting ones. Rolling month by 13 months would not be hard without the rollmethod. It may be that similar observations are the reasons why this functionality is not offered by java.time. Instead I believe that we would have to resort to more manual ways of rolling. For your first example: LocalDate date = LocalDate.of(2019, Month.JULY, 22); int newMonthValue = 1 + (date.getMonthValue() - 1 + 13) % 12;… Read more »

Jason
Guest

TL;DR There is no equivalent. Think about whether you really need the behavior of roll of java.util.Calendar: /** * Adds or subtracts (up/down) a single unit of time on the given time * field without changing larger fields. For example, to roll the current * date up by one day, you can achieve it by calling: * roll(Calendar.DATE, true). * When rolling on the year or Calendar.YEAR field, it will roll the year * value in the range between 1 and the value returned by calling * getMaximum(Calendar.YEAR). * When rolling on the month or Calendar.MONTH field, other fields like… Read more »