// Date/time overloads and extensions Swift playground

// by Joey deVilla - August 2015

// Partially based on Axel Schlueter's SwiftDateTimeExtensions library

// (https://github.com/schluete/SwiftDateTimeExtensions)

//

// Released under the MIT License (MIT)

// (c) 2015 Joey deVilla

import UIKit

let userCalendar = NSCalendar.currentCalendar()

// Let's create some dates to work with

// ====================================

// If you need to instantiate a number of pre-defined dates,

// an NSDateFormatter will get it done in the fewest number of lines.

let dateMaker = NSDateFormatter()

dateMaker.dateFormat = "yyyy/MM/dd hh:mm:ss Z"

// I'm setting these dates according to my time zone (UTC-0500);

// feel free to adjust the values according to your time zone.

let valentinesDay = dateMaker.dateFromString("2015/02/14 00:00:00 -05:00")!

let stPatricksDay = dateMaker.dateFromString("2015/03/17 00:00:00 -05:00")!

// Comparing dates, part 1

// =======================

// First, the clunky NSDate::compare way

// -------------------------------------

// Returns true because Valentine's Day comes before St. Patrick's Day

valentinesDay.compare(stPatricksDay) == .OrderedAscending

// Returns true because they're the same date

valentinesDay.compare(valentinesDay) == .OrderedSame

// Returns true because St. Patrick's Day comes after Valentine's Day

stPatricksDay.compare(valentinesDay) == .OrderedDescending

// Let's overload the <, >, and == operators to do date comparisons

// ----------------------------------------------------------------

func ==(lhs: NSDate, rhs: NSDate) -> Bool

{

return lhs === rhs || lhs.compare(rhs) == .OrderedSame

}

func <(lhs: NSDate, rhs: NSDate) -> Bool

{

return lhs.compare(rhs) == .OrderedAscending

}

func >(lhs: NSDate, rhs: NSDate) -> Bool

{

return lhs.compare(rhs) == .OrderedDescending

}

// Comparisons are less clunky now!

// --------------------------------

valentinesDay < stPatricksDay // true

valentinesDay == valentinesDay // true

stPatricksDay > valentinesDay // true

// Comparing dates, part 2

// =======================

// Let's create two dates that are only one second apart:

let groundhogDay = dateMaker.dateFromString("2015/02/02 00:00:00 -05:00")!

let groundhogDayPlus1Second = dateMaker.dateFromString("2015/02/02 00:00:01 -05:00")!

// This line returns false:

groundhogDay == groundhogDayPlus1Second

// This line returns true:

groundhogDay < groundhogDayPlus1Second

// NSDate's compare method is too precise for a lot of uses!

// Enter NSCalendar's compareDate method, available in iOS 8 and later

// This returns false, because 2015/02/02 00:00:00 and 2015/02/02 00:00:01

// are NOT both within the same SECOND.

userCalendar.compareDate(groundhogDay,

toDate: groundhogDayPlus1Second,

toUnitGranularity: .Second)

== .OrderedSame

// This returns true, because 2015/02/02 00:00:00 and 2015/02/02 00:00:01

// ARE both within the same MINUTE.

userCalendar.compareDate(groundhogDay,

toDate: groundhogDayPlus1Second,

toUnitGranularity: .Minute)

== .OrderedSame

// This returns true, because 2015/02/02 00:00:00 and 2015/02/02 00:00:01

// ARE both within the same HOUR.

userCalendar.compareDate(groundhogDay,

toDate: groundhogDayPlus1Second,

toUnitGranularity: .Hour)

== .OrderedSame

// This returns true, because 2015/02/02 00:00:00 and 2015/02/02 00:00:01

// ARE both within the same DAY.

userCalendar.compareDate(groundhogDay,

toDate: groundhogDayPlus1Second,

toUnitGranularity: .Day)

== .OrderedSame

// 1 second before Groundhog Day, it was the previous day.

// Note what happens when we use compareDate:

let groundhogDayMinus1Second = dateMaker.dateFromString("2015/02/01 11:59:59 -05:00")!

// This returns false, because 2015/02/02 00:00:00 and 2015/02/01 11:59:59

// are NOT both within the same SECOND.

userCalendar.compareDate(groundhogDay,

toDate: groundhogDayMinus1Second,

toUnitGranularity: .Second)

== .OrderedSame

// This returns false, because while 2015/02/02 00:00:00 and 2015/02/01 11:59:59

// ARE within a minute of each other, they're not both within the SAME MINUTE

// (00:00 vs. 11:59).

userCalendar.compareDate(groundhogDay,

toDate: groundhogDayMinus1Second,

toUnitGranularity: .Minute)

== .OrderedSame

// This returns false, because while 2015/02/02 00:00:00 and 2015/02/01 11:59:59

// ARE within an hour of each other, they're not both within the SAME HOUR

// (0 vs. 11).

userCalendar.compareDate(groundhogDay,

toDate: groundhogDayMinus1Second,

toUnitGranularity: .Hour)

== .OrderedSame

// This returns false, because while 2015/02/02 00:00:00 and 2015/02/01 11:59:59

// ARE within a day of each other, they're not both within the SAME DAY

// (February 2 vs. February 1).

userCalendar.compareDate(groundhogDay,

toDate: groundhogDayMinus1Second,

toUnitGranularity: .Day)

== .OrderedSame

// This returns true, because while 2015/02/02 00:00:00 and 2015/02/01 11:59:59

// ARE in the same month.

userCalendar.compareDate(groundhogDay,

toDate: groundhogDayMinus1Second,

toUnitGranularity: .Month)

== .OrderedSame

// Date arithmetic

// ===============

// The clunky way

// --------------

// Suppose we want to get the date of Groundhog Day plus

// 1 month, 8 days, 6 hours, and 17 minutes

let timeInterval = NSDateComponents()

timeInterval.month = 1

timeInterval.day = 8

timeInterval.hour = 6

timeInterval.minute = 17

// The resulting date should be March 10, 2015, 6:17 a.m.

let resultDate = userCalendar.dateByAddingComponents(timeInterval,

toDate: groundhogDay,

options: [])!

// The neat way

// ------------

// First, we define methods that allow us to add and subtract

// NSDateComponents instances

// The addition and subtraction code is nearly the same,

// so we've factored it out into this method

func combineComponents(lhs: NSDateComponents,

rhs: NSDateComponents,

_ multiplier: Int = 1)

-> NSDateComponents

{

let result = NSDateComponents()

let undefined = Int(NSDateComponentUndefined)

result.second = ((lhs.second != undefined ? lhs.second : 0) +

(rhs.second != undefined ? rhs.second : 0) * multiplier)

result.minute = ((lhs.minute != undefined ? lhs.minute : 0) +

(rhs.minute != undefined ? rhs.minute : 0) * multiplier)

result.hour = ((lhs.hour != undefined ? lhs.hour : 0) +

(rhs.hour != undefined ? rhs.hour : 0) * multiplier)

result.day = ((lhs.day != undefined ? lhs.day : 0) +

(rhs.day != undefined ? rhs.day : 0) * multiplier)

result.month = ((lhs.month != undefined ? lhs.month : 0) +

(rhs.month != undefined ? rhs.month : 0) * multiplier)

result.year = ((lhs.year != undefined ? lhs.year : 0) +

(rhs.year != undefined ? rhs.year : 0) * multiplier)

return result

}

// With combineComponents defined,

// overloading + and - is simple

func +(lhs: NSDateComponents, rhs: NSDateComponents) -> NSDateComponents

{

return combineComponents(lhs, rhs: rhs)

}

func -(lhs: NSDateComponents, rhs: NSDateComponents) -> NSDateComponents

{

return combineComponents(lhs, rhs: rhs, -1)

}

// We'll need to overload unary - so we can negate components

prefix func -(components: NSDateComponents) -> NSDateComponents {

let result = NSDateComponents()

let undefined = Int(NSDateComponentUndefined)

if(components.second != undefined) { result.second = -components.second }

if(components.minute != undefined) { result.minute = -components.minute }

if(components.hour != undefined) { result.hour = -components.hour }

if(components.day != undefined) { result.day = -components.day }

if(components.month != undefined) { result.month = -components.month }

if(components.year != undefined) { result.year = -components.year }

return result

}

// Next, we extend Int to bring some Ruby-like magic

// to date components

extension Int {

var seconds: NSDateComponents {

let components = NSDateComponents()

components.second = self;

return components

}

var second: NSDateComponents {

return self.seconds

}

var minutes: NSDateComponents {

let components = NSDateComponents()

components.minute = self;

return components

}

var minute: NSDateComponents {

return self.minutes

}

var hours: NSDateComponents {

let components = NSDateComponents()

components.hour = self;

return components

}

var hour: NSDateComponents {

return self.hours

}

var days: NSDateComponents {

let components = NSDateComponents()

components.day = self;

return components

}

var day: NSDateComponents {

return self.days

}

var weeks: NSDateComponents {

let components = NSDateComponents()

components.day = 7 * self;

return components

}

var week: NSDateComponents {

return self.weeks

}

var months: NSDateComponents {

let components = NSDateComponents()

components.month = self;

return components

}

var month: NSDateComponents {

return self.months

}

var years: NSDateComponents {

let components = NSDateComponents()

components.year = self;

return components

}

var year: NSDateComponents {

return self.years

}

}

// Building an NSDateComponents instance that represents

// a time interval is now a lot nicer:

let newTimeInterval = 1.month + 8.days + 6.hours + 17.minutes

// Let's confirm that it works

newTimeInterval.month // 1

newTimeInterval.day // 8

newTimeInterval.hour // 6

newTimeInterval.minute // 17

// Let's make it easy to add dates and components,

// and subtract components from dates

// Date + component

func +(lhs: NSDate, rhs: NSDateComponents) -> NSDate

{

return NSCalendar.currentCalendar().dateByAddingComponents(rhs,

toDate: lhs,

options: [])!

}

// Component + date

func +(lhs: NSDateComponents, rhs: NSDate) -> NSDate

{

return rhs + lhs

}

// Date - component

// (Component - date doesn't make sense)

func -(lhs: NSDate, rhs: NSDateComponents) -> NSDate

{

return lhs + (-rhs)

}

// Look at how easy date arithmetic is now:

// What's the date and time 2 weeks, 1 day, 13 hours, and 57 minutes

// after Groundhog Day 2015?

groundhogDay + 2.weeks + 1.day + 13.hours + 57.minutes

// (Answer: February 17, 2015, 1:57 p.m.)

// Adding dates to date components is quite flexible:

2.weeks + 1.day + 13.hours + 57.minutes + groundhogDay

2.weeks + 1.day + groundhogDay + 13.hours + 57.minutes

// What was the date 1 year, 2 months, and 12 days

// prior to Groundhog Day 2015?

groundhogDay - 1.year - 2.months - 12.days

/// (Answer: November 20, 2013)

// And finally some Ruby on Rails magic, that allows us to create dates

// with code like "2.days.fromNow" and "2.days.ago"

extension NSDateComponents {

var fromNow: NSDate {

let currentCalendar = NSCalendar.currentCalendar()

return currentCalendar.dateByAddingComponents(self,

toDate: NSDate(),

options: [])!

}

var ago: NSDate {

let currentCalendar = NSCalendar.currentCalendar()

return currentCalendar.dateByAddingComponents(-self,

toDate: NSDate(),

options: [])!

}

}

// Let's test the Rails magic!

// (It's August 26, 2015, 23:06 as I execute this code)

// August 28, 2015, 11:06 p.m.

2.days.fromNow

// August 29, 2015, 2:23 a.m.

(2.days + 3.hours + 17.minutes).fromNow

// August 24, 2015, 11:06 p.m.

2.days.ago

// August 24, 2015, 7:49 p.m.

(2.days + 3.hours + 17.minutes).ago