@@ -244,6 +244,10 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
244244 hasher. combine ( preferredMinimumDaysInFirstweek)
245245 }
246246
247+ // MARK: - Range
248+
249+ // Returns the range of a component in Gregorian Calendar.
250+ // When there are multiple possible upper bounds, the smallest one is returned.
247251 func minimumRange( of component: Calendar . Component ) -> Range < Int > ? {
248252 switch component {
249253 case . era: 0 ..< 2
@@ -266,30 +270,257 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
266270 }
267271 }
268272
273+ // Returns the range of a component in Gregorian Calendar.
274+ // When there are multiple possible upper bounds, the largest one is returned.
269275 func maximumRange( of component: Calendar . Component ) -> Range < Int > ? {
270276 switch component {
271- case . era: 0 ..< 2
272- case . year: 1 ..< 144684
273- case . month: 1 ..< 13
274- case . day: 1 ..< 32
275- case . hour: 0 ..< 24
276- case . minute: 0 ..< 60
277- case . second: 0 ..< 60
278- case . weekday: 1 ..< 8
279- case . weekdayOrdinal: 1 ..< 6
280- case . quarter: 1 ..< 5
281- case . weekOfMonth: 1 ..< 7
282- case . weekOfYear: 1 ..< 54
283- case . yearForWeekOfYear: 140742 ..< 144684
284- case . nanosecond: 0 ..< 1000000000
285- case . isLeapMonth: 0 ..< 2
277+ case . era: return 0 ..< 2
278+ case . year: return 1 ..< 144684
279+ case . month: return 1 ..< 13
280+ case . day: return 1 ..< 32
281+ case . hour: return 0 ..< 24
282+ case . minute: return 0 ..< 60
283+ case . second: return 0 ..< 60
284+ case . weekday: return 1 ..< 8
285+ case . weekdayOrdinal:
286+ return 1 ..< 6
287+ case . quarter: return 1 ..< 5
288+ case . weekOfMonth:
289+ let lowerBound = minimumDaysInFirstWeek == 1 ? 1 : 0
290+ let daysInMonthLimit = 31
291+ let upperBound = ( daysInMonthLimit + 6 + ( 7 - minimumDaysInFirstWeek) ) / 7 ;
292+ return lowerBound ..< ( upperBound + 1 )
293+ case . weekOfYear: return 1 ..< 54
294+ case . yearForWeekOfYear: return 140742 ..< 144684
295+ case . nanosecond: return 0 ..< 1000000000
296+ case . isLeapMonth: return 0 ..< 2
286297 case . calendar, . timeZone:
287- nil
298+ return nil
288299 }
289300 }
290-
301+
302+ // There is a chance of refactoring Calendar_ICU to use these
303+ func _algorithmA( smaller: Calendar . Component , larger: Calendar . Component , at: Date ) -> Range < Int > ? {
304+ guard let interval = dateInterval ( of: larger, for: at) else {
305+ return nil
306+ }
307+
308+ guard let ord1 = ordinality ( of: smaller, in: larger, for: interval. start + 0.1 ) else {
309+ return nil
310+ }
311+
312+ guard let ord2 = ordinality ( of: smaller, in: larger, for: interval. start + interval. duration - 0.1 ) else {
313+ return nil
314+ }
315+
316+ guard ord2 >= ord1 else {
317+ return ord1..< ord1
318+ }
319+
320+ return ord1..< ( ord2 + 1 )
321+ }
322+
323+ private func _algorithmB( smaller: Calendar . Component , larger: Calendar . Component , at: Date ) -> Range < Int > ? {
324+ guard let interval = dateInterval ( of: larger, for: at) else {
325+ return nil
326+ }
327+
328+ var counter = 15 // stopgap in case something goes wrong
329+ let end = interval. start + interval. duration - 1.0
330+ var current = interval. start + 1.0
331+
332+ var result : Range < Int > ?
333+ repeat {
334+ guard let innerInterval = dateInterval ( of: . month, for: current) else {
335+ return result
336+ }
337+
338+ guard let ord1 = ordinality ( of: smaller, in: . month, for: innerInterval. start + 0.1 ) else {
339+ return result
340+ }
341+
342+ guard let ord2 = ordinality ( of: smaller, in: . month, for: innerInterval. start + innerInterval. duration - 0.1 ) else {
343+ return result
344+ }
345+
346+ if let lastResult = result {
347+ let mn = min ( lastResult. first!, ord1)
348+ result = mn..< ( mn + lastResult. count + ord2)
349+ } else if ord2 >= ord1 {
350+ result = ord1..< ( ord2 + 1 )
351+ } else {
352+ return ord1..< ord1
353+ }
354+
355+ counter -= 1
356+ current = innerInterval. start + innerInterval. duration + 1.0
357+ } while current < end && 0 < counter
358+
359+ return result
360+ }
361+
362+ private func _algorithmC( smaller: Calendar . Component , larger: Calendar . Component , at: Date ) -> Range < Int > ? {
363+ guard let interval = dateInterval ( of: larger, for: at) else {
364+ return nil
365+ }
366+
367+ guard let ord1 = ordinality ( of: smaller, in: . year, for: interval. start + 0.1 ) else {
368+ return nil
369+ }
370+
371+ guard let ord2 = ordinality ( of: smaller, in: . year, for: interval. start + interval. duration - 0.1 ) else {
372+ return nil
373+ }
374+
375+ guard ord2 >= ord1 else {
376+ return ord1..< ord1
377+ }
378+
379+ return ord1..< ( ord2 + 1 )
380+ }
381+
382+ private func _algorithmD( at: Date ) -> Range < Int > ? {
383+ guard let weekInterval = dateInterval ( of: . weekOfMonth, for: at) else {
384+ return nil
385+ }
386+
387+ guard let monthInterval = dateInterval ( of: . month, for: at) else {
388+ return nil
389+ }
390+
391+ let start = weekInterval. start < monthInterval. start ? monthInterval. start : weekInterval. start
392+ let end = weekInterval. end < monthInterval. end ? weekInterval. end : monthInterval. end
393+
394+ guard let ord1 = ordinality ( of: . day, in: . month, for: start + 0.1 ) else {
395+ return nil
396+ }
397+
398+ guard let ord2 = ordinality ( of: . day, in: . month, for: end - 0.1 ) else {
399+ return nil
400+ }
401+
402+ guard ord2 >= ord1 else {
403+ return ord1..< ord1
404+ }
405+
406+ return ord1..< ( ord2 + 1 )
407+ }
408+
291409 func range( of smaller: Calendar . Component , in larger: Calendar . Component , for date: Date ) -> Range < Int > ? {
292- fatalError ( )
410+ func isValidComponent( _ c: Calendar . Component ) -> Bool {
411+ return !( c == . calendar || c == . timeZone || c == . weekdayOrdinal || c == . nanosecond)
412+ }
413+
414+ guard isValidComponent ( larger) else { return nil }
415+
416+ let capped = date. capped
417+
418+ // The range of these fields are fixed, and so are independent of what larger fields are
419+ switch smaller {
420+ case . weekday:
421+ switch larger {
422+ case . second, . minute, . hour, . day, . weekday:
423+ return nil
424+ default :
425+ return maximumRange ( of: smaller)
426+ }
427+ case . hour:
428+ switch larger {
429+ case . second, . minute, . hour:
430+ return nil
431+ default :
432+ return maximumRange ( of: smaller)
433+ }
434+ case . minute:
435+ switch larger {
436+ case . second, . minute:
437+ return nil
438+ default :
439+ return maximumRange ( of: smaller)
440+ }
441+ case . second:
442+ switch larger {
443+ case . second:
444+ return nil
445+ default :
446+ return maximumRange ( of: smaller)
447+ }
448+ case . nanosecond:
449+ return maximumRange ( of: smaller)
450+ default :
451+ break // Continue search
452+ }
453+
454+ switch larger {
455+ case . era:
456+ // assume it cycles through every possible combination in an era at least once; this is a little dodgy for the Japanese calendar but this calculation isn't terribly useful either
457+ switch smaller {
458+ case . year, . quarter, . month, . weekOfYear, . weekOfMonth, . day:
459+ return maximumRange ( of: smaller)
460+ case . weekdayOrdinal:
461+ guard let r = maximumRange ( of: . day) else { return nil }
462+ return 1 ..< ( ( ( r. lowerBound + ( r. upperBound - r. lowerBound) - 1 + 6 ) / 7 ) + 1 )
463+ default :
464+ break
465+ }
466+ case . year:
467+ switch smaller {
468+ case . month:
469+ return 1 ..< 13
470+ case . quarter, . weekOfYear: /* deprecated week */
471+ return _algorithmA ( smaller: smaller, larger: larger, at: capped)
472+ case . day:
473+ let year = dateComponent ( . year, from: date)
474+ let max = gregorianYearIsLeap ( year) ? 366 : 365
475+ return 1 ..< max + 1
476+ case . weekOfMonth, . weekdayOrdinal:
477+ return _algorithmB ( smaller: smaller, larger: larger, at: capped)
478+ default :
479+ break
480+ }
481+ case . yearForWeekOfYear:
482+ switch smaller {
483+ case . quarter, . month, . weekOfYear:
484+ return _algorithmA ( smaller: smaller, larger: larger, at: capped)
485+ case . weekOfMonth:
486+ break
487+ case . day, . weekdayOrdinal:
488+ return _algorithmB ( smaller: smaller, larger: larger, at: capped)
489+ default :
490+ break
491+ }
492+ case . quarter:
493+ switch smaller {
494+ case . month, . weekOfYear: /* deprecated week */
495+ return _algorithmC ( smaller: smaller, larger: larger, at: capped)
496+ case . weekOfMonth, . day, . weekdayOrdinal:
497+ return _algorithmB ( smaller: smaller, larger: larger, at: capped)
498+ default :
499+ break
500+ }
501+ case . month:
502+ switch smaller {
503+ case . weekOfYear: /* deprecated week */
504+ return _algorithmC ( smaller: smaller, larger: larger, at: capped)
505+ case . weekOfMonth, . day, . weekdayOrdinal:
506+ return _algorithmA ( smaller: smaller, larger: larger, at: capped)
507+ default :
508+ break
509+ }
510+ case . weekOfYear:
511+ break
512+ case . weekOfMonth: /* deprecated week */
513+ switch smaller {
514+ case . day:
515+ return _algorithmD ( at: capped)
516+ default :
517+ break
518+ }
519+ default :
520+ break
521+ }
522+
523+ return nil
293524 }
294525
295526 func minMaxRange( of component: Calendar . Component , in dateComponent: DateComponents ) -> Range < Int > ? {
0 commit comments