@@ -151,6 +151,13 @@ enum ResolvedDateComponents {
151151
152152}
153153
154+
155+ /// Internal-use error for indicating unexpected situations when finding dates.
156+ enum GregorianCalendarError : Error {
157+ case overflow( Calendar . Component , Date /* failing start date */, Date /* failing end date */)
158+ case notAdvancing( Date /* next */, Date /* previous */)
159+ }
160+
154161/// This class is a placeholder and work-in-progress to provide an implementation of the Gregorian calendar.
155162internal final class _CalendarGregorian : _CalendarProtocol , @unchecked Sendable {
156163
@@ -2525,9 +2532,167 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
25252532 }
25262533 }
25272534
2528-
2535+ // MARK: Differences
2536+
2537+ // Calendar::fieldDifference
2538+ func difference( inComponent component: Calendar . Component , from start: Date , to end: Date ) throws -> ( difference: Int , newStart: Date ) {
2539+ guard end != start else {
2540+ return ( 0 , start)
2541+ }
2542+
2543+ switch component {
2544+ case . calendar, . timeZone, . isLeapMonth:
2545+ preconditionFailure ( " Invalid arguments " )
2546+
2547+ case . era:
2548+ // Special handling since `add` below doesn't work with `era`
2549+ let currEra = dateComponent ( . era, from: start)
2550+ let goalEra = dateComponent ( . era, from: end)
2551+
2552+ return ( goalEra - currEra, start)
2553+ case . nanosecond:
2554+ let diffInNano = end. timeIntervalSince ( start) . remainder ( dividingBy: 1 ) * 1.0e+9
2555+ let diff = diffInNano < Double ( Int32 . max) ? Int ( diffInNano) : Int ( Int32 . max)
2556+ let advanced = add ( component, to: start, amount: diff, inTimeZone: timeZone)
2557+ return ( diff, advanced)
2558+
2559+ case . year, . month, . day, . hour, . minute, . second, . weekday, . weekdayOrdinal, . quarter, . weekOfMonth, . weekOfYear, . yearForWeekOfYear, . dayOfYear:
2560+ // continue to below
2561+ break
2562+ }
2563+
2564+ let forward = end > start
2565+ var max = forward ? 1 : - 1
2566+ var min = 0
2567+ while true {
2568+ let ms = add ( component, to: start, amount: max, inTimeZone: timeZone)
2569+ guard forward ? ( ms > start) : ( ms < start) else {
2570+ throw GregorianCalendarError . notAdvancing ( start, ms)
2571+ }
2572+
2573+ if ms == end {
2574+ return ( max, ms)
2575+ } else if ( forward && ms > end) || ( !forward && ms < end) {
2576+ break
2577+ } else {
2578+ min = max
2579+ max <<= 1
2580+ guard forward ? max >= 0 : max < 0 else {
2581+ throw GregorianCalendarError . overflow ( component, start, end)
2582+ }
2583+ }
2584+ }
2585+
2586+ // Binary search
2587+ while ( forward && ( max - min) > 1 ) || ( !forward && ( min - max > 1 ) ) {
2588+ let t = min + ( max - min) / 2
2589+
2590+ let ms = add ( component, to: start, amount: t, inTimeZone: timeZone)
2591+ if ms == end {
2592+ return ( t, ms)
2593+ } else if ( forward && ms > end) || ( !forward && ms < end) {
2594+ max = t
2595+ } else {
2596+ min = t
2597+ }
2598+ }
2599+
2600+ let advanced = add ( component, to: start, amount: min, inTimeZone: timeZone)
2601+
2602+ return ( min, advanced)
2603+ }
2604+
25292605 func dateComponents( _ components: Calendar . ComponentSet , from start: Date , to end: Date ) -> DateComponents {
2530- fatalError ( )
2606+ let cappedStart = start. capped
2607+ let cappedEnd = end. capped
2608+
2609+ let subseconds = cappedStart. timeIntervalSinceReferenceDate. remainder ( dividingBy: 1 )
2610+
2611+ var curr = cappedStart - subseconds
2612+ let goal = cappedEnd - subseconds
2613+ func orderedComponents( _ components: Calendar . ComponentSet ) -> [ Calendar . Component ] {
2614+ var comps : [ Calendar . Component ] = [ ]
2615+ if components. contains ( . era) {
2616+ comps. append ( . era)
2617+ }
2618+ if components. contains ( . year) {
2619+ comps. append ( . year)
2620+ }
2621+ if components. contains ( . yearForWeekOfYear) {
2622+ comps. append ( . yearForWeekOfYear)
2623+ }
2624+ if components. contains ( . quarter) {
2625+ comps. append ( . quarter)
2626+ }
2627+ if components. contains ( . month) {
2628+ comps. append ( . month)
2629+ }
2630+ if components. contains ( . weekOfYear) {
2631+ comps. append ( . weekOfYear)
2632+ }
2633+ if components. contains ( . weekOfMonth) {
2634+ comps. append ( . weekOfMonth)
2635+ }
2636+ if components. contains ( . day) {
2637+ comps. append ( . day)
2638+ }
2639+ if components. contains ( . weekday) {
2640+ comps. append ( . weekday)
2641+ }
2642+ if components. contains ( . weekdayOrdinal) {
2643+ comps. append ( . weekdayOrdinal)
2644+ }
2645+ if components. contains ( . hour) {
2646+ comps. append ( . hour)
2647+ }
2648+ if components. contains ( . minute) {
2649+ comps. append ( . minute)
2650+ }
2651+ if components. contains ( . second) {
2652+ comps. append ( . second)
2653+ }
2654+
2655+ if components. contains ( . nanosecond) {
2656+ comps. append ( . nanosecond)
2657+ }
2658+
2659+ return comps
2660+ }
2661+
2662+ var dc = DateComponents ( )
2663+
2664+ for component in orderedComponents ( components) {
2665+ switch component {
2666+ case . era, . year, . month, . day, . dayOfYear, . hour, . minute, . second, . weekday, . weekdayOrdinal, . weekOfYear, . yearForWeekOfYear, . weekOfMonth, . nanosecond:
2667+ do {
2668+ let ( diff, newStart) = try difference ( inComponent: component, from: curr, to: goal)
2669+ dc. setValue ( diff, for: component)
2670+ curr = newStart
2671+ } catch let error as GregorianCalendarError {
2672+ #if FOUNDATION_FRAMEWORK
2673+ switch error {
2674+
2675+ case . overflow( _, _, _) :
2676+ Logger ( Calendar . log) . error ( " Overflowing in dateComponents(from:start:end:). start: \( start. timeIntervalSinceReferenceDate, privacy: . public) end: \( end. timeIntervalSinceReferenceDate, privacy: . public) component: \( component, privacy: . public) " )
2677+ case . notAdvancing( _, _) :
2678+ Logger ( Calendar . log) . error ( " Not advancing in dateComponents(from:start:end:). start: \( start. timeIntervalSinceReferenceDate, privacy: . public) end: \( end. timeIntervalSinceReferenceDate, privacy: . public) component: \( component, privacy: . public) " )
2679+ }
2680+ #endif
2681+ dc. setValue ( 0 , for: component)
2682+ } catch {
2683+ preconditionFailure ( " Unknown error: \( error) " )
2684+ }
2685+
2686+ case . timeZone, . isLeapMonth, . calendar:
2687+ // No leap month support needed here, since these are quantities, not values
2688+ break
2689+ case . quarter:
2690+ // Currently unsupported so always return 0
2691+ dc. quarter = 0
2692+ }
2693+ }
2694+
2695+ return dc
25312696 }
25322697
25332698#if FOUNDATION_FRAMEWORK
0 commit comments