Created
December 17, 2017 21:37
-
-
Save blwinters/d4a49b99584e5a5e4eb7809adbba35ba to your computer and use it in GitHub Desktop.
Unit tests for generating dates from SMRecurrenceRule.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// SMRecurrenceRuleTests.swift | |
// Summit_iOS_Tests | |
// | |
// Created by Ben Winters on 9/6/17. | |
// Copyright © 2017 Goals LLC. All rights reserved. | |
// | |
import EventKit | |
import Foundation | |
import XCTest | |
@testable import Summit_iOS | |
struct RecurrenceRuleTestModel { | |
let firstStart: Date | |
let firstEnd: Date | |
let rangeStart: Date | |
let rangeEnd: Date | |
let strictRange: Bool | |
init(firstStart: Date, firstEnd: Date, rangeEnd: Date, strictRange: Bool = true, cal: Calendar) { | |
self.firstStart = firstStart | |
self.firstEnd = firstEnd | |
self.rangeStart = cal.startOfDay(for: firstStart) | |
self.rangeEnd = rangeEnd | |
self.strictRange = strictRange | |
} | |
} | |
struct StartDayCountTestModel { | |
var startDay: EKWeekday | |
var expectedCount: Int | |
} | |
struct OrdinalWeekdayTestModel { | |
var frequency: SMRecurrenceFrequency | |
var weekNumber = 0 | |
var setPositions: [Int]? | |
} | |
// swiftlint:disable type_body_length | |
class SMRecurrenceRuleTests: XCTestCase { | |
let cal = Calendar(identifier: .gregorian) | |
//For asserting the equivalency of two dates if using timeIntervalSinceReferenceDate | |
/* | |
let accuracy: Double = 0.001 //millisecond accuracy | |
*/ | |
func defaultStart() -> Date { | |
return Date().withoutNanoseconds() | |
} | |
func defaultEnd(for date: Date) -> Date { | |
return date.addHours(1) | |
} | |
//Use this when not testing the occurrenceCount or endDate | |
func defaultRangeStart() -> Date { | |
return Date().addMonths(-12) | |
} | |
//Use this when not testing the occurrenceCount or endDate | |
func defaultRangeEnd() -> Date { | |
return Date().addMonths(24) | |
} | |
override func setUp() { | |
super.setUp() | |
} | |
override func tearDown() { | |
super.tearDown() | |
} | |
func testDailyRecurrenceEndCount() { | |
let firstStart = defaultStart() | |
let firstEnd = defaultEnd(for: firstStart) | |
let count = 50 | |
let rangeEnd = firstStart.addMonths(3) | |
let ruleEnd = SMRecurrenceEnd(occurrenceCount: count) | |
let rule = SMRecurrenceRule(frequency: .daily, interval: 1, end: ruleEnd) | |
let testModels = [RecurrenceRuleTestModel(firstStart: firstStart, firstEnd: firstEnd, rangeEnd: rangeEnd, cal: cal), | |
] | |
for (i, model) in testModels.enumerated() { | |
let occurrences = rule.generateOccurrenceDates(firstStart: model.firstStart, firstEnd: model.firstEnd, exceptionDates: nil, from: model.firstStart.dayStart, upTo: model.rangeEnd, strictRange: model.strictRange) | |
//print("Model \(i) occurrences: \(occurrences)") | |
let expectedLastStart = model.firstStart.addDays(count - 1) | |
let msg = "Model \(i)" | |
XCTAssertEqual(occurrences.count, ruleEnd.occurrenceCount, msg) | |
XCTAssertEqual(model.firstStart, occurrences.first?.start, msg) | |
XCTAssertEqual(model.firstEnd, occurrences.first?.end, msg) | |
XCTAssertEqual(expectedLastStart, occurrences.last?.start, msg) | |
let occurrenceStartDates = occurrences.map({$0.start}) | |
XCTAssertTrue(occurrenceStartDates.contains(firstStart)) | |
} | |
} | |
func testDailyRecurrenceEndDate() { | |
let firstStart = defaultStart() | |
let firstEnd = defaultEnd(for: firstStart) | |
let days = 5 | |
let rangeEnd = firstStart.addMonths(1) | |
let ruleEnd = SMRecurrenceEnd(end: firstStart.addDays(days).dayEnd) | |
let rule = SMRecurrenceRule(frequency: .daily, interval: 1, end: ruleEnd) | |
let testModels = [RecurrenceRuleTestModel(firstStart: firstStart, firstEnd: firstEnd, rangeEnd: rangeEnd, cal: cal), | |
RecurrenceRuleTestModel(firstStart: firstStart.dayEnd, firstEnd: firstEnd, rangeEnd: rangeEnd, strictRange: false, cal: cal), | |
RecurrenceRuleTestModel(firstStart: firstStart.dayStart, firstEnd: firstEnd, rangeEnd: rangeEnd, cal: cal), | |
] | |
for (i, model) in testModels.enumerated() { | |
let expectedLastEnd = model.firstEnd.addDays(days) | |
let occurrences = rule.generateOccurrenceDates(firstStart: model.firstStart, firstEnd: model.firstEnd, exceptionDates: nil, from: model.firstStart.dayStart, upTo: model.rangeEnd, strictRange: model.strictRange) | |
//print("Model \(i) occurrences: \(occurrences)") | |
let msg = "Model \(i)" | |
XCTAssertEqual(occurrences.count, days + 1, msg) | |
XCTAssertEqual(model.firstStart, occurrences.first?.start, msg) | |
XCTAssertEqual(expectedLastEnd, occurrences.last?.end, msg) | |
if model.strictRange { | |
XCTAssertTrue(expectedLastEnd <= ruleEnd.endDate!.dayEnd, msg) | |
} | |
} | |
} | |
func testDailyStrictRange() { | |
let firstStart = defaultStart().nextDayStart.addMinutes(-30) | |
let firstEnd = defaultEnd(for: firstStart) | |
let days = 5 | |
let rangeEnd = firstStart.addMonths(1) | |
let ruleEnd = SMRecurrenceEnd(end: firstStart.addDays(days).dayEnd) | |
let rule = SMRecurrenceRule(frequency: .daily, interval: 1, end: ruleEnd) | |
let testModels = [RecurrenceRuleTestModel(firstStart: firstStart, firstEnd: firstEnd, rangeEnd: rangeEnd, strictRange: true, cal: cal), | |
RecurrenceRuleTestModel(firstStart: firstStart, firstEnd: firstEnd, rangeEnd: rangeEnd, strictRange: false, cal: cal), | |
] | |
for (i, model) in testModels.enumerated() { | |
let occurrences = rule.generateOccurrenceDates(firstStart: model.firstStart, firstEnd: model.firstEnd, exceptionDates: nil, from: model.firstStart.dayStart, upTo: model.rangeEnd, strictRange: model.strictRange) | |
//print("Model \(i) occurrences: \(occurrences)") | |
let expectedCount = model.strictRange ? days : days + 1 | |
let expectedLastStart = model.firstStart.addDays(expectedCount - 1) | |
let expectedLastEnd = defaultEnd(for: expectedLastStart) | |
let msg = "Model \(i)" | |
XCTAssertEqual(occurrences.count, expectedCount, msg) | |
XCTAssertEqual(model.firstStart, occurrences.first?.start, msg) | |
XCTAssertEqual(expectedLastEnd, occurrences.last?.end, msg) | |
} | |
} | |
func testEvery3Days() { | |
let firstStart = defaultStart() | |
let firstEnd = defaultEnd(for: firstStart) | |
//print("First start: \(firstStart.debugDescription), first end: \(firstEnd.debugDescription), duration: \(firstEnd.timeIntervalSince(firstStart))") | |
let interval = 3 | |
let expectedCount = 11 | |
let rangeDays = Int((expectedCount - 1) * interval) | |
let rangeEnd = firstStart.addDays(rangeDays).dayEnd | |
let rule = SMRecurrenceRule(frequency: .daily, interval: interval, end: nil) | |
let testModels = [RecurrenceRuleTestModel(firstStart: firstStart, firstEnd: firstEnd, rangeEnd: rangeEnd, strictRange: true, cal: cal), | |
] | |
for (i, model) in testModels.enumerated() { | |
let occurrences = rule.generateOccurrenceDates(firstStart: model.firstStart, firstEnd: model.firstEnd, exceptionDates: nil, from: model.firstStart.dayStart, upTo: model.rangeEnd, strictRange: model.strictRange) | |
//print("Model \(i) occurrences: \(occurrences)") | |
let expectedLastStart = model.firstStart.addDays(rangeDays) | |
let expectedLastEnd = defaultEnd(for: expectedLastStart) | |
//print("Expected last start: \(expectedLastStart), end: \(expectedLastEnd)") | |
let msg = "Model \(i)" | |
guard let firstOccurrenceStart = occurrences.first?.start, let lastOccurrenceEnd = occurrences.last?.end else { | |
XCTFail("\(msg) occurrence was nil") | |
continue | |
} | |
XCTAssertEqual(occurrences.count, expectedCount, msg) | |
XCTAssertEqual(model.firstStart, firstOccurrenceStart, msg) | |
XCTAssertEqual(expectedLastEnd, lastOccurrenceEnd, msg) | |
} | |
} | |
//For these tests with a weekly repeating rule, the first occurrence is not necessarily on the specified weekday. | |
//However, all other generated dates should follow it chronologically and have the correct weekday. | |
func testEverySundayStartingSunday() { | |
//This tests a single-day date range, to focus on a bug discovered in the Planner | |
//The bug was created by using Monday as the default firstDayOfTheWeek, fixed it by changing to Sunday as the default | |
let dayOfWeek = EKWeekday.sunday | |
let startDate = Date.date(2017, 10, 8, time: 10, 0) //a Sunday | |
let firstStartComps = cal.dateComponents([.hour, .minute, .second, .weekday, .weekOfYear, .yearForWeekOfYear], from: startDate) | |
let firstStart = cal.date(from: firstStartComps)! | |
let firstEnd = defaultEnd(for: firstStart) | |
print("First start: \(firstStart), \(firstStartComps)") | |
let expectedStart = startDate.addDays(21) | |
let unitArrays = SMRecurrenceRuleUnitArrays(daysOfTheWeek: nil, daysOfTheMonth: nil, monthsOfTheYear: nil, weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: nil) | |
let rule = SMRecurrenceRule(frequency: .weekly, interval: 1, end: nil, unitArrays: unitArrays) //daysOfTheWeek is not specified when it matches the startDate | |
let occurrences = rule.generateOccurrenceDates(firstStart: firstStart, firstEnd: firstEnd, exceptionDates: nil, from: expectedStart.dayStart, upTo: expectedStart.dayEnd, strictRange: true) | |
guard let firstOccurrenceStart = occurrences.first?.start, let lastOccurrenceStart = occurrences.last?.start else { | |
XCTFail("Occurrence was nil") | |
return | |
} | |
//firstStart should equal the generated firstOccurrenceStart | |
XCTAssertEqual(expectedStart, firstOccurrenceStart) | |
XCTAssertEqual(occurrences.count, 1) | |
//The first and last generated occurrences should both have a weekday that matches the dayOfWeek | |
let firstOccurrenceWeekday = cal.dateComponents([.weekday], from: firstOccurrenceStart) | |
let lastOccurrenceWeekday = cal.dateComponents([.weekday], from: lastOccurrenceStart) | |
XCTAssertEqual(dayOfWeek.rawValue, firstOccurrenceWeekday.weekday ?? 0) | |
XCTAssertEqual(dayOfWeek.rawValue, lastOccurrenceWeekday.weekday ?? 0) | |
} | |
func testEverySundayStartingNotSunday() { | |
//This tests a single-day date range, to focus on a bug discovered in the Planner | |
//The bug was created by using Monday as the default firstDayOfTheWeek, fixed it by changing to Sunday as the default | |
let weekday = EKWeekday.sunday | |
let startDate = Date.date(2017, 10, 9, time: 10, 0) //a Monday | |
let firstStartComps = cal.dateComponents([.hour, .minute, .second, .weekday, .weekOfYear, .yearForWeekOfYear], from: startDate) | |
let firstStart = cal.date(from: firstStartComps)! | |
let firstEnd = defaultEnd(for: firstStart) | |
print("First start: \(firstStart), \(firstStartComps)") | |
let expectedStart = startDate.addDays(20) //-1 day to get the Sunday that precedes the Monday | |
let daysOfTheWeek = SMRecurrenceDayOfWeek.createDays(for: [weekday]) //need to specify the daysOfTheWeek since they don't match the startDate | |
let unitArrays = SMRecurrenceRuleUnitArrays(daysOfTheWeek: daysOfTheWeek, daysOfTheMonth: nil, monthsOfTheYear: nil, weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: nil) | |
let rule = SMRecurrenceRule(frequency: .weekly, interval: 1, end: nil, unitArrays: unitArrays) | |
let occurrences = rule.generateOccurrenceDates(firstStart: firstStart, firstEnd: firstEnd, exceptionDates: nil, from: expectedStart.dayStart, upTo: expectedStart.dayEnd, strictRange: true) | |
guard let firstOccurrenceStart = occurrences.first?.start, let lastOccurrenceStart = occurrences.last?.start else { | |
XCTFail("Occurrence was nil") | |
return | |
} | |
//firstStart should equal the generated firstOccurrenceStart | |
XCTAssertEqual(expectedStart, firstOccurrenceStart) | |
XCTAssertEqual(occurrences.count, 1) | |
//The first and last generated occurrences should both have a weekday that matches the dayOfWeek | |
let firstOccurrenceWeekday = cal.dateComponents([.weekday], from: firstOccurrenceStart) | |
let lastOccurrenceWeekday = cal.dateComponents([.weekday], from: lastOccurrenceStart) | |
XCTAssertEqual(weekday.rawValue, firstOccurrenceWeekday.weekday ?? 0) | |
XCTAssertEqual(weekday.rawValue, lastOccurrenceWeekday.weekday ?? 0) | |
} | |
func testEveryTuesdayStartingTuesday() { | |
let now = defaultStart() | |
var firstStartComps = cal.dateComponents([.hour, .minute, .second, .weekday, .weekOfYear, .yearForWeekOfYear], from: now) | |
firstStartComps.setValue(EKWeekday.tuesday.rawValue, for: .weekday) | |
let firstStart = cal.date(from: firstStartComps)! | |
let firstEnd = defaultEnd(for: firstStart) | |
//print("First start: \(firstStart), \(firstStartComps)") | |
let dayOfWeek = SMRecurrenceDayOfWeek(.tuesday) | |
let unitArrays = SMRecurrenceRuleUnitArrays(daysOfTheWeek: [dayOfWeek], daysOfTheMonth: nil, monthsOfTheYear: nil, weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: nil) | |
let rule = SMRecurrenceRule(frequency: .weekly, interval: 1, end: nil, unitArrays: unitArrays) | |
let occurrences = rule.generateOccurrenceDates(firstStart: firstStart, firstEnd: firstEnd, exceptionDates: nil, from: defaultRangeStart(), upTo: defaultRangeEnd(), strictRange: true) | |
guard let firstOccurrenceStart = occurrences.first?.start, let lastOccurrenceStart = occurrences.last?.start else { | |
XCTFail("Occurrence was nil") | |
return | |
} | |
//firstStart should equal the generated firstOccurrenceStart | |
XCTAssertEqual(firstStart, firstOccurrenceStart) | |
//First and last occurrences should not be the same | |
XCTAssertNotEqual(firstOccurrenceStart, lastOccurrenceStart) | |
//The first and last generated occurrences should both have a weekday that matches the dayOfWeek | |
let firstOccurrenceWeekday = cal.dateComponents([.weekday], from: firstOccurrenceStart) | |
let lastOccurrenceWeekday = cal.dateComponents([.weekday], from: lastOccurrenceStart) | |
XCTAssertEqual(dayOfWeek.dayOfTheWeek.rawValue, firstOccurrenceWeekday.weekday ?? 0) | |
XCTAssertEqual(dayOfWeek.dayOfTheWeek.rawValue, lastOccurrenceWeekday.weekday ?? 0) | |
} | |
func testEveryTuesdayStartingNotTuesday() { | |
let now = defaultStart() | |
let startingWeekdays: [EKWeekday] = [.sunday, .monday, .wednesday, .thursday, .friday, .saturday] | |
for ekWeekday in startingWeekdays { | |
//Initial occurrence is not on the same day as the recurrence rule | |
var firstStartComps = cal.dateComponents([.hour, .minute, .second, .weekday, .weekOfYear, .yearForWeekOfYear], from: now) | |
firstStartComps.setValue(ekWeekday.rawValue, for: .weekday) | |
let firstStart = cal.date(from: firstStartComps)! | |
let firstEnd = defaultEnd(for: firstStart) | |
print("First start: \(firstStart), \(firstStartComps)") | |
//Subsequent occurences should be on Tuesdays | |
let dayOfWeek = SMRecurrenceDayOfWeek(.tuesday) | |
let unitArrays = SMRecurrenceRuleUnitArrays(daysOfTheWeek: [dayOfWeek], daysOfTheMonth: nil, monthsOfTheYear: nil, weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: nil) | |
let rule = SMRecurrenceRule(frequency: .weekly, interval: 1, end: nil, unitArrays: unitArrays) | |
let occurrences = rule.generateOccurrenceDates(firstStart: firstStart, firstEnd: firstEnd, exceptionDates: nil, from: defaultRangeStart(), upTo: defaultRangeEnd(), strictRange: true) | |
guard let firstOccurrenceStart = occurrences.first?.start, let lastOccurrenceStart = occurrences.last?.start else { | |
XCTFail("Occurrence was nil") | |
return | |
} | |
let msg = "\(ekWeekday.description)" | |
//firstStart should equal the generated firstOccurrenceStart | |
XCTAssertEqual(firstStart, firstOccurrenceStart, msg) | |
//The firstStart should always precede the second occurrence. | |
//The second occurrence may be in the same week if ekWeekday is .sunday or .monday | |
let secondStart = occurrences[1].start | |
print("Second start: \(secondStart)") | |
XCTAssertTrue(firstStart < secondStart, msg) | |
//First and last occurrences should not be the same | |
XCTAssertNotEqual(firstOccurrenceStart, lastOccurrenceStart, msg) | |
//The first and last generated occurrences should both have a weekday that matches the dayOfWeek | |
let firstOccurrenceWeekday = cal.dateComponents([.weekday], from: firstOccurrenceStart) | |
let secondOccurrenceWeekday = cal.dateComponents([.weekday], from: secondStart) | |
let lastOccurrenceWeekday = cal.dateComponents([.weekday], from: lastOccurrenceStart) | |
XCTAssertNotEqual(dayOfWeek.dayOfTheWeek.rawValue, firstOccurrenceWeekday.weekday ?? 0, msg) | |
XCTAssertEqual(dayOfWeek.dayOfTheWeek.rawValue, secondOccurrenceWeekday.weekday ?? 0, msg) | |
XCTAssertEqual(dayOfWeek.dayOfTheWeek.rawValue, lastOccurrenceWeekday.weekday ?? 0, msg) | |
} | |
} | |
func testEveryTuesdayDayRangeTuesday() { | |
let now = defaultStart() | |
var firstStartComps = cal.dateComponents([.hour, .minute, .second, .weekday, .weekOfYear, .yearForWeekOfYear], from: now) | |
firstStartComps.setValue(EKWeekday.tuesday.rawValue, for: .weekday) | |
let firstStart = cal.date(from: firstStartComps)! | |
let firstEnd = defaultEnd(for: firstStart) | |
//print("First start: \(firstStart), \(firstStartComps)") | |
let secondStart = firstStart.addDays(7) | |
let dayOfWeek = SMRecurrenceDayOfWeek(.tuesday) | |
let unitArrays = SMRecurrenceRuleUnitArrays(daysOfTheWeek: [dayOfWeek], daysOfTheMonth: nil, monthsOfTheYear: nil, weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: nil) | |
let rule = SMRecurrenceRule(frequency: .weekly, interval: 1, end: nil, unitArrays: unitArrays) | |
let occurrences = rule.generateOccurrenceDates(firstStart: firstStart, firstEnd: firstEnd, exceptionDates: nil, from: secondStart.dayStart, upTo: secondStart.dayEnd, strictRange: true) | |
guard let firstOccurrenceStart = occurrences.first?.start else { | |
XCTFail("Occurrence was nil") | |
return | |
} | |
XCTAssertNotEqual(firstStart, firstOccurrenceStart) | |
XCTAssertEqual(secondStart, firstOccurrenceStart) | |
XCTAssertEqual(occurrences.count, 1) | |
let firstOccurrenceWeekday = cal.dateComponents([.weekday], from: firstOccurrenceStart) | |
XCTAssertEqual(firstOccurrenceWeekday.weekday ?? 0, dayOfWeek.dayOfTheWeek.rawValue) | |
} | |
func testEveryMWF() { | |
let now = defaultStart() | |
let units: [Calendar.Component] = [.hour, .minute, .second, .weekday, .weekOfYear, .yearForWeekOfYear] | |
let rangeStart = now.addDays(-14) //arbitrary | |
let unadjustedRangeEnd = now.addDays(21) //add three weeks to setup four-week range | |
var rangeEndComps = cal.dateComponents(Set(units), from: unadjustedRangeEnd) | |
rangeEndComps.setValue(EKWeekday.saturday.rawValue, for: .weekday) //end of week | |
let rangeEnd = cal.date(from: rangeEndComps)!.dayEnd //end of day | |
let testModels: [StartDayCountTestModel] = [ | |
StartDayCountTestModel(startDay: .sunday, expectedCount: 13), | |
StartDayCountTestModel(startDay: .monday, expectedCount: 12), | |
StartDayCountTestModel(startDay: .tuesday, expectedCount: 12), | |
StartDayCountTestModel(startDay: .wednesday, expectedCount: 11), | |
StartDayCountTestModel(startDay: .thursday, expectedCount: 11), | |
StartDayCountTestModel(startDay: .friday, expectedCount: 10), | |
StartDayCountTestModel(startDay: .saturday, expectedCount: 10), | |
] | |
for model in testModels { | |
var firstStartComps = cal.dateComponents(Set(units), from: now) | |
firstStartComps.setValue(model.startDay.rawValue, for: .weekday) | |
let firstStart = cal.date(from: firstStartComps)! | |
let firstEnd = defaultEnd(for: firstStart) | |
//print("First start: \(firstStart), \(firstStartComps)") | |
let daysOfWeek = SMRecurrenceDayOfWeek.createDays(for: [.monday, .wednesday, .friday]) | |
let unitArrays = SMRecurrenceRuleUnitArrays(daysOfTheWeek: daysOfWeek, daysOfTheMonth: nil, monthsOfTheYear: nil, weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: nil) | |
let rule = SMRecurrenceRule(frequency: .weekly, interval: 1, end: nil, unitArrays: unitArrays) | |
let occurrences = rule.generateOccurrenceDates(firstStart: firstStart, firstEnd: firstEnd, exceptionDates: nil, from: rangeStart, upTo: rangeEnd, strictRange: true) | |
print("End of occurrences starting \(model.startDay.description), \(firstStart)") | |
guard let firstOccurrenceStart = occurrences.first?.start else { | |
XCTFail("Occurrence was nil") | |
return | |
} | |
let msg = model.startDay.description | |
XCTAssertEqual(occurrences.count, model.expectedCount, msg) | |
let firstOccurrenceWeekday = cal.dateComponents([.weekday], from: firstOccurrenceStart) | |
XCTAssertEqual(firstOccurrenceWeekday.weekday!, model.startDay.rawValue, msg) | |
} | |
} | |
/** | |
Repeating was missing items in beginning of the final week in the range | |
if the starting weekday value was greater than the range end weekday value. | |
*/ | |
func testEveryMTWThFSPartialWeek() { | |
let rangeStart = Date.date(2017, 9, 1) | |
let rangeEnd = Date.date(2017, 10, 3).dayEnd //Tuesday | |
let firstStart = Date.date(2017, 9, 20, time: 6, 0) //Wednesday | |
let firstEnd = defaultEnd(for: firstStart) | |
let daysOfWeek = SMRecurrenceDayOfWeek.createDays(for: [.monday, .tuesday, .wednesday, .thursday, .friday, .saturday]) | |
let unitArrays = SMRecurrenceRuleUnitArrays(daysOfTheWeek: daysOfWeek, daysOfTheMonth: nil, monthsOfTheYear: nil, weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: nil) | |
let rule = SMRecurrenceRule(frequency: .weekly, interval: 1, end: nil, unitArrays: unitArrays) | |
let occurrences = rule.generateOccurrenceDates(firstStart: firstStart, firstEnd: firstEnd, exceptionDates: nil, from: rangeStart, upTo: rangeEnd, strictRange: true) | |
print("End of occurrences starting \(firstStart)") | |
guard let firstOccurrenceStart = occurrences.first?.start else { | |
XCTFail("Occurrence was nil") | |
return | |
} | |
XCTAssertEqual(occurrences.count, 12) | |
let firstOccurrenceComps = cal.dateComponents([.weekday], from: firstOccurrenceStart) | |
XCTAssertEqual(firstOccurrenceComps.weekday!, SMWeekday.wednesday.rawValue) | |
} | |
func testDayOfWeekValues() { | |
XCTAssertEqual(Array(1...7), Array(EKWeekday.sunday.rawValue...EKWeekday.saturday.rawValue)) | |
XCTAssertEqual(Array(1...7), Array(SMWeekday.sunday.rawValue...SMWeekday.saturday.rawValue)) | |
} | |
func testSingleOrdinalWeekday() { | |
//use this only for its time components in this test | |
let now = defaultStart() | |
let year = 2018 | |
let weekdayOrdinal = 1 | |
let weekdayValue = 7 | |
var firstStartComps = cal.dateComponents([.hour, .minute, .second], from: now) | |
firstStartComps.setValue(year, for: .year) | |
firstStartComps.setValue(weekdayValue, for: .weekday) | |
let months = Array(1...12) | |
let expectedStartDates: [Date] = months.map({ month in | |
firstStartComps.setValue(month, for: .month) | |
firstStartComps.setValue(weekdayOrdinal, for: .weekdayOrdinal) | |
let startDate = cal.date(from: firstStartComps)! | |
return startDate | |
}) | |
let firstStart = expectedStartDates.first! | |
let firstEnd = defaultEnd(for: firstStart) | |
let rangeStart = firstStart.yearStart | |
let rangeEnd = rangeStart.yearEnd | |
//Create rule | |
let smWeekday = SMWeekday(rawValue: weekdayValue)! | |
let dayOfWeek = SMRecurrenceDayOfWeek(smWeekday) //Calendar.app creates this rule using setPositions instead of weekNumber | |
let unitArrays = SMRecurrenceRuleUnitArrays(daysOfTheWeek: [dayOfWeek], daysOfTheMonth: nil, monthsOfTheYear: nil, weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: [weekdayOrdinal]) | |
let rule = SMRecurrenceRule(frequency: .monthly, interval: 1, end: nil, unitArrays: unitArrays) | |
let occurrences = rule.generateOccurrenceDates(firstStart: firstStart, firstEnd: firstEnd, exceptionDates: nil, from: rangeStart, upTo: rangeEnd, strictRange: true) | |
let testCase = "Week: \(weekdayOrdinal), \(smWeekday.description)" | |
print("End of occurrences starting \(rangeStart), \(testCase)") | |
//Tests | |
guard let firstOccurrenceStart = occurrences.first?.start, let lastOccurrenceStart = occurrences.last?.start else { | |
XCTFail("Occurrence was nil") | |
return | |
} | |
XCTAssertEqual(firstStart, firstOccurrenceStart, testCase) | |
XCTAssertEqual(expectedStartDates.count, months.count, testCase) | |
XCTAssertEqual(occurrences.count, months.count, testCase) | |
let testPairs = zip(occurrences, expectedStartDates) | |
for (i, (occurrence, expectedStart)) in testPairs.enumerated() { | |
XCTAssertEqual(occurrence.start, expectedStart, "\(testCase), Pair \(i)") | |
} | |
let lastOccurrenceWeekday = cal.dateComponents([.weekday], from: lastOccurrenceStart) | |
XCTAssertEqual(lastOccurrenceWeekday.weekday!, firstStartComps.weekday!, testCase) | |
} | |
func test2ndTuesdaySeptemberWeekNumberSetPositions() { | |
//use this only for its time components in this test | |
let now = defaultStart() | |
let years = [2018, 2019, 2020] | |
let monthsOfYear = [9] | |
let weekdayOrdinal = 2 | |
let weekdayValue = EKWeekday.tuesday.rawValue | |
var firstStartComps = cal.dateComponents([.hour, .minute, .second], from: now) | |
firstStartComps.setValue(weekdayValue, for: .weekday) | |
let expectedStartDates: [Date] = years.reduce([], { results, year in | |
firstStartComps.setValue(year, for: .year) | |
return results + monthsOfYear.flatMap({ month in | |
firstStartComps.setValue(month, for: .month) | |
firstStartComps.setValue(weekdayOrdinal, for: .weekdayOrdinal) | |
return cal.date(from: firstStartComps) | |
}) | |
}) | |
let firstStart = expectedStartDates.first! | |
let firstEnd = defaultEnd(for: firstStart) | |
let rangeStart = firstStart.yearStart | |
let rangeEnd = rangeStart.addYears(2).yearEnd | |
let testModels = [OrdinalWeekdayTestModel(frequency: .monthly, weekNumber: weekdayOrdinal, setPositions: nil), | |
OrdinalWeekdayTestModel(frequency: .yearly, weekNumber: weekdayOrdinal, setPositions: nil), | |
OrdinalWeekdayTestModel(frequency: .monthly, weekNumber: 0, setPositions: [weekdayOrdinal]), | |
OrdinalWeekdayTestModel(frequency: .yearly, weekNumber: 0, setPositions: [weekdayOrdinal]), | |
] | |
for model in testModels { | |
//Create rule | |
let smWeekday = SMWeekday(rawValue: weekdayValue)! | |
let dayOfWeek = SMRecurrenceDayOfWeek(smWeekday, weekNumber: model.weekNumber) | |
let unitArrays = SMRecurrenceRuleUnitArrays(daysOfTheWeek: [dayOfWeek], daysOfTheMonth: nil, monthsOfTheYear: monthsOfYear, weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: model.setPositions) | |
//Frequency needs to be monthly when a set position is applied to single or multiple days of the week. | |
//The initial interface shows yearly, but the underlying logic creates a rule with monthly frequency. | |
let rule = SMRecurrenceRule(frequency: model.frequency, interval: 1, end: nil, unitArrays: unitArrays) | |
let occurrences = rule.generateOccurrenceDates(firstStart: firstStart, firstEnd: firstEnd, exceptionDates: nil, from: rangeStart, upTo: rangeEnd, strictRange: true) | |
let usingSetPositions = model.setPositions != nil | |
let testCase = "Frequency: \(model.frequency.description), Using set positions: \(usingSetPositions)" | |
print("End of occurrences starting \(rangeStart), \(testCase)") | |
//Tests | |
guard let firstOccurrenceStart = occurrences.first?.start, let lastOccurrenceStart = occurrences.last?.start else { | |
XCTFail("Occurrence was nil") | |
return | |
} | |
XCTAssertEqual(firstStart, firstOccurrenceStart, testCase) | |
let expectedCount = monthsOfYear.count * years.count | |
XCTAssertEqual(expectedStartDates.count, expectedCount, testCase) | |
XCTAssertEqual(occurrences.count, expectedCount, testCase) | |
let testPairs = zip(occurrences, expectedStartDates) | |
for (i, (occurrence, expectedStart)) in testPairs.enumerated() { | |
XCTAssertEqual(occurrence.start, expectedStart, "\(testCase), Pair \(i)") | |
} | |
let lastOccurrenceWeekday = cal.dateComponents([.weekday], from: lastOccurrenceStart) | |
XCTAssertEqual(lastOccurrenceWeekday.weekday!, firstStartComps.weekday!, testCase) | |
} | |
} | |
/** | |
This should skip any months without a 5th Thursday. It's common for calendar apps to not offer a "fifth" option, | |
but the Calendar app on macOS does. | |
*/ | |
func test5thThursdayOfMonth() { | |
let now = defaultStart() | |
let year = 2018 | |
let weekdayOrdinal = 5 | |
let weekdayValue = EKWeekday.thursday.rawValue | |
var firstStartComps = cal.dateComponents([.hour, .minute, .second], from: now) | |
firstStartComps.setValue(year, for: .year) | |
firstStartComps.setValue(weekdayValue, for: .weekday) | |
let months = Array(1...12) | |
let expectedStartDates: [Date] = months.flatMap({ month in | |
firstStartComps.setValue(month, for: .month) | |
firstStartComps.setValue(weekdayOrdinal, for: .weekdayOrdinal) | |
return firstStartComps.isValidDate(in: cal) ? cal.date(from: firstStartComps) : nil | |
}) | |
print("Expected start dates: \(expectedStartDates)") | |
let firstStart = expectedStartDates.first! | |
let firstEnd = defaultEnd(for: firstStart) | |
let rangeStart = firstStart.yearStart | |
let rangeEnd = rangeStart.yearEnd | |
//Create rule | |
let smWeekday = SMWeekday(rawValue: weekdayValue)! | |
let dayOfWeek = SMRecurrenceDayOfWeek(smWeekday, weekNumber: weekdayOrdinal) //Calendar.app creates this rule using setPositions instead of weekNumber | |
let unitArrays = SMRecurrenceRuleUnitArrays(daysOfTheWeek: [dayOfWeek], daysOfTheMonth: nil, monthsOfTheYear: nil, weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: nil) | |
let rule = SMRecurrenceRule(frequency: .monthly, interval: 1, end: nil, unitArrays: unitArrays) | |
let occurrences = rule.generateOccurrenceDates(firstStart: firstStart, firstEnd: firstEnd, exceptionDates: nil, from: rangeStart, upTo: rangeEnd, strictRange: true) | |
print("End of 5th Thursday occurrences starting \(rangeStart)") | |
//Tests | |
guard let firstOccurrenceStart = occurrences.first?.start, let lastOccurrenceStart = occurrences.last?.start else { | |
XCTFail("Occurrence was nil") | |
return | |
} | |
XCTAssertEqual(firstStart, firstOccurrenceStart) | |
XCTAssertEqual(expectedStartDates.count, occurrences.count) | |
let testPairs = zip(occurrences, expectedStartDates) | |
for (i, (occurrence, expectedStart)) in testPairs.enumerated() { | |
XCTAssertEqual(occurrence.start, expectedStart, "Pair \(i)") | |
} | |
let lastOccurrenceWeekday = cal.dateComponents([.weekday], from: lastOccurrenceStart) | |
XCTAssertEqual(lastOccurrenceWeekday.weekday!, firstStartComps.weekday!) | |
} | |
func testAllOrdinalWeekdaysAsSetPositions() { | |
//use this only for its time components in this test | |
let now = defaultStart() | |
let year = 2018 | |
//If users selects 5th week it should be stored as last, i.e. setPostions: [-1], but both are supported for synced rules | |
for weekdayOrdinal in [1, 2, 3, 4, 5, -1] { | |
for weekdayValue in 1...7 { | |
var firstStartComps = cal.dateComponents([.hour, .minute, .second], from: now) | |
firstStartComps.setValue(year, for: .year) | |
firstStartComps.setValue(weekdayValue, for: .weekday) | |
let months = Array(1...12) | |
let expectedStartDates: [Date] = months.flatMap({ month in | |
firstStartComps.setValue(month, for: .month) | |
firstStartComps.setValue(weekdayOrdinal, for: .weekdayOrdinal) | |
if weekdayOrdinal < 0 { | |
return cal.date(from: firstStartComps) //isValidDate() returns false for -1 | |
} else { | |
return firstStartComps.isValidDate(in: cal) ? cal.date(from: firstStartComps) : nil | |
} | |
}) | |
let firstStart = expectedStartDates.first! | |
let firstEnd = defaultEnd(for: firstStart) | |
let rangeStart = firstStart.yearStart | |
let rangeEnd = rangeStart.yearEnd | |
//Create rule | |
let smWeekday = SMWeekday(rawValue: weekdayValue)! | |
let dayOfWeek = SMRecurrenceDayOfWeek(smWeekday) //Calendar.app creates this rule using setPositions instead of weekNumber | |
let unitArrays = SMRecurrenceRuleUnitArrays(daysOfTheWeek: [dayOfWeek], daysOfTheMonth: nil, monthsOfTheYear: nil, weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: [weekdayOrdinal]) | |
let rule = SMRecurrenceRule(frequency: .monthly, interval: 1, end: nil, unitArrays: unitArrays) | |
let occurrences = rule.generateOccurrenceDates(firstStart: firstStart, firstEnd: firstEnd, exceptionDates: nil, from: rangeStart, upTo: rangeEnd, strictRange: true) | |
let testCase = "Week: \(weekdayOrdinal), \(smWeekday.description)" | |
print("End of occurrences starting \(rangeStart), \(testCase)") | |
//Tests | |
guard let firstOccurrenceStart = occurrences.first?.start, let lastOccurrenceStart = occurrences.last?.start else { | |
XCTFail("Occurrence was nil, \(testCase)") | |
return | |
} | |
XCTAssertEqual(firstStart, firstOccurrenceStart, testCase) | |
XCTAssertEqual(occurrences.count, expectedStartDates.count, testCase) | |
let testPairs = zip(occurrences, expectedStartDates) | |
for (i, (occurrence, expectedStart)) in testPairs.enumerated() { | |
XCTAssertEqual(occurrence.start, expectedStart, "\(testCase), Pair \(i)") | |
} | |
let lastOccurrenceWeekday = cal.dateComponents([.weekday], from: lastOccurrenceStart) | |
XCTAssertEqual(lastOccurrenceWeekday.weekday!, firstStartComps.weekday!, testCase) | |
} | |
} | |
} | |
func testAllOrdinalWeekdaysAsWeekNumbers() { | |
//use this only for its time components in this test | |
let now = defaultStart() | |
let year = 2018 | |
//If users selects 5th week it should be stored as last, i.e. setPostions: [-1] | |
for weekdayOrdinal in [1, 2, 3, 4, 5, -1] { | |
for weekdayValue in 1...7 { | |
var firstStartComps = cal.dateComponents([.hour, .minute, .second], from: now) | |
firstStartComps.setValue(year, for: .year) | |
firstStartComps.setValue(weekdayValue, for: .weekday) | |
let months = Array(1...12) | |
let expectedStartDates: [Date] = months.flatMap({ month in | |
firstStartComps.setValue(month, for: .month) | |
firstStartComps.setValue(weekdayOrdinal, for: .weekdayOrdinal) | |
if weekdayOrdinal < 0 { | |
return cal.date(from: firstStartComps) //isValidDate() returns false for -1 | |
} else { | |
return firstStartComps.isValidDate(in: cal) ? cal.date(from: firstStartComps) : nil | |
} | |
}) | |
let firstStart = expectedStartDates.first! | |
let firstEnd = defaultEnd(for: firstStart) | |
let rangeStart = firstStart.yearStart | |
let rangeEnd = rangeStart.yearEnd | |
//Create rule | |
let smWeekday = SMWeekday(rawValue: weekdayValue)! | |
let dayOfWeek = SMRecurrenceDayOfWeek(smWeekday, weekNumber: weekdayOrdinal) //Calendar.app creates this rule using setPositions instead of weekNumber | |
let unitArrays = SMRecurrenceRuleUnitArrays(daysOfTheWeek: [dayOfWeek], daysOfTheMonth: nil, monthsOfTheYear: nil, weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: nil) | |
let rule = SMRecurrenceRule(frequency: .monthly, interval: 1, end: nil, unitArrays: unitArrays) | |
let occurrences = rule.generateOccurrenceDates(firstStart: firstStart, firstEnd: firstEnd, exceptionDates: nil, from: rangeStart, upTo: rangeEnd, strictRange: true) | |
let testCase = "Week: \(weekdayOrdinal), \(smWeekday.description)" | |
print("End of occurrences starting \(rangeStart), \(testCase)") | |
//Tests | |
guard let firstOccurrenceStart = occurrences.first?.start, let lastOccurrenceStart = occurrences.last?.start else { | |
XCTFail("Occurrence was nil, \(testCase)") | |
return | |
} | |
XCTAssertEqual(firstStart, firstOccurrenceStart, testCase) | |
XCTAssertEqual(expectedStartDates.count, occurrences.count, testCase) | |
let testPairs = zip(occurrences, expectedStartDates) | |
for (i, (occurrence, expectedStart)) in testPairs.enumerated() { | |
XCTAssertEqual(occurrence.start, expectedStart, "\(testCase), Pair \(i)") | |
} | |
let lastOccurrenceWeekday = cal.dateComponents([.weekday], from: lastOccurrenceStart) | |
XCTAssertEqual(lastOccurrenceWeekday.weekday!, firstStartComps.weekday!, testCase) | |
} | |
} | |
} | |
/** | |
Tests each of the 7 days of the week independently. | |
*/ | |
func testLastSingleWeekday() { | |
//use this only for its time components in this test | |
let now = defaultStart() | |
let year = 2018 | |
let yearStartComps = DateComponents(year: year, month: 1, day: 1, hour: 0, minute: 0, second: 0) | |
let yearStart = cal.date(from: yearStartComps)! | |
let months = Array(1...12) | |
let monthStartDates: [Date] = months.map({ month in | |
var monthStartComps = yearStartComps | |
monthStartComps.setValue(month, for: .month) | |
return cal.date(from: monthStartComps)! | |
}) | |
for weekdayValue in 1...7 { | |
//Use .nextMonthStart instead of .monthEnd so that enumerateDates() returns the correct date when match is on last day of month | |
let nextMonthDates = monthStartDates.map({$0.nextMonthStart}) | |
print("Next month dates: \(nextMonthDates)") | |
let monthDateRanges = zip(monthStartDates, nextMonthDates) | |
var compsToMatch = cal.dateComponents([.hour, .minute, .second], from: now) | |
compsToMatch.setValue(weekdayValue, for: .weekday) | |
let expectedStartDates: [Date] = monthDateRanges.flatMap({ monthStart, nextMonthStart in | |
var matchingDate: Date? | |
cal.enumerateDates(startingAfter: nextMonthStart, matching: compsToMatch, matchingPolicy: .nextTimePreservingSmallerComponents, direction: .backward, using: { (date, isExact, stop) in | |
if let foundDate = date { | |
stop = true | |
matchingDate = foundDate | |
print("Found date: \(foundDate)") | |
} | |
}) | |
return matchingDate | |
}) | |
let firstStart = expectedStartDates.first! | |
let firstEnd = defaultEnd(for: firstStart) | |
let rangeStart = yearStart | |
let rangeEnd = rangeStart.yearEnd | |
//Create rule | |
let smWeekday = SMWeekday(rawValue: weekdayValue)! | |
let dayOfWeek = SMRecurrenceDayOfWeek(smWeekday) //Calendar.app creates this rule using setPositions instead of weekNumber | |
let unitArrays = SMRecurrenceRuleUnitArrays(daysOfTheWeek: [dayOfWeek], daysOfTheMonth: nil, monthsOfTheYear: nil, weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: [-1]) | |
let rule = SMRecurrenceRule(frequency: .monthly, interval: 1, end: nil, unitArrays: unitArrays) | |
let occurrences = rule.generateOccurrenceDates(firstStart: firstStart, firstEnd: firstEnd, exceptionDates: nil, from: rangeStart, upTo: rangeEnd, strictRange: true) | |
print("End of \(smWeekday.description) occurrences starting \(rangeStart)") | |
//Tests | |
guard let firstOccurrenceStart = occurrences.first?.start, let lastOccurrenceStart = occurrences.last?.start else { | |
XCTFail("Occurrence was nil") | |
return | |
} | |
XCTAssertEqual(firstStart, firstOccurrenceStart) | |
XCTAssertEqual(expectedStartDates.count, months.count) | |
XCTAssertEqual(occurrences.count, months.count) | |
let testPairs = zip(occurrences, expectedStartDates) | |
for (i, (occurrence, expectedStart)) in testPairs.enumerated() { | |
XCTAssertEqual(occurrence.start, expectedStart, "Pair \(i)") | |
} | |
let lastOccurrenceWeekday = cal.dateComponents([.weekday], from: lastOccurrenceStart) | |
XCTAssertEqual(lastOccurrenceWeekday.weekday!, weekdayValue) | |
} | |
} | |
func testLastMondaySingleDayRange() { | |
//use this only for its time components in this test | |
let now = defaultStart() | |
let year = 2017 | |
let weekdayValue = EKWeekday.monday.rawValue | |
let setPositions = [-1] | |
var timeComps = cal.dateComponents([.hour, .minute, .second], from: now) | |
let expectedStartDates: [Date] = Array(1...12).flatMap({ month in | |
let dateComps = DateComponents(year: year, month: month, hour: timeComps.hour, minute: timeComps.minute, second: timeComps.second, weekday: weekdayValue, weekdayOrdinal: setPositions.first!) | |
return cal.date(from: dateComps) | |
}) | |
let firstStart = expectedStartDates.first! | |
let firstEnd = defaultEnd(for: firstStart) | |
//The test results depend on using a single day period | |
let rangeStart = Date.date(year, 11, 27) | |
let rangeEnd = rangeStart.dayEnd | |
//Create rule | |
let smWeekday = SMWeekday(rawValue: weekdayValue)! | |
let dayOfWeek = SMRecurrenceDayOfWeek(smWeekday) //Calendar.app creates this rule using setPositions instead of weekNumber | |
let unitArrays = SMRecurrenceRuleUnitArrays(daysOfTheWeek: [dayOfWeek], daysOfTheMonth: nil, monthsOfTheYear: nil, weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: setPositions) | |
let rule = SMRecurrenceRule(frequency: .monthly, interval: 1, end: nil, unitArrays: unitArrays) | |
let occurrences = rule.generateOccurrenceDates(firstStart: firstStart, firstEnd: firstEnd, exceptionDates: nil, from: rangeStart, upTo: rangeEnd, strictRange: false) | |
print("End of \(smWeekday.description) occurrences starting \(rangeStart)") | |
//Tests | |
guard let firstOccurrenceStart = occurrences.first?.start else { | |
XCTFail("Occurrence was nil") | |
return | |
} | |
XCTAssertEqual(occurrences.count, 1) | |
XCTAssertTrue(expectedStartDates.contains(firstOccurrenceStart)) | |
} | |
func test1st27thLastMondaysOfYear() { | |
let now = defaultStart() | |
let year = 2018 | |
let weekdayValue = EKWeekday.monday.rawValue | |
let setPositions = [1, 27, -1] | |
let yearStartComps = DateComponents(year: year, month: 1, day: 1, hour: 0, minute: 0, second: 0) | |
let yearStart = cal.date(from: yearStartComps)! | |
var sharedComps = cal.dateComponents([.hour, .minute, .second], from: now) | |
sharedComps.setValue(weekdayValue, for: .weekday) | |
var firstStartComps = sharedComps | |
firstStartComps.setValue(year, for: .yearForWeekOfYear) | |
firstStartComps.setValue(1, for: .weekOfYear) | |
let firstStart = cal.date(from: firstStartComps)! | |
print("First start: \(firstStart)") | |
let firstEnd = defaultEnd(for: firstStart) | |
let rangeStart = yearStart | |
let rangeEnd = rangeStart.yearEnd | |
//Manually checked values for 2018 | |
let expectedDateComps = [DateComponents(month: 1, day: 1), | |
DateComponents(month: 7, day: 2), | |
DateComponents(month: 12, day: 31), | |
] | |
let expectedStartDates: [Date] = expectedDateComps.flatMap({ expectedComps in | |
let combinedComps = DateComponents(year: year, month: expectedComps.month, day: expectedComps.day, hour: sharedComps.hour, minute: sharedComps.minute, second: sharedComps.second) | |
return cal.date(from: combinedComps) | |
}) | |
//Create rule | |
let smWeekday = SMWeekday(rawValue: weekdayValue)! | |
let dayOfWeek = SMRecurrenceDayOfWeek(smWeekday) //Calendar.app creates this rule using setPositions instead of weekNumber | |
let unitArrays = SMRecurrenceRuleUnitArrays(daysOfTheWeek: [dayOfWeek], daysOfTheMonth: nil, monthsOfTheYear: nil, weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: setPositions) | |
let rule = SMRecurrenceRule(frequency: .yearly, interval: 1, end: nil, unitArrays: unitArrays) | |
let occurrences = rule.generateOccurrenceDates(firstStart: firstStart, firstEnd: firstEnd, exceptionDates: nil, from: rangeStart, upTo: rangeEnd, strictRange: true) | |
print("End of \(smWeekday.description) occurrences starting \(rangeStart)") | |
//Tests | |
guard let firstOccurrenceStart = occurrences.first?.start, let lastOccurrenceStart = occurrences.last?.start else { | |
XCTFail("Occurrence was nil") | |
return | |
} | |
XCTAssertEqual(firstStart, firstOccurrenceStart) | |
XCTAssertEqual(expectedDateComps.count, occurrences.count) | |
XCTAssertEqual(occurrences.count, setPositions.count) | |
let testPairs = zip(occurrences, expectedStartDates) | |
for (i, (occurrence, expectedStart)) in testPairs.enumerated() { | |
XCTAssertEqual(occurrence.start, expectedStart, "Pair \(i)") | |
} | |
let lastOccurrenceWeekday = cal.dateComponents([.weekday], from: lastOccurrenceStart) | |
XCTAssertEqual(lastOccurrenceWeekday.weekday!, weekdayValue) | |
} | |
func testLastWeekdayOfYear() { | |
let now = defaultStart() //to get random time of day | |
let firstYear = 2016 | |
let weekdays: [EKWeekday] = [.monday, .tuesday, .wednesday, .thursday, .friday] | |
let setPositions = [-1] | |
var timeComps = cal.dateComponents([.hour, .minute, .second], from: now) | |
let yearStartComps = DateComponents(year: firstYear, month: 1, day: 1, hour: 0, minute: 0, second: 0) | |
let yearStart = cal.date(from: yearStartComps)! | |
//Manually checked values | |
let expectedDateComps = [DateComponents(year: 2016, month: 12, day: 30), | |
DateComponents(year: 2017, month: 12, day: 29), | |
DateComponents(year: 2018, month: 12, day: 31), | |
DateComponents(year: 2019, month: 12, day: 31), | |
] | |
let expectedStartDates: [Date] = expectedDateComps.flatMap({ expectedComps in | |
let combinedComps = DateComponents(year: expectedComps.year, month: expectedComps.month, day: expectedComps.day, hour: timeComps.hour, minute: timeComps.minute, second: timeComps.second) | |
return cal.date(from: combinedComps) | |
}) | |
let firstStart = expectedStartDates.first! | |
print("First start: \(firstStart)") | |
let firstEnd = defaultEnd(for: firstStart) | |
let rangeStart = yearStart | |
let rangeEnd = yearStart.addYears(3).yearEnd //4-year range | |
//Create rule | |
let daysOfWeek = SMRecurrenceDayOfWeek.createDays(for: weekdays) | |
let unitArrays = SMRecurrenceRuleUnitArrays(daysOfTheWeek: daysOfWeek, daysOfTheMonth: nil, monthsOfTheYear: nil, weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: setPositions) | |
let rule = SMRecurrenceRule(frequency: .yearly, interval: 1, end: nil, unitArrays: unitArrays) | |
let occurrences = rule.generateOccurrenceDates(firstStart: firstStart, firstEnd: firstEnd, exceptionDates: nil, from: rangeStart, upTo: rangeEnd, strictRange: true) | |
print("End of \(weekdays.map({$0.description})) occurrences starting \(rangeStart)") | |
//Tests | |
guard let firstOccurrenceStart = occurrences.first?.start, let lastOccurrenceStart = occurrences.last?.start else { | |
XCTFail("Occurrence was nil") | |
return | |
} | |
XCTAssertEqual(firstStart, firstOccurrenceStart) | |
XCTAssertEqual(expectedDateComps.count, occurrences.count) | |
let testPairs = zip(occurrences, expectedStartDates) | |
for (i, (occurrence, expectedStart)) in testPairs.enumerated() { | |
XCTAssertEqual(occurrence.start, expectedStart, "Pair \(i)") | |
} | |
let lastOccurrenceWeekday = cal.dateComponents([.weekday], from: lastOccurrenceStart) | |
XCTAssertTrue(weekdays.map({$0.rawValue}).contains(lastOccurrenceWeekday.weekday!)) | |
} | |
func testLastWednesdayOfDecember() { | |
let now = defaultStart() //to get random time of day | |
let firstYear = 2017 | |
let weekdays: [EKWeekday] = [.wednesday] | |
let monthsOfTheYear = [12] | |
var timeComps = cal.dateComponents([.hour, .minute, .second], from: now) | |
let yearStartComps = DateComponents(year: firstYear, month: 1, day: 1, hour: 0, minute: 0, second: 0) | |
let yearStart = cal.date(from: yearStartComps)! | |
//Manually checked values | |
let expectedDateComps = [DateComponents(year: 2017, month: 12, day: 27), | |
DateComponents(year: 2018, month: 12, day: 26), | |
DateComponents(year: 2019, month: 12, day: 25), | |
] | |
let expectedStartDates: [Date] = expectedDateComps.flatMap({ expectedComps in | |
let combinedComps = DateComponents(year: expectedComps.year, month: expectedComps.month, day: expectedComps.day, hour: timeComps.hour, minute: timeComps.minute, second: timeComps.second) | |
return cal.date(from: combinedComps) | |
}) | |
let firstStart = expectedStartDates.first! | |
print("First start: \(firstStart)") | |
let firstEnd = defaultEnd(for: firstStart) | |
let rangeStart = yearStart | |
let rangeEnd = yearStart.addYears(2).yearEnd //3-year range | |
//Create rule | |
let daysOfWeek = SMRecurrenceDayOfWeek(.wednesday, weekNumber: -1) | |
let unitArrays = SMRecurrenceRuleUnitArrays(daysOfTheWeek: [daysOfWeek], daysOfTheMonth: nil, monthsOfTheYear: monthsOfTheYear, weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: nil) | |
let rule = SMRecurrenceRule(frequency: .yearly, interval: 1, end: nil, unitArrays: unitArrays) | |
let occurrences = rule.generateOccurrenceDates(firstStart: firstStart, firstEnd: firstEnd, exceptionDates: nil, from: rangeStart, upTo: rangeEnd, strictRange: true) | |
print("End of \(weekdays.map({$0.description})) occurrences starting \(rangeStart)") | |
//Tests | |
guard let firstOccurrenceStart = occurrences.first?.start, let lastOccurrenceStart = occurrences.last?.start else { | |
XCTFail("Occurrence was nil") | |
return | |
} | |
XCTAssertEqual(firstStart, firstOccurrenceStart) | |
XCTAssertEqual(expectedDateComps.count, occurrences.count) | |
let testPairs = zip(occurrences, expectedStartDates) | |
for (i, (occurrence, expectedStart)) in testPairs.enumerated() { | |
XCTAssertEqual(occurrence.start, expectedStart, "Pair \(i)") | |
} | |
let lastOccurrenceWeekday = cal.dateComponents([.weekday], from: lastOccurrenceStart) | |
XCTAssertTrue(weekdays.map({$0.rawValue}).contains(lastOccurrenceWeekday.weekday!)) | |
} | |
func test1st15thMonthly() { | |
let now = defaultStart() | |
let year = now.yearInt | |
let months = Array(1...12) | |
let daysOfMonth = [1, 15] | |
let timeComps = cal.dateComponents([.hour, .minute, .second], from: now) | |
let expectedDateComps: [DateComponents] = months.reduce( [], { results, month in | |
var dateComps = DateComponents(year: year, month: month, hour: timeComps.hour, minute: timeComps.minute, second: timeComps.second) | |
return results + daysOfMonth.map({ | |
dateComps.setValue($0, for: .day) | |
return dateComps | |
}) | |
}) | |
let expectedStartDates = expectedDateComps.flatMap({cal.date(from: $0)}) | |
let firstStart = expectedStartDates.first! | |
let firstEnd = defaultEnd(for: firstStart) | |
let rangeStart = firstStart.yearStart | |
let rangeEnd = rangeStart.yearEnd | |
//Create rule | |
let unitArrays = SMRecurrenceRuleUnitArrays(daysOfTheWeek: nil, daysOfTheMonth: daysOfMonth, monthsOfTheYear: nil, weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: nil) | |
let rule = SMRecurrenceRule(frequency: .monthly, interval: 1, end: nil, unitArrays: unitArrays) | |
let occurrences = rule.generateOccurrenceDates(firstStart: firstStart, firstEnd: firstEnd, exceptionDates: nil, from: rangeStart, upTo: rangeEnd, strictRange: true) | |
print("End of days of month \(daysOfMonth) occurrences starting \(rangeStart)") | |
//Tests | |
guard let firstOccurrenceStart = occurrences.first?.start, let lastOccurrenceStart = occurrences.last?.start else { | |
XCTFail("Occurrence was nil") | |
return | |
} | |
XCTAssertEqual(firstStart, firstOccurrenceStart) | |
XCTAssertEqual(expectedStartDates.count, occurrences.count) | |
XCTAssertEqual(occurrences.count, (months.count * daysOfMonth.count)) | |
let testPairs = zip(occurrences, expectedStartDates) | |
for (i, (occurrence, expectedStart)) in testPairs.enumerated() { | |
XCTAssertEqual(occurrence.start, expectedStart, "Pair \(i)") | |
} | |
let lastOccurrenceDay = cal.dateComponents([.day], from: lastOccurrenceStart) | |
XCTAssertEqual(lastOccurrenceDay.day!, daysOfMonth.last!) | |
} | |
func test1stOfJanuaryJuly() { | |
let now = defaultStart() | |
let year = now.yearInt | |
let months = [1, 7] | |
let daysOfMonth = [1] | |
let frequencies: [SMRecurrenceFrequency] = [.monthly, .yearly] | |
let timeComps = cal.dateComponents([.hour, .minute, .second], from: now) | |
let expectedDateComps: [DateComponents] = months.reduce( [], { results, month in | |
var dateComps = DateComponents(year: year, month: month, hour: timeComps.hour, minute: timeComps.minute, second: timeComps.second) | |
return results + daysOfMonth.map({ | |
dateComps.setValue($0, for: .day) | |
return dateComps | |
}) | |
}) | |
let expectedStartDates = expectedDateComps.flatMap({cal.date(from: $0)}) | |
let firstStart = expectedStartDates.first! | |
let firstEnd = defaultEnd(for: firstStart) | |
let rangeStart = firstStart.yearStart | |
let rangeEnd = rangeStart.yearEnd | |
for frequency in frequencies { | |
//Create rule | |
let unitArrays = SMRecurrenceRuleUnitArrays(daysOfTheWeek: nil, daysOfTheMonth: daysOfMonth, monthsOfTheYear: months, weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: nil) | |
let rule = SMRecurrenceRule(frequency: frequency, interval: 1, end: nil, unitArrays: unitArrays) | |
let occurrences = rule.generateOccurrenceDates(firstStart: firstStart, firstEnd: firstEnd, exceptionDates: nil, from: rangeStart, upTo: rangeEnd, strictRange: true) | |
print("End of days of month \(daysOfMonth) occurrences starting \(rangeStart)") | |
//Tests | |
guard let firstOccurrenceStart = occurrences.first?.start, let lastOccurrenceStart = occurrences.last?.start else { | |
XCTFail("Occurrence was nil") | |
return | |
} | |
XCTAssertEqual(firstStart, firstOccurrenceStart) | |
XCTAssertEqual(expectedStartDates.count, occurrences.count) | |
XCTAssertEqual(occurrences.count, (months.count * daysOfMonth.count)) | |
let testPairs = zip(occurrences, expectedStartDates) | |
for (i, (occurrence, expectedStart)) in testPairs.enumerated() { | |
XCTAssertEqual(occurrence.start, expectedStart, "Pair \(i)") | |
} | |
let lastOccurrenceDay = cal.dateComponents([.day], from: lastOccurrenceStart) | |
XCTAssertEqual(lastOccurrenceDay.day!, daysOfMonth.last!) | |
} | |
} | |
func testLastDayOfTheMonth() { | |
let now = defaultStart() | |
let year = 2016 //to test leap day | |
let weekdays: [EKWeekday] = [.sunday, .monday, .tuesday, .wednesday, .thursday, .friday, .saturday] | |
let setPositions = [-1] | |
let timeComps = cal.dateComponents([.hour, .minute, .second], from: now) | |
let expectedDateComps = [DateComponents(year: year, month: 1, day: 31), | |
DateComponents(year: year, month: 2, day: 29), | |
DateComponents(year: year, month: 3, day: 31), | |
DateComponents(year: year, month: 4, day: 30), | |
DateComponents(year: year, month: 5, day: 31), | |
DateComponents(year: year, month: 6, day: 30), | |
DateComponents(year: year, month: 7, day: 31), | |
DateComponents(year: year, month: 8, day: 31), | |
DateComponents(year: year, month: 9, day: 30), | |
DateComponents(year: year, month: 10, day: 31), | |
DateComponents(year: year, month: 11, day: 30), | |
DateComponents(year: year, month: 12, day: 31), | |
] | |
let expectedStartDates: [Date] = expectedDateComps.flatMap({ expectedComps in | |
let combinedComps = DateComponents(year: expectedComps.year, month: expectedComps.month, day: expectedComps.day, hour: timeComps.hour, minute: timeComps.minute, second: timeComps.second) | |
return cal.date(from: combinedComps) | |
}) | |
let firstStart = expectedStartDates.first! | |
let firstEnd = defaultEnd(for: firstStart) | |
let rangeStart = firstStart.yearStart | |
let rangeEnd = rangeStart.yearEnd | |
//Create rule | |
let daysOfWeek = SMRecurrenceDayOfWeek.createDays(for: weekdays) | |
let unitArrays = SMRecurrenceRuleUnitArrays(daysOfTheWeek: daysOfWeek, daysOfTheMonth: nil, monthsOfTheYear: nil, weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: setPositions) | |
let rule = SMRecurrenceRule(frequency: .monthly, interval: 1, end: nil, unitArrays: unitArrays) | |
let occurrences = rule.generateOccurrenceDates(firstStart: firstStart, firstEnd: firstEnd, exceptionDates: nil, from: rangeStart, upTo: rangeEnd, strictRange: true) | |
print("End of last day of month occurrences starting \(rangeStart), count: \(occurrences.count)") | |
//Tests | |
guard let firstOccurrenceStart = occurrences.first?.start, let lastOccurrenceStart = occurrences.last?.start else { | |
XCTFail("Occurrence was nil") | |
return | |
} | |
XCTAssertEqual(firstStart, firstOccurrenceStart) | |
XCTAssertEqual(expectedDateComps.count, occurrences.count) | |
let testPairs = zip(occurrences, expectedStartDates) | |
for (i, (occurrence, expectedStart)) in testPairs.enumerated() { | |
XCTAssertEqual(occurrence.start, expectedStart, "Pair \(i)") | |
} | |
let lastOccurrenceDay = cal.dateComponents([.day], from: lastOccurrenceStart) | |
XCTAssertEqual(lastOccurrenceDay.day!, expectedDateComps.last!.day!) | |
} | |
func testLastDayOfEachQuarter() { | |
let now = defaultStart() | |
let year = 2018 | |
let weekdays: [EKWeekday] = [.sunday, .monday, .tuesday, .wednesday, .thursday, .friday, .saturday] | |
let monthValues = [3, 6, 9, 12] | |
let setPositions = [-1] | |
let timeComps = cal.dateComponents([.hour, .minute, .second], from: now) | |
let expectedDateComps = [DateComponents(year: year, month: 3, day: 31), | |
DateComponents(year: year, month: 6, day: 30), | |
DateComponents(year: year, month: 9, day: 30), | |
DateComponents(year: year, month: 12, day: 31), | |
] | |
let expectedStartDates: [Date] = expectedDateComps.flatMap({ expectedComps in | |
let combinedComps = DateComponents(year: expectedComps.year, month: expectedComps.month, day: expectedComps.day, hour: timeComps.hour, minute: timeComps.minute, second: timeComps.second) | |
return cal.date(from: combinedComps) | |
}) | |
let firstStart = expectedStartDates.first! | |
let firstEnd = defaultEnd(for: firstStart) | |
let rangeStart = firstStart.yearStart | |
let rangeEnd = rangeStart.yearEnd | |
//Create rule | |
let daysOfWeek = SMRecurrenceDayOfWeek.createDays(for: weekdays) | |
let unitArrays = SMRecurrenceRuleUnitArrays(daysOfTheWeek: daysOfWeek, daysOfTheMonth: nil, monthsOfTheYear: monthValues, weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: setPositions) | |
//Frequency needs to be monthly when a set position is applied to single or multiple days of the week. | |
//The initial interface shows yearly, but the underlying logic creates a rule with monthly frequency. | |
let rule = SMRecurrenceRule(frequency: .monthly, interval: 1, end: nil, unitArrays: unitArrays) | |
measure { | |
let occurrences = rule.generateOccurrenceDates(firstStart: firstStart, firstEnd: firstEnd, exceptionDates: nil, from: rangeStart, upTo: rangeEnd, strictRange: true) | |
print("End of last day of month occurrences starting \(rangeStart), count: \(occurrences.count)") | |
//Tests | |
guard let firstOccurrenceStart = occurrences.first?.start, let lastOccurrenceStart = occurrences.last?.start else { | |
XCTFail("Occurrence was nil") | |
return | |
} | |
XCTAssertEqual(firstStart, firstOccurrenceStart) | |
XCTAssertEqual(expectedDateComps.count, occurrences.count) | |
let testPairs = zip(occurrences, expectedStartDates) | |
for (i, (occurrence, expectedStart)) in testPairs.enumerated() { | |
XCTAssertEqual(occurrence.start, expectedStart, "Pair \(i)") | |
} | |
let lastOccurrenceDay = cal.dateComponents([.day], from: lastOccurrenceStart) | |
XCTAssertEqual(lastOccurrenceDay.day!, expectedDateComps.last!.day!) | |
} | |
} | |
func test1stWeekdayOfEachQuarter() { | |
let now = defaultStart() | |
let year = 2018 | |
let weekdays: [EKWeekday] = [.monday, .tuesday, .wednesday, .thursday, .friday] | |
let monthValues = [1, 4, 7, 10] | |
let setPositions = [1] | |
let timeComps = cal.dateComponents([.hour, .minute, .second], from: now) | |
let expectedDateComps = [DateComponents(year: year, month: 1, day: 1), | |
DateComponents(year: year, month: 4, day: 2), | |
DateComponents(year: year, month: 7, day: 2), | |
DateComponents(year: year, month: 10, day: 1), | |
] | |
let expectedStartDates: [Date] = expectedDateComps.flatMap({ expectedComps in | |
let combinedComps = DateComponents(year: expectedComps.year, month: expectedComps.month, day: expectedComps.day, hour: timeComps.hour, minute: timeComps.minute, second: timeComps.second) | |
return cal.date(from: combinedComps) | |
}) | |
let firstStart = expectedStartDates.first! | |
let firstEnd = defaultEnd(for: firstStart) | |
let rangeStart = firstStart.yearStart | |
let rangeEnd = rangeStart.yearEnd | |
//Create rule | |
let daysOfWeek = SMRecurrenceDayOfWeek.createDays(for: weekdays) | |
let unitArrays = SMRecurrenceRuleUnitArrays(daysOfTheWeek: daysOfWeek, daysOfTheMonth: nil, monthsOfTheYear: monthValues, weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: setPositions) | |
//Frequency needs to be monthly when a set position is applied to single or multiple days of the week. | |
//The initial interface shows yearly, but the underlying logic creates a rule with monthly frequency. | |
let rule = SMRecurrenceRule(frequency: .monthly, interval: 1, end: nil, unitArrays: unitArrays) | |
measure { | |
let occurrences = rule.generateOccurrenceDates(firstStart: firstStart, firstEnd: firstEnd, exceptionDates: nil, from: rangeStart, upTo: rangeEnd, strictRange: true) | |
print("End of last day of month occurrences starting \(rangeStart), count: \(occurrences.count)") | |
//Tests | |
guard let firstOccurrenceStart = occurrences.first?.start, let lastOccurrenceStart = occurrences.last?.start else { | |
XCTFail("Occurrence was nil") | |
return | |
} | |
XCTAssertEqual(firstStart, firstOccurrenceStart) | |
XCTAssertEqual(expectedDateComps.count, occurrences.count) | |
let testPairs = zip(occurrences, expectedStartDates) | |
for (i, (occurrence, expectedStart)) in testPairs.enumerated() { | |
XCTAssertEqual(occurrence.start, expectedStart, "Pair \(i)") | |
} | |
let lastOccurrenceDay = cal.dateComponents([.day], from: lastOccurrenceStart) | |
XCTAssertEqual(lastOccurrenceDay.day!, expectedDateComps.last!.day!) | |
} | |
} | |
func test2ndWeekdayOfJune() { | |
let now = defaultStart() | |
let weekdays: [EKWeekday] = [.monday, .tuesday, .wednesday, .thursday, .friday] | |
let monthValues = [6] | |
let setPositions = [2] | |
let timeComps = cal.dateComponents([.hour, .minute, .second], from: now) | |
let expectedDateComps = [DateComponents(year: 2018, month: 6, day: 4), //June 2018 starts on Friday, so 2nd weekday is Monday the 4th | |
DateComponents(year: 2019, month: 6, day: 4), | |
DateComponents(year: 2020, month: 6, day: 2), | |
] | |
let expectedStartDates: [Date] = expectedDateComps.flatMap({ expectedComps in | |
let combinedComps = DateComponents(year: expectedComps.year, month: expectedComps.month, day: expectedComps.day, hour: timeComps.hour, minute: timeComps.minute, second: timeComps.second) | |
return cal.date(from: combinedComps) | |
}) | |
let firstStart = expectedStartDates.first! | |
let firstEnd = defaultEnd(for: firstStart) | |
let rangeStart = firstStart.yearStart | |
let rangeEnd = rangeStart.addYears(2).yearEnd | |
//Create rule | |
let daysOfWeek = SMRecurrenceDayOfWeek.createDays(for: weekdays) | |
let unitArrays = SMRecurrenceRuleUnitArrays(daysOfTheWeek: daysOfWeek, daysOfTheMonth: nil, monthsOfTheYear: monthValues, weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: setPositions) | |
//Frequency needs to be monthly when a set position is applied to single or multiple days of the week. | |
//The initial interface shows yearly, but the underlying logic creates a rule with monthly frequency. | |
let rule = SMRecurrenceRule(frequency: .monthly, interval: 1, end: nil, unitArrays: unitArrays) | |
let occurrences = rule.generateOccurrenceDates(firstStart: firstStart, firstEnd: firstEnd, exceptionDates: nil, from: rangeStart, upTo: rangeEnd, strictRange: true) | |
print("End of last day of month occurrences starting \(rangeStart), count: \(occurrences.count)") | |
//Tests | |
guard let firstOccurrenceStart = occurrences.first?.start, let lastOccurrenceStart = occurrences.last?.start else { | |
XCTFail("Occurrence was nil") | |
return | |
} | |
XCTAssertEqual(firstStart, firstOccurrenceStart) | |
XCTAssertEqual(expectedDateComps.count, occurrences.count) | |
let testPairs = zip(occurrences, expectedStartDates) | |
for (i, (occurrence, expectedStart)) in testPairs.enumerated() { | |
XCTAssertEqual(occurrence.start, expectedStart, "Pair \(i)") | |
} | |
let lastOccurrenceDay = cal.dateComponents([.day], from: lastOccurrenceStart) | |
XCTAssertEqual(lastOccurrenceDay.day!, expectedDateComps.last!.day!) | |
} | |
/* | |
The results should skip February in accordance with the RFC specifications. | |
This is different from monthly repeating where the first instance is on the 30th. | |
int/freq: Every 1 month | |
daysOfTheMonth: [30] | |
*/ | |
func test30thOfEachMonth() { | |
let now = defaultStart() | |
let year = 2018 | |
let daysOfMonth = [30] | |
let timeComps = cal.dateComponents([.hour, .minute, .second], from: now) | |
let expectedDateComps: [DateComponents] = Array(1...12).flatMap({ month in | |
guard month != 2 else { return nil} | |
return DateComponents(year: year, month: month, day: daysOfMonth.first, hour: timeComps.hour, minute: timeComps.minute, second: timeComps.second) | |
}) | |
let expectedStartDates: [Date] = expectedDateComps.flatMap({ cal.date(from: $0) }) | |
let firstStart = expectedStartDates.first! | |
let firstEnd = defaultEnd(for: firstStart) | |
let rangeStart = firstStart.yearStart | |
let rangeEnd = rangeStart.yearEnd | |
//Create rule | |
let unitArrays = SMRecurrenceRuleUnitArrays(daysOfTheWeek: nil, daysOfTheMonth: daysOfMonth, monthsOfTheYear: nil, weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: nil) | |
let rule = SMRecurrenceRule(frequency: .monthly, interval: 1, end: nil, unitArrays: unitArrays) | |
let occurrences = rule.generateOccurrenceDates(firstStart: firstStart, firstEnd: firstEnd, exceptionDates: nil, from: rangeStart, upTo: rangeEnd, strictRange: true) | |
print("End of 30th of month occurrences starting \(rangeStart), count: \(occurrences.count)") | |
//Tests | |
guard let firstOccurrenceStart = occurrences.first?.start, let lastOccurrenceStart = occurrences.last?.start else { | |
XCTFail("Occurrence was nil") | |
return | |
} | |
XCTAssertEqual(firstStart, firstOccurrenceStart) | |
XCTAssertEqual(expectedDateComps.count, occurrences.count) | |
let testPairs = zip(occurrences, expectedStartDates) | |
for (i, (occurrence, expectedStart)) in testPairs.enumerated() { | |
XCTAssertEqual(occurrence.start, expectedStart, "Pair \(i)") | |
} | |
let lastOccurrenceDay = cal.dateComponents([.day], from: lastOccurrenceStart) | |
XCTAssertEqual(lastOccurrenceDay.day!, expectedDateComps.last!.day!) | |
} | |
/* | |
int/freq: Every 1 month | |
daysOfTheMonth: [28, 29, 30] | |
setPositions: [-1] | |
This represents monthly repeating where the first instance is on the 30th. | |
The results should include February 28/29 instead of skipping it, as would be the case if daysOfTheMonth = [30] | |
This function tests with intervals of 1, 2, and 3. | |
*/ | |
func testMonthlyRepeatingStartingOn30th() { | |
let now = defaultStart() | |
let year = 2018 | |
let daysOfMonth = [28, 29, 30] | |
let setPositions = [-1] | |
let intervals = [1, 2, 3] | |
let timeComps = cal.dateComponents([.hour, .minute, .second], from: now) | |
let expectedDateComps: [DateComponents] = Array(1...12).map({ month in | |
let expectedDay = (month == 2) ? 28 : 30 | |
return DateComponents(year: year, month: month, day: expectedDay, hour: timeComps.hour, minute: timeComps.minute, second: timeComps.second) | |
}) | |
let expectedStartDates: [Date] = expectedDateComps.flatMap({ cal.date(from: $0) }) | |
let firstStart = expectedStartDates.first! | |
let firstEnd = defaultEnd(for: firstStart) | |
let rangeStart = firstStart.yearStart | |
let rangeEnd = rangeStart.yearEnd | |
//Create rule | |
let unitArrays = SMRecurrenceRuleUnitArrays(daysOfTheWeek: nil, daysOfTheMonth: daysOfMonth, monthsOfTheYear: nil, weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: setPositions) | |
for interval in intervals { | |
let expectedDatesForInterval = expectedStartDates.filter({ date in | |
return ((date.monthInt - 1 + interval) % interval) == 0 | |
}) | |
let rule = SMRecurrenceRule(frequency: .monthly, interval: interval, end: nil, unitArrays: unitArrays) | |
let occurrences = rule.generateOccurrenceDates(firstStart: firstStart, firstEnd: firstEnd, exceptionDates: nil, from: rangeStart, upTo: rangeEnd, strictRange: true) | |
print("End of 30th of month occurrences starting \(rangeStart), count: \(occurrences.count)") | |
//Tests | |
guard let firstOccurrenceStart = occurrences.first?.start, let lastOccurrenceStart = occurrences.last?.start else { | |
XCTFail("Occurrence was nil") | |
return | |
} | |
XCTAssertEqual(firstStart, firstOccurrenceStart) | |
XCTAssertEqual(expectedDatesForInterval.count, occurrences.count) | |
let testPairs = zip(occurrences, expectedDatesForInterval) | |
for (i, (occurrence, expectedStart)) in testPairs.enumerated() { | |
XCTAssertEqual(occurrence.start, expectedStart, "Pair \(i)") | |
} | |
let lastOccurrenceDay = cal.dateComponents([.day], from: lastOccurrenceStart) | |
XCTAssertEqual(lastOccurrenceDay.day!, expectedDatesForInterval.last!.dayOfMonthInt) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment