Skip to content

Commit ed71088

Browse files
authored
[Gregorian Calendar] Implement range(of:in:for:). (swiftlang#365)
* [Gregorian Calendar] Implement `range(of:in:for:)`. The logic is identical to that in Calendar_ICU.
1 parent d569316 commit ed71088

File tree

2 files changed

+417
-18
lines changed

2 files changed

+417
-18
lines changed

Sources/FoundationEssentials/Calendar/Calendar_Gregorian.swift

Lines changed: 249 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)