-
Notifications
You must be signed in to change notification settings - Fork 6.2k
8225641: Calendar.roll(int field) does not work correctly for WEEK_OF_YEAR #13031
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
8225641: Calendar.roll(int field) does not work correctly for WEEK_OF_YEAR #13031
Conversation
|
👋 Welcome back jlu! A progress list of the required criteria for merging this PR into |
|
@justin-curtis-lu The following labels will be automatically applied to this pull request:
When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing lists. If you would like to change these labels, use the /label pull request command. |
Webrevs
|
naotoj
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi Justin,
Thanks for the fix. Still reviewing the changes, but here are some comments I have noticed:
| // Make sure that the (potential) minimum week has the | ||
| // current DAY_OF_WEEK | ||
| if (newWeekOfYear == 1 && isInvalidWeek1()) { | ||
| newWeekOfYear+=1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is +1 always correct? Does it work when the amount is negative?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a week is rolled down to 1 (and it does not have a valid day_of_week), the existing code would appropriately adjust min, so that the new week set would be 52. However, yes, it should only be incremented if amount is positive, but there does not need to be a case for when amount is negative.
| // Calculate the DAY_OF_WEEK for Jan 1 of the current YEAR | ||
| long jan1Fd = gcal.getFixedDate(internalGet(YEAR), 1, 1, null); | ||
| int jan1Dow = BaseCalendar.getDayOfWeekFromFixedDate(jan1Fd); | ||
| int daysInFirstWeek; | ||
| if (getFirstDayOfWeek() <= jan1Dow) { | ||
| // Add wrap around days | ||
| daysInFirstWeek = (7 - jan1Dow) + getFirstDayOfWeek(); | ||
| } else { | ||
| daysInFirstWeek = getFirstDayOfWeek() - jan1Dow; | ||
| } | ||
| // If the week is minimum, check if the DAY_OF_WEEK does not exist | ||
| return isMinWeek(daysInFirstWeek) && | ||
| dayNotInMinWeek(internalGet(DAY_OF_WEEK), jan1Dow, getFirstDayOfWeek() - 1); | ||
| } | ||
|
|
||
| /** | ||
| * Determines if the specified amount of days can make up | ||
| * a valid minimum week. | ||
| */ | ||
| private boolean isMinWeek (int days) { | ||
| return days >= getMinimalDaysInFirstWeek(); | ||
| } | ||
|
|
||
| /** | ||
| * Determines if the specified day exists in the minimum week. | ||
| * For example, dayNotInMinWeek(4, 6, 3) returns false since Wednesday | ||
| * is not between the minimum week given by [Friday, Saturday, | ||
| * Sunday, Monday, Tuesday]. | ||
| */ | ||
| private boolean dayNotInMinWeek (int day, int startDay, int endDay) { | ||
| if (endDay >= startDay) { | ||
| // dayNotInMinWeek(2, 3, 6), check that 2 is | ||
| // not between 3 4 5 6 | ||
| return !(day >= startDay && day <= endDay); | ||
| } else { | ||
| // dayNotInMinWeek(4, 6, 3), check that 4 is | ||
| // not between 6 7 1 2 3 | ||
| return !(day >= startDay || day <= endDay); | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these GregorianCalendar specific? What about other calendars?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, these are probably applicable to the other calendars, will take another look there.
| /* | ||
| * @test | ||
| * @bug 8225641 | ||
| * @summary Test the behavior of Calendar.roll(WEEK_OF_YEAR) when the week | ||
| * is rolled into a minimal week 1 | ||
| * @run junit RollToMinWeek | ||
| */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you considered adding test cases into test/jdk/java/util/Calendar/CalendarTestScripts, instead of creating a single-purpose test case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will take a look at the existing .cts files and see if it would be advantageous to add test cases there
| private static Stream<Arguments> calendarProvider() { | ||
| return Stream.of( | ||
| // Test a variety of rolls that previously produced incorrect results | ||
| Arguments.of(buildCalendar(27, 11, 2020, Locale.ENGLISH), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first day of week depends on the region, not the language. In fact, I would prefer explicitly specifying it via a locale like en-US-u-fw-mon, and testing all the weekdays.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated the testing
| } | ||
|
|
||
| private static Calendar buildCalendar(int day, int month, int year, Locale locale) { | ||
| Calendar calendar = Calendar.getInstance(locale); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the fix is GregorianCalendar specific, should it check the type?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I have changed it so that it is intentionally set as Gregorian, and also tested for being Gregorian.
|
Although this issue applies to Gregorian Calendar, it appears it may also occur with a Japanese Calendar under the right circumstances.
The Reiwa era begins on May 1st, 2019. Rolling the last Sunday of 2019 by 1 week, one would expect the resultant date to be the first Sunday of the Reiwai period (the 5th of May, 2019). However, a completely wrong date is returned, and the calendar actually rolls back an era into Heisei. Looking into it further, as well as the other calendars |
|
The test currently tests 343 combinations for rolling from the end of the year into week 1 Before patch: After patch: |
naotoj
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. Mostly cosmetic comments follow
| // rolling up into week 1, as the existing checks | ||
| // sufficiently handle rolling down into week 1. | ||
| if (newWeekOfYear == 1 && (isInvalidWeek1())) { | ||
| if (amount > 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can be folded into the above if
| // current DAY_OF_WEEK. Only make a check for | ||
| // rolling up into week 1, as the existing checks | ||
| // sufficiently handle rolling down into week 1. | ||
| if (newWeekOfYear == 1 && (isInvalidWeek1())) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extra parens around isInvalidWeek1()
| private boolean isMinWeek (int days) { | ||
| return days >= getMinimalDaysInFirstWeek(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this method is only used above once, maybe make the check inline?
| private boolean dayNotInMinWeek (int day, int startDay, int endDay) { | ||
| if (endDay >= startDay) { | ||
| // dayNotInMinWeek(2, 3, 6), check that 2 is | ||
| // not between 3 4 5 6 | ||
| return !(day >= startDay && day <= endDay); | ||
| } else { | ||
| // dayNotInMinWeek(4, 6, 3), check that 4 is | ||
| // not between 6 7 1 2 3 | ||
| return !(day >= startDay || day <= endDay); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could be more readable if the method does not have Not, i.e, daysInMinWeek? This would remove the negation in the return statements.
| * @test | ||
| * @bug 8225641 | ||
| * @summary Test the behavior of Calendar.roll(WEEK_OF_YEAR) when the last week | ||
| * is rolled up into a minimal week 1 of the same year |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minimal week 1 may be unclear.
|
|
||
| /** | ||
| * Test to validate the behavior of Calendar.roll(WEEK_OF_YEAR, +1) | ||
| * when rolling into a minimal week 1 from the max week. WEEK_OF_YEAR can |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIUC, minimal and max mean first and last respectively? I'd rather change those as it would confuse with minimal days in the first week.
| @MethodSource("rollUpCalProvider") | ||
| public void rollUpTest(Calendar calendar, String[] validDates){ | ||
| if (calendar instanceof GregorianCalendar) { | ||
| testRoll(calendar, validDates, +1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the amount is fixed to +1, no need to pass it as an argument
| for (int weekLength = 1; weekLength <= 7; weekLength++) { | ||
| for (int firstDay = 1; firstDay <= 7; firstDay++) { | ||
| calList.add(Arguments.of(buildCalendar("gregory", firstDay, weekLength, | ||
| dayOfMonth, 11, 2019), validDates[date])); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
11 may better be replaced with Calendar.DECEMBER.
| Calendar.Builder calBuilder = new Builder(); | ||
| calBuilder.setCalendarType(type); | ||
| calBuilder.setWeekDefinition(firstDayOfWeek, minimumWeekLength); | ||
| calBuilder.setDate(year, month, dayOfMonth); | ||
| return calBuilder.build(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Builder instance can be reused. Also since Builder can method chain, you could write
static Calendar.Builder CAL_BUILDER = new Builder().setCalendarType(type);
return CAL_BUILDER
.setWeekDefinition(...)
.setDate(...)
.build();
| * Sunday, Monday, Tuesday]. | ||
| */ | ||
| private boolean dayInMinWeek (int day, int startDay, int endDay) { | ||
| endDay = endDay == 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why endDay can be 0? If the arguments are Calendar.XXXDAY, should they be between 1 to 7? In other words, why is the above calling site doing getFirstDayOfWeek() - 1?
I'd add some range checks for the input values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. I have changed it so the method throws an exception if the start and end days are not valid DAY_OF_WEEK values.
And I have moved the ternary of switching 0 to 7 outside the method accordingly.
| * week is incremented to prevent this. | ||
| */ | ||
| public class RollFromLastToFirstWeek { | ||
| static Calendar.Builder GREGORIAN_BUILDER = new Builder() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Calendar. can be omitted (and can be private static final)
| throw new IllegalArgumentException("Start day or end day is not " + | ||
| "a valid day of the week"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I take my previous comment back. I think we can simply rely on the return value from getFirstDayOfWeek() as it is well-checked, so no need to check the input here. Otherwise the thrown IAE woudl have to end up in add()/roll() public methods, which cannot be spec'ed with these internal arguments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No worries, that makes sense. Removed the exception.
naotoj
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
|
@justin-curtis-lu This change now passes all automated pre-integration checks. ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details. After integration, the commit message for the final commit will be: You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed. At the time when this comment was updated there had been 413 new commits pushed to the
As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details. As you do not have Committer status in this project an existing Committer must agree to sponsor your change. Possible candidates are the reviewers of this PR (@naotoj) but any other Committer may sponsor as well. ➡️ To flag this PR as ready for integration with the above commit message, type |
|
/integrate |
|
@justin-curtis-lu |
|
/sponsor |
|
Going to push as commit a324fa2.
Your commit was automatically rebased without conflicts. |
|
@naotoj @justin-curtis-lu Pushed as commit a324fa2. 💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored. |
This PR fixes the bug which occurred when
Calendar.roll(WEEK_OF_YEAR)rolled into a minimal first week with an invalidWEEK_OF_YEARandDAY_OF_WEEKcombo.For example, Rolling Monday, 30 December 2019 by 1 week produced Monday, 31 December 2018, which is incorrect. This is because
WEEK_OF_YEARis rolled from 52 to 1, and the originalDAY_OF_WEEKis 1. However, there is no Monday in week 1 of 2019. This is exposed when a future method callsCalendar.complete(), which eventually calculates afixedDatewith the invalidWEEK_OF_YEARandDAY_OF_WEEKcombo.To prevent this, a check is added for rolls into week 1, which determines if the first week is minimal. If it is indeed minimal, then it is checked if
DAY_OF_WEEKexists in that week, if not,WEEK_OF_YEARmust be incremented by one.After the fix, Rolling Monday, 30 December 2019 by 1 week produces Monday, 7 January 2019
Progress
Issue
Reviewers
Reviewing
Using
gitCheckout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/13031/head:pull/13031$ git checkout pull/13031Update a local copy of the PR:
$ git checkout pull/13031$ git pull https://git.openjdk.org/jdk.git pull/13031/headUsing Skara CLI tools
Checkout this PR locally:
$ git pr checkout 13031View PR using the GUI difftool:
$ git pr show -t 13031Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/13031.diff
Webrev
Link to Webrev Comment