@@ -673,10 +673,7 @@ extension Calendar {
673673 return false
674674 }
675675
676- /// Expand `_DateComponentCombinations` into a flat array of single-valued
677- /// `DateComponents`. Negative ordinals are translated to `{month, weekday,
678- /// weekOfMonth}` using `anchor`'s month structure. Returns nil if the
679- /// pattern can't be expanded (too many combinations, `.every()` weekday, etc).
676+ /// Expand `_DateComponentCombinations` into a flat array of single-valued `DateComponents`. Negative ordinals are translated to `{month, weekday, weekOfMonth}` using `anchor`'s month structure. Returns nil if the pattern can't be expanded.
680677 fileprivate func _expandedDateComponents(
681678 _ c: _DateComponentCombinations ,
682679 anchor: Date ? = nil ,
@@ -789,53 +786,77 @@ extension Calendar {
789786 return true
790787 }
791788
792- func make( monthIdx: Int , weekdayIdx: Int ,
793- daysOfMonthIdx: Int , daysOfYearIdx: Int , weeksOfYearIdx: Int ,
794- hoursIdx: Int , minutesIdx: Int , secondsIdx: Int ) -> DateComponents ? {
795- var dc = DateComponents ( )
796- if let ms = c. months {
797- dc. month = ms [ monthIdx] . index
798- dc. isLeapMonth = ms [ monthIdx] . isLeap
789+ // Build a base DateComponents with single valued axes, then vary only the multi valued ones.
790+ var base = DateComponents ( )
791+ var axes : [ ( WritableKeyPath < DateComponents , Int ? > , [ Int ] ) ] = [ ]
792+
793+ if let ms = c. months {
794+ if ms. count == 1 {
795+ base. month = ms [ 0 ] . index
796+ base. isLeapMonth = ms [ 0 ] . isLeap
797+ } else {
798+ axes. append ( ( \. month, ms. map ( \. index) ) )
799799 }
800- if let woy = c. weeksOfYear { dc. weekOfYear = woy [ weeksOfYearIdx] }
801- if let doy = c. daysOfYear { dc. dayOfYear = doy [ daysOfYearIdx] }
802- if let dom = c. daysOfMonth { dc. day = dom [ daysOfMonthIdx] }
803- if let wds = c. weekdays {
804- guard translateWeekday ( wds [ weekdayIdx] , into: & dc) else { return nil }
800+ }
801+ if let woy = c. weeksOfYear {
802+ if woy. count == 1 { base. weekOfYear = woy [ 0 ] }
803+ else { axes. append ( ( \. weekOfYear, woy) ) }
804+ }
805+ if let doy = c. daysOfYear {
806+ if doy. count == 1 { base. dayOfYear = doy [ 0 ] }
807+ else { axes. append ( ( \. dayOfYear, doy) ) }
808+ }
809+ if let dom = c. daysOfMonth {
810+ if dom. count == 1 { base. day = dom [ 0 ] }
811+ else { axes. append ( ( \. day, dom) ) }
812+ }
813+ if let hs = c. hours {
814+ if hs. count == 1 { base. hour = hs [ 0 ] }
815+ else { axes. append ( ( \. hour, hs) ) }
816+ }
817+ if let mins = c. minutes {
818+ if mins. count == 1 { base. minute = mins [ 0 ] }
819+ else { axes. append ( ( \. minute, mins) ) }
820+ }
821+ if let secs = c. seconds {
822+ if secs. count == 1 { base. second = secs [ 0 ] }
823+ else { axes. append ( ( \. second, secs) ) }
824+ }
825+
826+ // Handle weekdays: translate each entry into the base or build a seed list.
827+ var seeds : [ DateComponents ]
828+ if let wds = c. weekdays {
829+ if wds. count == 1 {
830+ guard translateWeekday ( wds [ 0 ] , into: & base) else { return nil }
831+ seeds = [ base]
832+ } else {
833+ seeds = [ ]
834+ seeds. reserveCapacity ( wds. count)
835+ for wd in wds {
836+ var wdBase = base
837+ guard translateWeekday ( wd, into: & wdBase) else { return nil }
838+ seeds. append ( wdBase)
839+ }
805840 }
806- if let hs = c. hours { dc. hour = hs [ hoursIdx] }
807- if let mins = c. minutes { dc. minute = mins [ minutesIdx] }
808- if let secs = c. seconds { dc. second = secs [ secondsIdx] }
809- return dc
841+ } else {
842+ seeds = [ base]
810843 }
811844
812- var result : [ DateComponents ] = [ ]
813- result. reserveCapacity ( total)
814- for mIdx in 0 ..< monthsCount {
815- for wIdx in 0 ..< weekdaysCount {
816- for domIdx in 0 ..< daysOfMonthCount {
817- for doyIdx in 0 ..< daysOfYearCount {
818- for woyIdx in 0 ..< weeksOfYearCount {
819- for hIdx in 0 ..< hoursCount {
820- for miIdx in 0 ..< minutesCount {
821- for sIdx in 0 ..< secondsCount {
822- guard let dc = make (
823- monthIdx: mIdx, weekdayIdx: wIdx,
824- daysOfMonthIdx: domIdx,
825- daysOfYearIdx: doyIdx,
826- weeksOfYearIdx: woyIdx,
827- hoursIdx: hIdx, minutesIdx: miIdx,
828- secondsIdx: sIdx) else { return nil }
829- result. append ( dc)
830- }
831- }
832- }
833- }
834- }
845+ // Expand each multi valued axis by cloning and patching.
846+ for (keyPath, values) in axes {
847+ var expanded : [ DateComponents ] = [ ]
848+ expanded. reserveCapacity ( seeds. count * values. count)
849+ for seed in seeds {
850+ for v in values {
851+ var dc = seed
852+ dc [ keyPath: keyPath] = v
853+ expanded. append ( dc)
835854 }
836855 }
856+ seeds = expanded
837857 }
838- return result
858+
859+ return seeds
839860 }
840861
841862 /// Single-valued `DateComponents` from combinations, or nil if expansion is needed.
@@ -1040,41 +1061,35 @@ extension Calendar {
10401061 matchingPolicy: MatchingPolicy ,
10411062 repeatedTimePolicy: RepeatedTimePolicy ) throws -> [ ( Date , DateComponents ) ] ? {
10421063
1043- // Fast-path short-circuits. The protocol default for _calendarNextDate is nil,
1044- // so non-Hebrew calendars fall through to the existing path unchanged.
1064+ // Fast-path short-circuits. Only fires when the calendar opts in.
1065+ if _supportsNextDateFastPath && matchingPolicy == . nextTime && repeatedTimePolicy == . first {
10451066
1046- // (1) Single-combination: one value per field → single _calendarNextDate call.
1047- if matchingPolicy == . nextTime && repeatedTimePolicy == . first,
1048- let dc = _singleCombinationDateComponents ( combinationComponents) ,
1049- let fast = _calendarNextDate ( after: startDate, matching: dc, direction: . forward) {
1050- return [ ( fast, dc) ]
1051- }
1067+ // (1) Single-combination: one value per field.
1068+ if let dc = _singleCombinationDateComponents ( combinationComponents) ,
1069+ let fast = _calendarNextDate ( after: startDate, matching: dc, direction: . forward) {
1070+ return [ ( fast, dc) ]
1071+ }
10521072
1053- // (2) Multi-combination (positive ordinals): cartesian product → probe each.
1054- if matchingPolicy == . nextTime && repeatedTimePolicy == . first,
1055- let allDCs = _expandedDateComponents ( combinationComponents) {
1056- var results : [ ( Date , DateComponents ) ] = [ ]
1057- results. reserveCapacity ( allDCs. count)
1058- var allFastPathed = true
1059- for dc in allDCs {
1060- guard let fast = _calendarNextDate ( after: startDate, matching: dc, direction: . forward) else {
1061- allFastPathed = false
1062- break
1073+ // (2) Multi-combination (positive ordinals): cartesian product.
1074+ if let allDCs = _expandedDateComponents ( combinationComponents) {
1075+ var results : [ ( Date , DateComponents ) ] = [ ]
1076+ results. reserveCapacity ( allDCs. count)
1077+ var allFastPathed = true
1078+ for dc in allDCs {
1079+ guard let fast = _calendarNextDate ( after: startDate, matching: dc, direction: . forward) else {
1080+ allFastPathed = false
1081+ break
1082+ }
1083+ results. append ( ( fast, dc) )
1084+ }
1085+ if allFastPathed && !results. isEmpty {
1086+ results. sort { $0. 0 < $1. 0 }
1087+ return results
10631088 }
1064- results. append ( ( fast, dc) )
1065- }
1066- if allFastPathed && !results. isEmpty {
1067- results. sort { $0. 0 < $1. 0 }
1068- return results
10691089 }
1070- }
10711090
1072- // (3) Multi-combination with negative-ordinal translation.
1073- if matchingPolicy == . nextTime && repeatedTimePolicy == . first,
1074- _unadjustedDatesHasNegativeOrdinal ( combinationComponents) {
1075- var sentinel = DateComponents ( )
1076- sentinel. weekday = 1
1077- if _calendarNextDate ( after: startDate, matching: sentinel, direction: . forward) != nil ,
1091+ // (3) Multi-combination with negative ordinal translation.
1092+ if _unadjustedDatesHasNegativeOrdinal ( combinationComponents) ,
10781093 let allDCs = _expandedDateComponents ( combinationComponents, anchor: startDate) {
10791094 var results : [ ( Date , DateComponents ) ] = [ ]
10801095 results. reserveCapacity ( allDCs. count)
0 commit comments