dateBySettingUnit does not return past date.

Sample code is following:

//: Playground - noun: a place where people can play

import Foundation

let timeZone = NSTimeZone(name: "PST")!
// Make Results Sidebar to display in PST
NSTimeZone.setDefaultTimeZone(timeZone)

let cal = NSCalendar(identifier: NSCalendarIdentifierGregorian)!
let comp = NSDateComponents()
comp.calendar = cal
comp.timeZone = timeZone
comp.era = 1
comp.year = 2015
comp.month = 7
comp.day = 24
comp.hour = 11
comp.minute = 27
comp.second = 0
comp.nanosecond = 0

let d = cal.dateFromComponents(comp)! // "2015/07/24 11:27"

//: getting past date
var u: NSCalendarUnit, v: Int

// I wish "2015/07/24 11:00"
u = .Minute
v = 0
// Using dateBySettingUnit fails
cal.dateBySettingUnit(u, value: v, ofDate: d, options: .WrapComponents)                                // "2015/07/24 12:00"
cal.dateBySettingUnit(u, value: v, ofDate: d, options: .MatchStrictly)                                 // "2015/07/24 12:00"
cal.dateBySettingUnit(u, value: v, ofDate: d, options: .SearchBackwards)                               // "2015/07/24 12:00"
cal.dateBySettingUnit(u, value: v, ofDate: d, options: .MatchPreviousTimePreservingSmallerUnits)       // "2015/07/24 12:00"
cal.dateBySettingUnit(u, value: v, ofDate: d, options: .MatchNextTimePreservingSmallerUnits)           // "2015/07/24 12:00"
cal.dateBySettingUnit(u, value: v, ofDate: d, options: .MatchNextTime)                                 // "2015/07/24 12:00"
cal.dateBySettingUnit(u, value: v, ofDate: d, options: .MatchFirst)                                    // "2015/07/24 12:00"
cal.dateBySettingUnit(u, value: v, ofDate: d, options: .MatchLast)                                     // "2015/07/24 12:00"
// Using nextDateAfterDate fails. Yes, it's intended.
cal.nextDateAfterDate(d, matchingUnit: u, value: v, options: .MatchPreviousTimePreservingSmallerUnits) // "2015/07/24 12:00"
cal.nextDateAfterDate(d, matchingUnit: u, value: v, options: .MatchNextTimePreservingSmallerUnits)     // "2015/07/24 12:00"
cal.nextDateAfterDate(d, matchingUnit: u, value: v, options: .MatchNextTime)                           // "2015/07/24 12:00"

// I wish "2015/07/24 11:13"
v = 13
// Using dateBySettingUnit fails
cal.dateBySettingUnit(u, value: v, ofDate: d, options: .WrapComponents)                                // "2015/07/25 0:00"
cal.dateBySettingUnit(u, value: v, ofDate: d, options: .MatchStrictly)                                 // "2015/07/25 0:00"
cal.dateBySettingUnit(u, value: v, ofDate: d, options: .SearchBackwards)                               // "2015/07/25 0:00"
cal.dateBySettingUnit(u, value: v, ofDate: d, options: .MatchPreviousTimePreservingSmallerUnits)       // "2015/07/25 0:00"
cal.dateBySettingUnit(u, value: v, ofDate: d, options: .MatchNextTimePreservingSmallerUnits)           // "2015/07/25 0:00"
cal.dateBySettingUnit(u, value: v, ofDate: d, options: .MatchNextTime)                                 // "2015/07/25 0:00"
cal.dateBySettingUnit(u, value: v, ofDate: d, options: .MatchFirst)                                    // "2015/07/25 0:00"
cal.dateBySettingUnit(u, value: v, ofDate: d, options: .MatchLast)                                     // "2015/07/25 0:00"
// Using nextDateAfterDate fails. Yes, it's intended, but something wrongs…
cal.nextDateAfterDate(d, matchingUnit: u, value: v, options: .MatchPreviousTimePreservingSmallerUnits) // "2015/07/24 13:13"
cal.nextDateAfterDate(d, matchingUnit: u, value: v, options: .MatchNextTimePreservingSmallerUnits)     // "2015/07/25 0:00"
cal.nextDateAfterDate(d, matchingUnit: u, value: v, options: .MatchNextTime)                           // "2015/07/25 0:00"

//: getting future date

// I wish "2015/07/24 11:28"
v = 28
// Using dateBySettingUnit successes
cal.dateBySettingUnit(u, value: v, ofDate: d, options: .WrapComponents)                                // "2015/07/24 11:28"
cal.dateBySettingUnit(u, value: v, ofDate: d, options: .MatchStrictly)                                 // "2015/07/24 11:28"
cal.dateBySettingUnit(u, value: v, ofDate: d, options: .SearchBackwards)                               // "2015/07/24 11:28"
cal.dateBySettingUnit(u, value: v, ofDate: d, options: .MatchPreviousTimePreservingSmallerUnits)       // "2015/07/24 11:28"
cal.dateBySettingUnit(u, value: v, ofDate: d, options: .MatchNextTimePreservingSmallerUnits)           // "2015/07/24 11:28"
cal.dateBySettingUnit(u, value: v, ofDate: d, options: .MatchNextTime)                                 // "2015/07/24 11:28"
cal.dateBySettingUnit(u, value: v, ofDate: d, options: .MatchFirst)                                    // "2015/07/24 11:28"
cal.dateBySettingUnit(u, value: v, ofDate: d, options: .MatchLast)                                     // "2015/07/24 11:28"
// Using nextDateAfterDate successes. Yes, it's intended
cal.nextDateAfterDate(d, matchingUnit: u, value: v, options: .MatchPreviousTimePreservingSmallerUnits) // "2015/07/24 11:28"
cal.nextDateAfterDate(d, matchingUnit: u, value: v, options: .MatchNextTimePreservingSmallerUnits)     // "2015/07/24 11:28"
cal.nextDateAfterDate(d, matchingUnit: u, value: v, options: .MatchNextTime)                           // "2015/07/24 11:28"


Is this intended behavior?

Replies

I seem to have come across this issue as well, so it apparently still exists or may be intentional This is my (mostly implemented) work around:

extension NSCalendar {

/

* This is to deal with the odd issue of dateBySettingUnit only working for future dates, and not past dates

*/

func dateByActuallySettingUnit(unit: NSCalendarUnit, newValue: Int, date: NSDate) -> NSDate {

let currentValue = component(unit, fromDate: date)

let componants = NSDateComponents()

componants.setValue(unit, newValue: (newValue - currentValue))

return dateByAddingComponents(componants, toDate: date, options: NSCalendarOptions(rawValue: 0))!

}

}

extension NSDateComponents {

func setValue(unit: NSCalendarUnit, newValue: Int) {

switch unit {

case NSCalendarUnit.Era:

era = newValue

case NSCalendarUnit.Year:

year = newValue

case NSCalendarUnit.Month:

month = newValue

case NSCalendarUnit.Minute:

minute = newValue

case NSCalendarUnit.Second:

second = newValue

case NSCalendarUnit.Nanosecond:

nanosecond = newValue

default:

return

}

}