Skip to content

Commit aafc8a8

Browse files
committed
Add to the original start date, not the iterative result
1 parent f6caa92 commit aafc8a8

File tree

4 files changed

+66
-32
lines changed

4 files changed

+66
-32
lines changed

Sources/FoundationEssentials/Calendar/Calendar.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,11 @@ public struct Calendar : Hashable, Equatable, Sendable {
554554
byAdding component: Calendar.Component,
555555
value: Int = 1,
556556
wrappingComponents: Bool = false) -> some (Sequence<Date> & Sendable) {
557-
DatesByAdding(calendar: self, start: start, range: range, components: DateComponents(component: component, value: value), wrappingComponents: wrappingComponents)
557+
guard let components = DateComponents(component: component, value: value) else {
558+
preconditionFailure("Attempt to add with an invalid Calendar.Component argument")
559+
}
560+
561+
return DatesByAdding(calendar: self, start: start, range: range, components: components, wrappingComponents: wrappingComponents)
558562
}
559563

560564
/// Returns a sequence of `Date`s, calculated by repeatedly adding an amount of `Calendar.Component`s to a starting `Date` and then to each subsequent result.

Sources/FoundationEssentials/Calendar/Calendar_Enumerate.swift

Lines changed: 27 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -284,13 +284,13 @@ extension Calendar {
284284
struct DatesByMatching : Sendable, Sequence {
285285
typealias Element = Date
286286

287-
var calendar: Calendar
288-
var start: Date
289-
var range: Range<Date>?
290-
var matchingComponents: DateComponents
291-
var matchingPolicy: Calendar.MatchingPolicy
292-
var repeatedTimePolicy: Calendar.RepeatedTimePolicy
293-
var direction: Calendar.SearchDirection
287+
let calendar: Calendar
288+
let start: Date
289+
let range: Range<Date>?
290+
let matchingComponents: DateComponents
291+
let matchingPolicy: Calendar.MatchingPolicy
292+
let repeatedTimePolicy: Calendar.RepeatedTimePolicy
293+
let direction: Calendar.SearchDirection
294294

295295
init(calendar: Calendar, start: Date, range: Range<Date>?, matchingComponents: DateComponents, matchingPolicy: Calendar.MatchingPolicy = .nextTime, repeatedTimePolicy: Calendar.RepeatedTimePolicy = .first, direction: Calendar.SearchDirection = .forward) {
296296
self.calendar = calendar
@@ -391,14 +391,14 @@ extension Calendar {
391391
struct DatesByAdding : Sendable, Sequence {
392392
typealias Element = Date
393393

394-
var calendar: Calendar
395-
var start: Date
396-
var range: Range<Date>?
397-
var components: DateComponents?
398-
var wrappingComponents: Bool
394+
let calendar: Calendar
395+
let start: Date
396+
let range: Range<Date>?
397+
let components: DateComponents
398+
let wrappingComponents: Bool
399399

400400
/// If `components` is `nil`, the result is an empty `Sequence`.
401-
internal init(calendar: Calendar, start: Date, range: Range<Date>?, components: DateComponents?, wrappingComponents: Bool) {
401+
internal init(calendar: Calendar, start: Date, range: Range<Date>?, components: DateComponents, wrappingComponents: Bool) {
402402
self.calendar = calendar
403403
self.start = start
404404
self.range = range
@@ -407,47 +407,43 @@ extension Calendar {
407407
}
408408

409409
struct Iterator: Sendable, IteratorProtocol {
410-
var calendar: Calendar
411-
var previousResult: Date?
410+
let calendar: Calendar
411+
let start: Date
412412
let range: Range<Date>?
413-
var components: DateComponents?
414-
var wrappingComponents: Bool
413+
let components: DateComponents
414+
let wrappingComponents: Bool
415+
var finished = false
416+
var iteration = 1
415417

416418
/// If `components` is `nil`, the result is an empty `Sequence`.
417-
init(calendar: Calendar, start: Date, range: Range<Date>?, components: DateComponents?, wrappingComponents: Bool) {
419+
init(calendar: Calendar, start: Date, range: Range<Date>?, components: DateComponents, wrappingComponents: Bool) {
420+
self.start = start
418421
self.calendar = calendar
419-
previousResult = start
420422
self.components = components
421423
self.range = range
422424
self.wrappingComponents = wrappingComponents
423425
}
424426

425427
mutating func next() -> Element? {
426-
guard let components else {
427-
// We have nothing to add
428+
guard !finished else {
428429
return nil
429430
}
430431

431-
guard let startAt = previousResult else {
432-
// We have finished
433-
return nil
434-
}
435-
436-
let next = calendar.date(byAdding: components, to: startAt, wrappingComponents: wrappingComponents)
432+
let scaled = components.scaled(by: iteration)
433+
let next = calendar.date(byAdding: scaled, to: start, wrappingComponents: wrappingComponents)
437434
guard let next else {
438435
// We have finished
439-
previousResult = nil
436+
finished = false
440437
return nil
441438
}
442439

443440
if let range, !range.contains(next) {
444441
// We are out of the range
445-
previousResult = nil
442+
finished = true
446443
return nil
447444
}
448445

449-
// Next addition is based on this new result
450-
previousResult = next
446+
iteration += 1
451447
return next
452448
}
453449
}

Sources/FoundationEssentials/Calendar/DateComponents.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,27 @@ public struct DateComponents : Hashable, Equatable, Sendable {
393393
}
394394
return true
395395
}
396+
397+
// MARK: -
398+
399+
/// Returns a new `DateComponents` where the subset of fields that can be scaled have been mulitplied by `value`.
400+
internal func scaled(by value: Int) -> DateComponents {
401+
var dc = self
402+
if let era = _era { dc.era = era * value }
403+
if let year = _year { dc.year = year * value }
404+
if let month = _month { dc.month = month * value }
405+
if let day = _day { dc.day = day * value }
406+
if let hour = _hour { dc.hour = hour * value }
407+
if let minute = _minute { dc.minute = minute * value }
408+
if let second = _second { dc.second = second * value }
409+
if let nanosecond = _nanosecond { dc.nanosecond = nanosecond * value }
410+
if let quarter = _quarter { dc.quarter = quarter * value }
411+
if let week = _week { dc.week = week * value }
412+
if let weekOfMonth = _weekOfMonth { dc.weekOfMonth = weekOfMonth * value }
413+
if let weekOfYear = _weekOfYear { dc.weekOfYear = weekOfYear * value }
414+
if let yearForWeekOfYear = _yearForWeekOfYear { dc.yearForWeekOfYear = yearForWeekOfYear * value }
415+
return dc
416+
}
396417

397418
// MARK: -
398419

Tests/FoundationInternationalizationTests/CalendarTests.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,19 @@ final class CalendarTests : XCTestCase {
659659
XCTAssertEqual(numberOfDays, 3)
660660
}
661661

662+
@available(FoundationPreview 0.4, *)
663+
func test_datesAdding_year() {
664+
// Verify that adding 12 months once is the same as adding 1 month 12 times
665+
let startDate = Date(timeIntervalSinceReferenceDate: 688946558.712307) // 2022-10-31 22:02:38 UTC
666+
var cal = Calendar(identifier: .gregorian)
667+
cal.timeZone = TimeZone.gmt
668+
669+
let oneYearOnce = cal.date(byAdding: .month, value: 12, to: startDate)
670+
let oneYearTwelve = Array(cal.dates(startingAt: startDate, byAdding: .month, value: 1).prefix(12)).last!
671+
672+
XCTAssertEqual(oneYearOnce, oneYearTwelve)
673+
}
674+
662675
@available(FoundationPreview 0.4, *)
663676
func test_datesMatching_simpleExample() {
664677
let cal = Calendar(identifier: .gregorian)

0 commit comments

Comments
 (0)