Calendar Roll will it wrap around a given year, month, etc? - java

So let's say I have this code:
//someParameterizedDate = New Years Eve 2011
Calendar cal = new GregorianCalendar();
cal.setTime(someParameterizedDate);
cal.roll(Calendar.DAY_OF_YEAR, 1);
Will the calendar now be equal to january 1, 2012? I found all the JavaDocs a little confusing.

java.util.Calendar
roll(f, delta) adds delta to field f without changing larger fields.
This is equivalent to calling add(f, delta) with the following
adjustment:
Roll rule. Larger fields are unchanged after the call. A larger field
represents a larger unit of time. DAY_OF_MONTH is a larger field than
HOUR.
You roll with DAY_OF_YEAR which means it will not affect MONTH or YEAR which are larger units
So basically, you should get you to December 1st 2011
You can use add if you want it to go to January 1st 2012

Related

Is there anything in the java.time package that does the same thing as the roll method in java.util.Calendar? [duplicate]

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?
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;
date = date.with(ChronoField.MONTH_OF_YEAR, newMonthValue);
System.out.println(date);
Output:
2019-08-22
I am using the fact that in the ISO chronology there are always 12 months in the year. Since % always gives a 0-based result, I subtract 1 from the 1-based month value before the modulo operation and add it back in afterwards And I am assuming a positive roll. If the number of months to roll may be negative, it gets slightly more complicated (left to the reader).
For other fields I think that a similar approach will work for most cases: Find the smallest and the largest possible value of the field given the larger fields and do some modulo operation.
It may become a challenge in some cases. For example, when summer time (DST) ends and the clock is turned backward from 3 to 2 AM, so the day is 25 hours long, how would you roll 37 hours from 6 AM? I’m sure it can be done. And I am also sure that the functionality is not built in.
For your example with rolling the week of month, another difference between the old and the modern API comes into play: a GregorianCalendar not only defines a calendar day and time, it also defines a week scheme consisting of a first day of the week and a minimum number of days in the first week. In java.time the week scheme is defined by a WeekFields object instead. So while rolling the week of month may be unambiguous in GregorianCalendar, without knowing the week scheme it isn’t with LocalDate or LocalDateTime. An attempt may be to assume ISO weeks (start on Monday, and the first week is the on that has at least 4 days of the new month in it), but it may not always be what a user had intended.
Week of month and week of year are special since weeks cross month and year boundaries. Here’s my attempt to implement a roll of week of month:
private static LocalDate rollWeekOfMonth(LocalDate date, int amount, WeekFields wf) {
LocalDate firstOfMonth = date.withDayOfMonth(1);
int firstWeekOfMonth = firstOfMonth.get(wf.weekOfMonth());
LocalDate lastOfMonth = date.with(TemporalAdjusters.lastDayOfMonth());
int lastWeekOfMonth = lastOfMonth.get(wf.weekOfMonth());
int weekCount = lastWeekOfMonth - firstWeekOfMonth + 1;
int newWeekOfMonth = firstWeekOfMonth
+ (date.get(wf.weekOfMonth()) - firstWeekOfMonth
+ amount % weekCount + weekCount)
% weekCount;
LocalDate result = date.with(wf.weekOfMonth(), newWeekOfMonth);
if (result.isBefore(firstOfMonth)) {
result = firstOfMonth;
} else if (result.isAfter(lastOfMonth)) {
result = lastOfMonth;
}
return result;
}
Try it out:
System.out.println(rollWeekOfMonth(LocalDate.of(1999, Month.JUNE, 6), -1, WeekFields.SUNDAY_START));
System.out.println(rollWeekOfMonth(LocalDate.of(1999, Month.JUNE, 6), -1, WeekFields.ISO));
Output:
1999-06-01
1999-06-30
Explanation: The documentation you quote assumes that Sunday is the first day of the week (it ends “where Sunday is the first day of the week”; it was probably written in the USA) so there is a week before Sunday June 6. And rolling by -1 week should roll into this week before. My first line of code does that.
In the ISO week scheme, Sunday June 6 belong to the week from Monday May 31 through Sunday June 6, so in June there is no week before this week. Therefore my second line of code rolls into the last week of June, June 28 through July 4. Since we cannot go outside June, June 30 is chosen.
I have not tested whether it behaves the same as GregorianCalendar. For comparison,the GregorianCalendar.roll implementation uses 52 code lines to handle the WEEK_OF_MONTH case, compared to my 20 lines. Either I have left something out of consideration, or java.time once again shows it superiority.
Rather my suggestion for the real world is: make your requirements clear and implement them directly on top of java.time, ignoring how the old API behaved. As an academic exercise, your question is a fun and interesting one.
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
* date might conflict and, need to be changed. For instance,
* rolling the month on the date 01/31/96 will result in 02/29/96.
* When rolling on the hour-in-day or Calendar.HOUR_OF_DAY field, it will
* roll the hour value in the range between 0 and 23, which is zero-based.
*
* #param field the time field.
* #param up indicates if the value of the specified time field is to be
* rolled up or rolled down. Use true if rolling up, false otherwise.
* #see Calendar#add(int,int)
* #see Calendar#set(int,int)
*/
public void roll(int field, boolean up);

Calling getTime changes Calendar value

I'm trying to get the sunday of the same week as a given date.
During this I ran into this problem:
Calendar calendar = Calendar.getInstance(Locale.GERMANY);
calendar.set(2017, 11, 11);
calendar.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
System.out.println(calendar.getTime().toString());
results in "Sun Jan 07 11:18:42 CET 2018"
but
Calendar calendar2 = Calendar.getInstance(Locale.GERMANY);
calendar2.set(2017, 11, 11);
calendar2.getTime();
calendar2.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
System.out.println(calendar2.getTime().toString());
gives me the correct Date "Sun Dec 17 11:18:42 CET 2017"
Can someone explain why the first exmple is behaving this way? Is this really intended?
Thanks
Basically, the Calendar API is horrible, and should be avoided. It's not documented terribly clearly, but I think I see where it's going, and it's behaving as intended in this situation. By that I mean it's following the intention of the API authors, not the intention of you or anyone reading your code...
From the documentation:
The calendar field values can be set by calling the set methods. Any field values set in a Calendar will not be interpreted until it needs to calculate its time value (milliseconds from the Epoch) or values of the calendar fields. Calling the get, getTimeInMillis, getTime, add and roll involves such calculation.
And then:
When computing a date and time from the calendar fields, there may be insufficient information for the computation (such as only year and month with no day of month), or there may be inconsistent information (such as Tuesday, July 15, 1996 (Gregorian) -- July 15, 1996 is actually a Monday). Calendar will resolve calendar field values to determine the date and time in the following way.
If there is any conflict in calendar field values, Calendar gives priorities to calendar fields that have been set more recently. The following are the default combinations of the calendar fields. The most recent combination, as determined by the most recently set single field, will be used.
For the date fields:
YEAR + MONTH + DAY_OF_MONTH
YEAR + MONTH + WEEK_OF_MONTH + DAY_OF_WEEK
YEAR + MONTH + DAY_OF_WEEK_IN_MONTH + DAY_OF_WEEK
YEAR + DAY_OF_YEAR
YEAR + DAY_OF_WEEK + WEEK_OF_YEAR
In the first example, the fact that the last field set was "day of week" means it will then use the YEAR + MONTH + WEEK_OF_MONTH + DAY_OF_WEEK calculation (I think). The year and month have been set to December 2017, but the week-of-month is the current week-of-month, which is the week 5 of January 2018... so when you then say to set the day of week to Sunday, it's finding the Sunday in the "week 5" of December 2017. December only had 4 weeks, so it's effectively rolling it forward... I think. It's all messy and you shouldn't have to think about that, basically.
In the second example, calling getTime() "locks in" the year/month/day you've specified, and computes the other fields. When you set the day of week, that's then adjusting it within the existing computed fields.
Basically, avoid this API as far as you possibly can. Use java.time, which is a far cleaner date/time API.
As Jon Skeet said, avoid Calendar. For your case it is truly horrible, and it’s poorly designed in general. Instead do
WeekFields weekFieldsForLocale = WeekFields.of(Locale.GERMANY);
// To find out which number Sunday has in the locale,
// grab any Sunday and get its weekFieldsForLocale.dayOfWeek()
int dayNumberOfSundayInLocale = LocalDate.now()
.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY))
.get(weekFieldsForLocale.dayOfWeek());
LocalDate date = LocalDate.of(2017, Month.DECEMBER, 11);
LocalDate sunday
= date.with(weekFieldsForLocale.dayOfWeek(), dayNumberOfSundayInLocale);
System.out.println(sunday);
This prints the expected date
2017-12-17
As others have already mentioned, the solution is to use java.time, the modern Java date and time API. Also generally it is so much nicer to work with. One nice feature is the LocalDate class that I am using. It is a date without time of day, which seems to match your requirements more precisely that Calendar did.
If the above looks complicated, it’s because, as I think you are aware, “Sunday of the same week” means different things in different locales. In the international standard that Germany follows, weeks begin on Monday, so Sunday is the last day of the week. In the American standard, for example, Sunday os the first day of the week. WeekFields.dayOfWeek() numbers the days of the week from 1 to 7, so when we want to set the day to Sunday, we first need to find out which number Sunday has got in this numbering (7 in Germany, 1 in the US). So for any Sunday, get its weekFieldsForLocale.dayOfWeek() value and later use this for setting the day of week to Sunday. The reason why this is necessary is that the with() method is so general and therefore has been designed to accept only numeric values; we can’t just pass it a DayOfWeek object.
If I substitute Locale.US into the code, I get 2017-12-10, which is the correct Sunday for a calendar where Sunday is the first day of the week. If you are sure your only want your code to work for Germany, you may of course just hardcode a 7 (please make it a constant with a very explanatory name).
Link: Oracle Tutorial Date Time explaining how to use java.time. There are other resources on the net (just avoid the outdated placed that suggest java.util.Calendar :-)

How to calculate sequential (ordinal) number of a specific weekday in a month in Java

I am trying to determine the sequential ordinal number of a weekday in a month in Java. i.e. if a Friday is the first or 3rd friday of a month.
I can not find a simple way after reading all the things I can find on Java Calendar and posts here. One way I can think of is to determine how many days the first week of this month have in this month and then adjust week_of_month based on what day the day in question is. However, it requires a little complicated calculation. Anyone knows a simple solution?
Just take the day of month, subtract 1, divide by 7, then add 1. The first seven days of the month are always the first (Tuesday, Wednesday, ...) whatever day of the week the actual 1st of the month is.
Personally I'd use Joda Time:
public int getWeekOfWeekDay(LocalDate date) {
return ((date.getDayOfMonth() - 1) / 7) + 1;
}
... but you could do the same using Calendar and fetching the value of the Calendar.DAY_OF_MONTH field.
EDIT: Actually, I've just noticed that for a change, java.util.Calendar is actually simpler than Joda Time - there's a particular field for it! All you need is:
int weekOfWeekDay = calendar.get(Calendar.DAY_OF_WEEK_IN_MONTH);
From the docs for DAY_OF_WEEK_IN_MONTH:
Field number for get and set indicating the ordinal number of the day of the week within the current month. Together with the DAY_OF_WEEK field, this uniquely specifies a day within a month. Unlike WEEK_OF_MONTH and WEEK_OF_YEAR, this field's value does not depend on getFirstDayOfWeek() or getMinimalDaysInFirstWeek(). DAY_OF_MONTH 1 through 7 always correspond to DAY_OF_WEEK_IN_MONTH 1; 8 through 14 correspond to DAY_OF_WEEK_IN_MONTH 2, and so on.
I think I'd probably still use the Joda Time version because it's just a much nicer API all round, but if you're forced to use Calendar, at least you can do this in one shot.

Calendar roll operation doesn't provide me with the correct output

We are using Calendar.roll to either move the dates up or down. The javadoc mentions that the larger fields are not modified (i.e. if we move the date by 5 to the left starting on the first day of the month, unfortunately the calendar.getTime() doesn't get me a value from the previous month). The month value remains unchanged, how do I change this behavior. I really would like to move the date value as appropriate. (e.g. If I moved 5 days to the left on Aug 1st, 2010 - I would want to see Jun 27th, 2010 instead of Aug 27th, 2010). What am I missing here?
You can use Calendar.add with a negative amount.
You will need to use add(Calendar.DATE, -5) method from Calendar because of roll rule check.
roll method is described as :
Add to field a signed amount without
changing larger fields. A negative
roll amount means to subtract from
field without changing larger fields.
Example: Consider a GregorianCalendar
originally set to August 31, 1999.
Calling roll(Calendar.MONTH, 8) sets
the calendar to April 30, 1999. Using
a GregorianCalendar, the DAY_OF_MONTH
field cannot be 31 in the month April.
DAY_OF_MONTH is set to the closest
possible value, 30. The YEAR field
maintains the value of 1999 because it
is a larger field than MONTH.
Example: 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).

Calendar add() vs roll() when do we use it?

I know add() adds the specified (signed) amount of time to the given time field, based on the calendar's rules.
And roll() adds the specified (signed) single unit of time on the given time field without changing larger fields.
I can't think of an everyday usage of roll() I would do everything by add().
Can you help me out with examples when do we use roll() and when add()?
EDIT 1
Joda answers are not accepted!
add() - almost always, as you said
roll() - for example you want to "dispense" events in one month. The algorithm may be to proceed a number of days and place the event, then proceed further. When the end of the month is reached, it should start over from the beginning. Hence roll().
Found in jGuru
Calendar.roll()
Changes a specific unit
and leaves 'larger' (in terms of time-month
is 'larger' than day) units unchanged. The API example is that
given a date of August 31, 1999,
rolling by (Calendar.MONTH, 8) yields
April 30, 1999. That is, the DAY was
changed to meet April's maximum, but
the 'larger' unit, YEAR, was
unchanged.
roll(): Rolls up 8 months here i.e., adding 8 months to Aug will result in Apr but year remains unchanged(untouched).
Calendar.add()
Will cause the
next 'larger' unit to change, if
necessary. That is, given a date of
August 31, 1999, add(Calendar.MONTH,
8) yields April 30, 2000. add() also
forces a recalculation of milliseconds
and all fields.
add(): Adds months to the current date i.e., adding 8 months to Aug will give Apr of Next Year, hence forces the Year change.
I was just asking the same question (which is how I found this page) and someone at my work place (well done, DCK) came up with a suggestion:
The date selectors on many smart phones (and other similar interfaces) will "roll" the day from the 31st to the 1st without altering the month, similarly for the month field.
I can't think of another use ATM and this one could be implemented in other ways, but at least it's an example!
Tim
Here is an example that will not work. The condition in the loop will never be satisfied, because the roll, once reaching January 31, 2014, will go back to January 1, 2014.
Calendar start=new GregorianCalendar();
start.set(Calendar.YEAR, 2014);
start.set(Calendar.MONTH, 0);
start.set(Calendar.DAY_OF_MONTH, 1);
//January 2, 2014
Calendar end=new GregorianCalendar();
end.set(Calendar.YEAR, 2014);
end.set(Calendar.MONTH, 1);
end.set(Calendar.DAY_OF_MONTH, 2);
//February 2, 2014
while (start.getTime().before(end.getTime())){
start.roll(Calendar.DATE, 1);
}

Categories

Resources