Skip to content

compare week-year not calendar year in compareWeeks#408

Merged
sebbASF merged 2 commits into
apache:masterfrom
sahvx655-wq:calendar-week-year-compare
Jun 27, 2026
Merged

compare week-year not calendar year in compareWeeks#408
sebbASF merged 2 commits into
apache:masterfrom
sahvx655-wq:calendar-week-year-compare

Conversation

@sahvx655-wq

@sahvx655-wq sahvx655-wq commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

compareWeeks reports 31 December and 1 January of the same calendar year as the same week. AbstractCalendarValidator.compare ordered the WEEK_OF_YEAR comparison on Calendar.YEAR and then the week number, but a week belongs to its own week-year, and at the turn of the year that week-year differs from the calendar year. Under the US locale 31 Dec 2018 has WEEK_OF_YEAR 1 and a week-year of 2019, so against 1 Jan 2018 (also WEEK_OF_YEAR 1) the year and the week number both match and two dates roughly 52 weeks apart compare equal. The mirror case fails the other way: 31 Dec 2018 and 1 Jan 2019 share one week yet compare as different weeks because their calendar years differ. WEEK_OF_MONTH resets the same way at the start of a month.

Following the review, this now orders both WEEK_OF_YEAR and WEEK_OF_MONTH by the distance in days first: dates a week or more apart are always in different weeks, and nearer dates share a week only when the week number also matches. That removes the year-boundary false match for WEEK_OF_YEAR and is correct by construction for WEEK_OF_MONTH, which keeps its previous results. It also drops the earlier getWeekYear() lookup and its unsupported-calendar fallback. Tests cover both fields across the year boundary and the full suite passes.

  • Read the contribution guidelines for this project.
  • Read the ASF Generative Tooling Guidance if you use Artificial Intelligence (AI).
  • I used AI to create any part of, or all of, this pull request. Which AI tool was used to create this pull request, and to what extent did it contribute?
  • Run a successful build using the default Maven goal with mvn; that's mvn on the command line by itself.
  • Write unit tests that match behavioral changes, where the tests fail if the changes to the runtime are not applied. This may not always be possible, but it is a best practice.
  • Write a pull request description that is detailed enough to understand what the pull request does, how, and why.
  • Each commit in the pull request should have a meaningful subject line and body. Note that a maintainer may squash commits during the merge process.

@sebbASF

sebbASF commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

It has just occurred to me - the same issue may occur with WEEK_OF_MONTH.
AFAICT the first WEEK_OF_MONTH in January could contain one or more days from December of the previous year.

Maybe an alternative approach would be to initially compare days since the epoch. If the difference between the dates is less than a week, then compare WEEK_OF values. If they are the same, then return 0, otherwise return the result of the epoch comparison.

AbstractCalendarValidator.compare ordered WEEK_OF_YEAR on Calendar.YEAR
then the week number, but a week belongs to its own week-year, which at
the year boundary differs from the calendar year (31 December can be week
1 of the following year). So compareWeeks reported 31 December and 1
January of the same calendar year as the same week, and treated two dates
that share one week across the boundary as different weeks. WEEK_OF_MONTH
resets in the same way at the start of a month.

Order both week fields by the distance in days first: dates a week or more
apart are always in different weeks, and nearer dates share a week only
when the week number also matches. This drops the week-year lookup and its
unsupported-calendar fallback. WEEK_OF_MONTH keeps its previous results;
only the WEEK_OF_YEAR boundary case changes. Tests cover both fields
across the year boundary.
@sahvx655-wq sahvx655-wq force-pushed the calendar-week-year-compare branch from 684f5ea to 55526f4 Compare June 26, 2026 06:19
@sahvx655-wq

Copy link
Copy Markdown
Contributor Author

Took the epoch route you suggested. compare() now sends both WEEK_OF_YEAR and WEEK_OF_MONTH through one helper that looks at the day gap first: a week or more apart is always a different week, and within a week they only count as the same week when the week number also matches. That removes the year-boundary false match on WEEK_OF_YEAR and drops the getWeekYear()/isWeekDateSupported() machinery the earlier version leaned on.

On WEEK_OF_MONTH: the old path compared MONTH before WEEK_OF_MONTH, so a December day and a January day never collided the way the year-boundary case did, which is why it wasn't visibly broken. Routing it through the same guard keeps every existing WEEK_OF_MONTH result and makes that invariant explicit rather than incidental. I added boundary tests for both fields and the full build (mvn clean verify with checkstyle/spotbugs/pmd, 1092 tests) is green.

@sebbASF

sebbASF commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

There’s another issue here - the WEEK_OF_* values depend on the Calendar settings, so only make sense if the calendars are the same according to .equals().

Does it make ever make sense to compare calendars in different time zones?

However, that can be handled separately.

@sahvx655-wq

Copy link
Copy Markdown
Contributor Author

On the WEEK_OF_* values depending on calendar settings, and comparing across time zones: I agree those only really hold up when the calendars match, but since you noted it can be handled separately I've kept it out of this PR so the change stays scoped to the week-boundary fix. Happy to follow up on it on its own.

@sebbASF sebbASF merged commit 7f1bae0 into apache:master Jun 27, 2026
10 checks passed
@sebbASF

sebbASF commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Thanks!

asf-gitbox-commits pushed a commit that referenced this pull request Jun 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants