Last active
July 20, 2016 10:50
-
-
Save simme/b1fd08cb30c1a52225274323c461153f to your computer and use it in GitHub Desktop.
A struct representing a range of dates.
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
/// License: MIT | |
import Foundation | |
/** | |
A struct representing a range of dates. | |
*/ | |
public struct DateRange { | |
// MARK: Properties | |
public let startDate: NSDate | |
public let endDate: NSDate | |
// MARK: Initialization | |
/** | |
Creates a new `DateRange` with the given start and end dates. | |
- Parameter start: The first `NSDate` in the range. | |
- Parameter end: The last `NSDate` in the range. | |
- Returns: A newly initialized `DateRange` with the given start and end dates. | |
*/ | |
public init(start: NSDate, end: NSDate) { | |
precondition(start.compare(end) == NSComparisonResult.OrderedAscending) | |
self.startDate = start | |
self.endDate = end | |
} | |
/** | |
Creates a new date range by calculating the first date based on the day of the week a week starts and the current date. | |
The `startOfWeek` parameter should represent the weekday that marks the first day of a week. `1` means Sunday. All date math done according to the Gregorian calendar. | |
- Parameter date: The date to start from when calculating the range. | |
- Parameter startOfWeek: The first weekday of a week. `1` is Sunday. | |
- Returns: A newly initialized `DateRange` with the `startDate` being the first date of the current week as decided by the `startOfWeek` parameter. | |
*/ | |
public init(date: NSDate, startOfWeek: Int) { | |
precondition(startOfWeek >= 0) | |
precondition(startOfWeek < 8) | |
guard let calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian) else { | |
fatalError("Could not create gregorian calendar") | |
} | |
calendar.timeZone = NSTimeZone.localTimeZone() | |
let components = calendar.components(.Weekday, fromDate: date) | |
/** | |
Monday, week starts on Saturday, need to back two days. | |
7 + 2 - 7 = 2 | |
Wednesday, week starts on Friday, need to go back five days. | |
7 + 4 - 6 = 5 | |
Friday, week starts on Monday, need to go back four days. | |
6 - 2 = 4 | |
Sunday, week starts on Sunday, no need to go back. | |
1 - 1 = 0 | |
Sunday, week starts on Monday, need to go back six days. | |
7 + 1 - 2 = 6 | |
M T W T F S S | |
2 3 4 5 6 7 1 | |
*/ | |
// Add one week to dates where current week day is < startOfWeek to compensate for difference. | |
let weekday = components.weekday < startOfWeek ? components.weekday + 7 : components.weekday | |
let subtractionComponents = NSDateComponents() | |
subtractionComponents.day = -(weekday - startOfWeek) | |
guard let startDate = calendar.dateByAddingComponents(subtractionComponents, toDate: date, options: []) else { | |
fatalError("Could not produce start date") | |
} | |
// Now normalize date to midnight | |
let midnightComponents = calendar.components([.Year, .Month, .Day], fromDate: startDate) | |
guard let firstDayOfWeek = calendar.dateFromComponents(midnightComponents) else { | |
fatalError("Failed to construct midnight normalized start date") | |
} | |
// Create end date by adding 7 days to the start date | |
let endOfWeekComponents = calendar.components([.Year, .Month, .Day], fromDate: firstDayOfWeek) | |
endOfWeekComponents.day += 6 | |
guard let lastDayOfWeek = calendar.dateFromComponents(endOfWeekComponents) else { | |
fatalError("Failed to construct midnight normalized start date") | |
} | |
self.startDate = firstDayOfWeek | |
self.endDate = lastDayOfWeek | |
} | |
/** | |
- Returns: `true` if the given date is in the current date range. | |
*/ | |
public func rangeIncludes(date date: NSDate) -> Bool { | |
return date.timeIntervalSinceReferenceDate >= startDate.timeIntervalSinceReferenceDate | |
&& date.timeIntervalSinceReferenceDate <= endDate.timeIntervalSinceReferenceDate | |
} | |
} | |
// MARK: - Generator and SequenceType | |
public class DateRangeGenerator: GeneratorType { | |
public typealias Element = NSDate | |
private let calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)! | |
private let range: DateRange | |
private var current: NSDate | |
public init(range: DateRange) { | |
self.range = range | |
self.current = range.startDate | |
} | |
public func next() -> Element? { | |
let currentDay = current.copy() as! NSDate | |
current = calendar.dateByAddingUnit(.Day, value: 1, toDate: current, options: NSCalendarOptions.MatchStrictly)! | |
let comparisonResult = currentDay.compare(range.endDate) | |
if comparisonResult == .OrderedAscending || comparisonResult == .OrderedSame { | |
return currentDay | |
} else { | |
return nil | |
} | |
} | |
} | |
extension DateRange: SequenceType { | |
public typealias Generator = DateRangeGenerator | |
public func generate() -> DateRangeGenerator { | |
return DateRangeGenerator(range: self) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment