Skip to content

Commit

Permalink
support exdate
Browse files Browse the repository at this point in the history
  • Loading branch information
hongxinhope committed Apr 14, 2016
1 parent dfc0675 commit aceea6d
Show file tree
Hide file tree
Showing 9 changed files with 168 additions and 20 deletions.
41 changes: 32 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ recurrenceRule.byweekday = ...
recurrenceRule.byhour = ...
recurrenceRule.byminute = ...
recurrenceRule.bysecond = ...
recurrenceRule.exdate = ...
```

##### Rule form string
Expand All @@ -46,28 +47,50 @@ print(ruleString)
// RRULE:FREQ=MONTHLY;DTSTART=20160404T021000Z;COUNT=5;INTERVAL=2;WKST=MO;BYDAY=MO,TU
```

##### Exclusion date
```swift
let exdateString = "EXDATE:20181231T160000Z,20201231T160000Z"
if let exclusionDate = ExclusionDate(exdateString: exdateString, unitGranularity: .Year) {
print(exclusionDate.toExDateString())
// EXDATE:20181231T160000Z,20201231T160000Z

print(exclusionDate.dates)
/*
2019-01-01 00:00:00 Tue
2021-01-01 00:00:00 Fri
*/
}
```

##### Occurrence generator
```swift
let ruleString = "RRULE:FREQ=YEARLY;COUNT=5;WKST=MO"
let ruleString = "RRULE:FREQ=YEARLY;COUNT=11;WKST=MO"
if let rule = RecurrenceRule(recurrenceWithRRuleString: ruleString) {
var rule = rule
rule.exdate = exclusionDate // EXDATE:20181231T160000Z,20201231T160000Z
let allDates = rule.allOccurrences()
print(allDates)
/*
2016-04-06 14:26:17 Wed,
2017-04-06 14:26:17 Thu,
2018-04-06 14:26:17 Fri,
2019-04-06 14:26:17 Sat,
2020-04-06 14:26:17 Mon
2016-04-14 14:22:30 Thu
2017-04-14 14:22:30 Fri
2018-04-14 14:22:30 Sat
2020-04-14 14:22:30 Tue
2022-04-14 14:22:30 Thu
2023-04-14 14:22:30 Fri
2024-04-14 14:22:30 Sun
2025-04-14 14:22:30 Mon
2026-04-14 14:22:30 Tue
*/

let date = dateFormatter.dateFromString("2017-01-01 00:00:00 Sun")
let otherDate = dateFormatter.dateFromString("2020-01-01 00:00:00 Wed")
let betweenDates = rule.occurrencesBetween(date: date!, andDate: otherDate!)
print(betweenDates)
/*
2017-04-06 14:26:17 Thu,
2018-04-06 14:26:17 Fri,
2019-04-06 14:26:17 Sat
2018-04-14 14:22:30 Sat
2020-04-14 14:22:30 Tue
2022-04-14 14:22:30 Thu
2023-04-14 14:22:30 Fri
*/
}
```
Expand Down
4 changes: 4 additions & 0 deletions RRuleSwift.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
D31B13B21CA91F9000D0B863 /* EKWeekday+RRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D31B13B11CA91F9000D0B863 /* EKWeekday+RRule.swift */; };
D31B13BF1CAA7F1800D0B863 /* Generators.swift in Sources */ = {isa = PBXBuildFile; fileRef = D31B13BE1CAA7F1700D0B863 /* Generators.swift */; };
D382675C1CB4BCFB0080C91A /* NSDate+Comparison.swift in Sources */ = {isa = PBXBuildFile; fileRef = D382675B1CB4BCFB0080C91A /* NSDate+Comparison.swift */; };
D393D72F1CBF507700B89FB8 /* ExclusionDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D393D72E1CBF507700B89FB8 /* ExclusionDate.swift */; };
D3D7E3E51CB3E03F00BF052F /* nlp.js in Resources */ = {isa = PBXBuildFile; fileRef = D3D7E3E31CB3E03F00BF052F /* nlp.js */; };
D3D7E3E61CB3E03F00BF052F /* rrule.js in Resources */ = {isa = PBXBuildFile; fileRef = D3D7E3E41CB3E03F00BF052F /* rrule.js */; };
D3D7E3E81CB3EBC800BF052F /* JavaScriptBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3D7E3E71CB3EBC800BF052F /* JavaScriptBridge.swift */; };
Expand All @@ -27,6 +28,7 @@
D31B13B11CA91F9000D0B863 /* EKWeekday+RRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "EKWeekday+RRule.swift"; sourceTree = "<group>"; };
D31B13BE1CAA7F1700D0B863 /* Generators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Generators.swift; sourceTree = "<group>"; };
D382675B1CB4BCFB0080C91A /* NSDate+Comparison.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSDate+Comparison.swift"; sourceTree = "<group>"; };
D393D72E1CBF507700B89FB8 /* ExclusionDate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExclusionDate.swift; sourceTree = "<group>"; };
D3D7E3E31CB3E03F00BF052F /* nlp.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = nlp.js; sourceTree = "<group>"; };
D3D7E3E41CB3E03F00BF052F /* rrule.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = rrule.js; sourceTree = "<group>"; };
D3D7E3E71CB3EBC800BF052F /* JavaScriptBridge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JavaScriptBridge.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -65,6 +67,7 @@
children = (
D3D7E3E21CB3E03F00BF052F /* lib */,
D31B13AD1CA90A9A00D0B863 /* RecurrenceRule.swift */,
D393D72E1CBF507700B89FB8 /* ExclusionDate.swift */,
D31B13A91CA8F37F00D0B863 /* RRule.swift */,
D31B13BE1CAA7F1700D0B863 /* Generators.swift */,
D31B13AF1CA90ED300D0B863 /* RecurrenceFrequency.swift */,
Expand Down Expand Up @@ -178,6 +181,7 @@
D31B13AE1CA90A9A00D0B863 /* RecurrenceRule.swift in Sources */,
D3D7E3E81CB3EBC800BF052F /* JavaScriptBridge.swift in Sources */,
D31B13B01CA90ED300D0B863 /* RecurrenceFrequency.swift in Sources */,
D393D72F1CBF507700B89FB8 /* ExclusionDate.swift in Sources */,
D31B13BF1CAA7F1800D0B863 /* Generators.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
57 changes: 57 additions & 0 deletions RRuleSwift/ExclusionDate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// ExclusionDate.swift
// RRuleSwift
//
// Created by Xin Hong on 16/3/28.
// Copyright © 2016年 Teambition. All rights reserved.
//

import Foundation

public struct ExclusionDate {
/// All exclusion dates.
public private(set) var dates = [NSDate]()
/// The unit of ExclusionDate, used to decide which exdate will be excluded.
public private(set) var unit: NSCalendarUnit!

public init(dates: [NSDate], unitGranularity unit: NSCalendarUnit) {
self.dates = dates
self.unit = unit
}

public init?(exdateString string: String, unitGranularity unit: NSCalendarUnit) {
let string = string.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
guard let range = string.rangeOfString("EXDATE:") where range.startIndex == string.startIndex else {
print("error: invalid exdate string, must be started with 'EXDATE:'")
return nil
}
let exdateString = string.substringFromIndex(range.endIndex)
let exdates = exdateString.componentsSeparatedByString(",").flatMap { (dateString) -> String? in
if (dateString.isEmpty || dateString.characters.count == 0) {
return nil
}
return dateString
}

self.dates = exdates.flatMap({ (dateString) -> NSDate? in
return RRule.dateFormatter.dateFromString(dateString)
})
self.unit = unit
}

public func toExDateString() -> String {
var exdateString = "EXDATE:"
let dateStrings = dates.map { (date) -> String in
return RRule.dateFormatter.stringFromDate(date)
}
if dateStrings.count > 0 {
exdateString += dateStrings.joinWithSeparator(",")
}

if exdateString.substringFromIndex(exdateString.endIndex.advancedBy(-1)) == "," {
exdateString.removeAtIndex(exdateString.endIndex.advancedBy(-1))
}

return exdateString
}
}
32 changes: 30 additions & 2 deletions RRuleSwift/Generators.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,21 @@ public extension RecurrenceRule {
guard let allOccurrences = context.evaluateScript("rule.all()").toArray() as? [NSDate] else {
return []
}
return allOccurrences

var occurrences = allOccurrences
if let exdates = exdate?.dates, unit = exdate?.unit {
for occurrence in occurrences {
for exdate in exdates {
if calendar.isDate(occurrence, equalToDate: exdate, toUnitGranularity: unit) {
let index = occurrences.indexOf(occurrence)!
occurrences.removeAtIndex(index)
break
}
}
}
}

return occurrences
}

public func occurrencesBetween(date date: NSDate, andDate otherDate: NSDate) -> [NSDate] {
Expand All @@ -49,9 +63,23 @@ public extension RecurrenceRule {
}
context.evaluateScript(rrulejs)
context.evaluateScript("var rule = new RRule({ \(ruleJSONString) })")
guard let occurrences = context.evaluateScript("rule.between(new Date('\(beginDateJSON)'), new Date('\(untilDateJSON)'))").toArray() as? [NSDate] else {
guard let betweenOccurrences = context.evaluateScript("rule.between(new Date('\(beginDateJSON)'), new Date('\(untilDateJSON)'))").toArray() as? [NSDate] else {
return []
}

var occurrences = betweenOccurrences
if let exdates = exdate?.dates, unit = exdate?.unit {
for occurrence in occurrences {
for exdate in exdates {
if calendar.isDate(occurrence, equalToDate: exdate, toUnitGranularity: unit) {
let index = occurrences.indexOf(occurrence)!
occurrences.removeAtIndex(index)
break
}
}
}
}

return occurrences
}
}
3 changes: 3 additions & 0 deletions RRuleSwift/RecurrenceRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ public struct RecurrenceRule {
/// The seconds of the minute associated with the recurrence rule, as an array of integers.
public var bysecond = [Int]()

/// The exclusion dates of the recurrence rule.
public var exdate: ExclusionDate?

public init(frequency: RecurrenceFrequency) {
self.frequency = frequency
}
Expand Down
2 changes: 1 addition & 1 deletion RRuleSwift/Supporting Files/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>0.0.4</string>
<string>0.0.5</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
Expand Down
4 changes: 2 additions & 2 deletions RRuleSwiftExample/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.0.4</string>
<string>0.0.5</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>4</string>
<string>5</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
Expand Down
19 changes: 16 additions & 3 deletions RRuleSwiftExample/RRuleExample.playground/Contents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,27 @@ if let rule1 = RecurrenceRule(recurrenceWithRRuleString: ruleString1) {
})
}

let ruleString2 = "RRULE:FREQ=YEARLY;COUNT=5;WKST=MO"
let exdateString = "EXDATE:20160416T030000Z,20160420T030000Z"
if let exclusionDate = ExclusionDate(exdateString: exdateString, unitGranularity: .Day) {
let exdates = exclusionDate.dates
}

let exdate1 = dateFormatter.dateFromString("2019-01-01 00:00:00 Sun")!
let exdate2 = dateFormatter.dateFromString("2021-01-01 00:00:00 Wed")!
let ruleExclusionDate = ExclusionDate(dates: [exdate1, exdate2], unitGranularity: .Year)
let ruleExDateString = ruleExclusionDate.toExDateString()


let ruleString2 = "RRULE:FREQ=YEARLY;COUNT=11;WKST=MO"
if let rule2 = RecurrenceRule(recurrenceWithRRuleString: ruleString2) {
var rule2 = rule2
rule2.exdate = ruleExclusionDate
let allDates = rule2.allOccurrences().map({ (date) -> String in
return dateFormatter.stringFromDate(date)
})

let date = dateFormatter.dateFromString("2017-01-01 00:00:00 Sun")
let otherDate = dateFormatter.dateFromString("2020-01-01 00:00:00 Wed")
let date = dateFormatter.dateFromString("2018-01-01 00:00:00 Sun")
let otherDate = dateFormatter.dateFromString("2024-01-01 00:00:00 Wed")
let betweenDates = rule2.occurrencesBetween(date: date!, andDate: otherDate!).map({ (date) -> String in
return dateFormatter.stringFromDate(date)
})
Expand Down
26 changes: 23 additions & 3 deletions RRuleSwiftExample/RRuleExample.playground/timeline.xctimeline
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,42 @@
version = "3.0">
<TimelineItems>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=5&amp;CharacterRangeLoc=805&amp;EndingColumnNumber=14&amp;EndingLineNumber=23&amp;StartingColumnNumber=9&amp;StartingLineNumber=23&amp;Timestamp=482212217.983148"
documentLocation = "#CharacterRangeLen=5&amp;CharacterRangeLoc=1349&amp;EndingColumnNumber=14&amp;EndingLineNumber=36&amp;StartingColumnNumber=9&amp;StartingLineNumber=36&amp;Timestamp=482305742.591149"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=8&amp;CharacterRangeLoc=802&amp;EndingColumnNumber=17&amp;EndingLineNumber=23&amp;StartingColumnNumber=9&amp;StartingLineNumber=23&amp;Timestamp=482212217.983615"
documentLocation = "#CharacterRangeLen=8&amp;CharacterRangeLoc=1346&amp;EndingColumnNumber=17&amp;EndingLineNumber=36&amp;StartingColumnNumber=9&amp;StartingLineNumber=36&amp;Timestamp=482305742.59131"
lockedSize = "{334, 216}"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=12&amp;CharacterRangeLoc=1075&amp;EndingColumnNumber=21&amp;EndingLineNumber=29&amp;StartingColumnNumber=9&amp;StartingLineNumber=29&amp;Timestamp=482212217.983765"
documentLocation = "#CharacterRangeLen=12&amp;CharacterRangeLoc=1619&amp;EndingColumnNumber=21&amp;EndingLineNumber=42&amp;StartingColumnNumber=9&amp;StartingLineNumber=42&amp;Timestamp=482305742.591435"
lockedSize = "{295, 168}"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=8&amp;CharacterRangeLoc=573&amp;EndingColumnNumber=17&amp;EndingLineNumber=16&amp;StartingColumnNumber=9&amp;StartingLineNumber=16&amp;Timestamp=481970863.875966"
lockedSize = "{178, 119}"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=1&amp;CharacterRangeLoc=856&amp;EndingColumnNumber=31&amp;EndingLineNumber=23&amp;StartingColumnNumber=5&amp;StartingLineNumber=23&amp;Timestamp=482304339.187826"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=7&amp;CharacterRangeLoc=828&amp;EndingColumnNumber=16&amp;EndingLineNumber=23&amp;StartingColumnNumber=9&amp;StartingLineNumber=23&amp;Timestamp=482304339.187957"
lockedSize = "{236, 71}"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=16&amp;CharacterRangeLoc=1096&amp;EndingColumnNumber=21&amp;EndingLineNumber=29&amp;StartingColumnNumber=5&amp;StartingLineNumber=29&amp;Timestamp=482305742.591905"
lockedSize = "{321.5, 50}"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
Expand Down

0 comments on commit aceea6d

Please sign in to comment.