ICU-23489 WEEK_OF_MONTH in the Gregorian cutover year - #4132
Open
cayde-6 wants to merge 1 commit into
Open
Conversation
Two related fixes; the first is a prerequisite for the second. 1. Store a Julian Day in fCutoverJulianDay when the cutover is set explicitly. The constructors store kCutoverJulianDay, an actual Julian Day, and that is what handleComputeJulianDay() and handleComputeFields() compare the field against, but setGregorianChange() stored a day count relative to the 1970 epoch. A calendar given the standard cutover date explicitly therefore resolved dates of the cutover year differently from the default calendar, which uses that same date implicitly. 2. Apply the cutover-year WEEK_OF_MONTH shift only to the cutover month. The shift compensates for the days the cutover removes from a month, but it was applied to every month of the cutover year, so November and December 1582 resolved two weeks late, as did an out of range month such as MONTH 14 with YEAR 1582. ICU4J, which has no such shift, resolves the ordinary months the way this change now does. The month comes from internalGetMonth(), so a month resolved from ORDINAL_MONTH is treated the same, and the cutover month is derived from fGregorianCutover. October 1582 is unchanged: its five weeks stay five distinct instants seven days apart. Add TestExplicitCutoverMatchesDefault23489 and TestWeekOfMonthInCutoverYear23489.
cayde-6
force-pushed
the
ICU-23489-wom-cutover
branch
from
August 22, 2026 21:06
1e2f6be to
49bfa25
Compare
|
Hooray! The files in the branch are the same across the force-push. 😃 ~ Your Friendly Jira-GitHub PR Checker Bot |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two related fixes, squashed into one commit as the single-commit check asks.
The first is a prerequisite: without it the second one changes behaviour for
calendars whose cutover was set explicitly.
1. Store a Julian Day in
fCutoverJulianDaywhen the cutover is set explicitlyThe constructors store
kCutoverJulianDay, 2299161, an actual Julian Day, andthat is what
handleComputeJulianDay()andhandleComputeFields()compare thefield against.
setGregorianChange()storedfloorDivide(date, kOneDay)instead, a day count relative to the 1970 epoch, 2440588 days off.
The visible effect: a calendar given the standard papal cutover date explicitly
resolves dates of the cutover year differently from the default calendar, which
uses that very same date implicitly. With
WEEK_OF_MONTHin 1582, GMT, firstday of week Sunday, minimal days in first week 1, the two disagree in 25 of the
60 month and week combinations, February, March, June, August and September
each by seven days.
TestExplicitCutoverMatchesDefault23489pins the two against each other acrossthe whole cutover year.
ICU4J has the same defect at
GregorianCalendar.java:514, with the same2299161 default at line 323. I have not touched it here, since I have no way to
build and run ICU4J; happy to file it separately.
2. Apply the cutover-year WEEK_OF_MONTH shift only to the cutover month
handleComputeJulianDayadds 14 days when the calendar is Gregorian, theextended year equals the cutover year, and the best field is
WEEK_OF_MONTH.The condition tests the year, so it fired for every month of that year rather
than for the month that loses days.
With the default cutover only October 1582 is short: 1582-10-01 to 1582-10-04,
then 1582-10-15 to 1582-10-31. November and December 1582 are ordinary months.
1582-11-01 is a Monday and 1582-12-01 is a Wednesday, so with weeks starting on
Sunday the first week of November begins on 1582-10-31 and the first week of
December begins on 1582-11-28. ICU4C resolved those to 1582-11-14 and
1582-12-12, two weeks late. ICU4J, which has no such shift, returns the earlier
dates. An out of range month was shifted too: YEAR 1582 with MONTH 14 landed two
weeks after YEAR 1583 with MONTH 2.
The shift is now applied only when the requested month is the month the cutover
falls in. The month comes from
internalGetMonth(), the same accessorCalendar::handleComputeJulianDayuses to compute the day, so a month resolvedfrom
ORDINAL_MONTHis treated identically to one set throughMONTH. Thecutover month is derived from
fGregorianCutover.Deleting the branch outright is not correct, in case that looks tempting:
without the shift, weeks 4 and 5 of October 1582 collapse onto the same instants
as weeks 2 and 3, verified to the millisecond.
The 14 day constant is left as is. It is tied to the ten day gap of the 1582
cutover, and a cutover set through
setGregorianChangeneeds a different value.That case is ICU-3350, which is open for exactly this.
TestWeekOfMonthInCutoverYear23489covers October, November and December 1582,two control months outside the cutover year, the
ORDINAL_MONTHpath, an out ofrange month, and pins the five weeks of October 1582 as five distinct instants
exactly seven days apart.
Verification
intltestandcintltstboth pass in full, from a clean build.Reverting the whole change:
TestExplicitCutoverMatchesDefault23489fails on 25of its 60 rows, and
TestWeekOfMonthInCutoverYear23489on 11, November,December and the out of range month.
The two parts have to travel together. Reverting only the
setGregorianChangepart while keeping the narrowed condition makes
TestExplicitCutoverMatchesDefault23489fail on 45 rows rather than 25: thenarrowed condition removes a shift that used to mask the unit mismatch for
explicitly set cutovers. Reverting only the narrowed condition while keeping the
unit fix leaves
TestWeekOfMonthInCutoverYear23489failing on its 11 rows.ICU4J parity
For the ordinary months of the cutover year ICU4C now agrees with ICU4J, which
is what this ticket is about.
For October 1582 the two still differ, and this does not paper over it: ICU4J
resolves weeks 4 and 5 to the same instants as weeks 2 and 3, while ICU4C keeps
five distinct weeks. ICU4C looks right here, so a mirrored ICU4J test would fail
today rather than pass. Worth a separate ticket if you agree.