Search code examples
iosswiftnsdatenscalendarnsdatecomponents

Strange date result calculating start of next month using NSDateComponents in Swift 2


I have a function:

internal func startOfNextMonth() -> NSDate? {
    guard
        let cal: NSCalendar = NSCalendar.currentCalendar(),
        let comp: NSDateComponents = NSDateComponents() else { return nil }
    comp.month = 1
    comp.day = 0
    comp.to12pm()
    let date = cal.dateByAddingComponents(comp, toDate: self.startOfMonth()!, options: [])!
    return date
}

This should calculate the first day of next month, however it's returning 2016-03-02 08:00:00 UTC.

Is this something to do with it being a leap year (Feb. 29 messing it up?)

Here's my startOfMonth function for reference, and the to12pm() extension:

internal func startOfMonth() -> NSDate? {
    guard
        let cal: NSCalendar = NSCalendar.autoupdatingCurrentCalendar(),
        let comp: NSDateComponents = cal.components([.Year, .Month], fromDate: self.at12pm()!) else { return nil }
    comp.to12pm()
    return cal.dateFromComponents(comp)!
}

internal extension NSDateComponents {
    func to12pm() {
        self.hour = 12
        self.minute = 0
        self.second = 0
    }
}

internal func at12pm() -> NSDate? {
    let cal = NSCalendar.currentCalendar()
    return cal.dateBySettingHour(12, minute: 0, second: 0, ofDate: self, options: [])
}

Solution

  • startOfMonth() returns first of next month at 12 pm (12:00) then you add another 12 hours in startOfNextMonth(), this results one day ahead.

    NSCalendar has a smart method to calculate the start of next month regardless of daylight saving changes or other irregularity

    let calendar = NSCalendar.currentCalendar()
    let components = NSDateComponents()
    components.day = 1
    let startOfNextMonth = calendar.nextDateAfterDate(NSDate(), matchingComponents: components, options: .MatchNextTime)