Skip to content

Instantly share code, notes, and snippets.

@simme
Last active July 20, 2016 10:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save simme/b1fd08cb30c1a52225274323c461153f to your computer and use it in GitHub Desktop.
Save simme/b1fd08cb30c1a52225274323c461153f to your computer and use it in GitHub Desktop.
A struct representing a range of dates.
/// 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