Questions about NSCalendar

Here's a code snippet that compiles and works as expected, but I get a warning for depricated use of the unit .NSYearCalendarUnit.


This is within XCode 7 (Swift 2.0)


Playground execution failed: /var/folders/37/2hl0kpw50lv18q1d48gbbb5r0000gn/T/./lldb/16158/playground8.swift:52:20: warning: 'NSYearCalendarUnit' was deprecated in iOS 8.0: Use NSCalendarUnitYear instead


var TermEndDate = NSDate()

if (TermYears>0) {
    TermEndDate = userCalendar.dateByAddingUnit(
    NSCalendarUnit.NSYearCalendarUnit,
    value: TermYears,
    toDate: TermStartDate,
    options: .WrapComponents )!
}

if (leaseMonths>0) {
    TermEndDate = userCalendar.dateByAddingUnit(
    NSCalendarUnit.NSMonthCalendarUnit,
    value: TermMonths,
    toDate: TermEndDate,
    options: .WrapComponents )!
}
If I switch to the recommended syntax, it errors out.

/var/folders/37/2hl0kpw50lv18q1d48gbbb5r0000gn/T/./lldb/16158/playground8.swift:60:5: error: 'NSCalendarUnit.Type' does not have a member named 'NSCalendarUnitMonth'

NSCalendarUnit.NSCalendarUnitMonth,

^ ~~~~~~~~~~~~~~~~~~~


I've dug through the documentation and can't find what I'm doing wrong.


Thoughts?


Thanks!!!

Accepted Reply

They renamed some enums / options to be more consistent.


In Swift, it would now be NSCalendarUnit.Year (type NSCalendarUnit, case .Year). If you double command-click on NSCalendarUnit (or NSYearCalendarUnit) in Xcode in your playground, it will pull up the Swift version of the header.


And as far as the example code goes, OptionSets are handled differently now in Swift 2.0 and use set syntax instead of bitwise operators to join and test them.

See the Xcode 7 beta release notes or https://forums.developer.apple.com/thread/3623 for more info on that.

Replies

Also.... I found a very informative website that has a ton of examples specifically on how to work with dates within Swift.


Here's a code snippet that he uses as an example, but does not compile for me, and I have no idea why I get the "error: could not find member 'CalendarUnitDay'". Was this syntax changed with Swift 2.0?


Every example I could find uses this syntax, but for me... no luck.


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

import UIKit

let userCalendar = NSCalendar.currentCalendar()

// Let's create some dates to work with
// ====================================

// It's 3:45:30 a.m., New Year's Day. Time to go home.
let goHomeYoureDrunkTimeComponents = NSDateComponents()
goHomeYoureDrunkTimeComponents.year = 2015
goHomeYoureDrunkTimeComponents.month = 1
goHomeYoureDrunkTimeComponents.day = 1
goHomeYoureDrunkTimeComponents.hour = 3
goHomeYoureDrunkTimeComponents.minute = 45
goHomeYoureDrunkTimeComponents.second = 30
let goHomeYoureDrunkTime = userCalendar.dateFromComponents(goHomeYoureDrunkTimeComponents)!

// Let's create an NSDate representing Bad Poetry Day (August 18)
// at 4:20:10 p.m.
let badPoetryDayComponents = NSDateComponents()
badPoetryDayComponents.year = 2015
badPoetryDayComponents.month = 8
badPoetryDayComponents.day = 18
badPoetryDayComponents.hour = 16
badPoetryDayComponents.minute = 20
badPoetryDayComponents.second = 10
let badPoetryDay = userCalendar.dateFromComponents(badPoetryDayComponents)!

// How many days, hours, minutes, and seconds between
// goHomeYoureDrunkTime and badPoetryDay?
let dayHourMinuteSecond: NSCalendarUnit =
    .CalendarUnitDay    |  //                <<-- Error listed below
    .CalendarUnitHour   |
    .CalendarUnitMinute |
    .CalendarUnitSecond

let difference = NSCalendar.currentCalendar().components(
    dayHourMinuteSecond,
    fromDate: goHomeYoureDrunkTime,
    toDate: badPoetryDay,
    options: nil)

difference.day     // 229
difference.hour    // 12
difference.minute  // 34
difference.second  // 40


I get the following errors in XCode...


Playground execution failed: /var/folders/37/2hl0kpw50lv18q1d48gbbb5r0000gn/T/./lldb/16158/playground35.swift:37:6: error: could not find member 'CalendarUnitDay'

.CalendarUnitDay |

~^~~~~~~~~~~~~~~

/var/folders/37/2hl0kpw50lv18q1d48gbbb5r0000gn/T/./lldb/16158/playground35.swift:42:47: error: cannot invoke 'components' with an argument list of type '(NSCalendarUnit, fromDate: NSDate, toDate: NSDate, options: nil)'

let difference = NSCalendar.currentCalendar().components(

^


Thoughts?


Thanks!!

They renamed some enums / options to be more consistent.


In Swift, it would now be NSCalendarUnit.Year (type NSCalendarUnit, case .Year). If you double command-click on NSCalendarUnit (or NSYearCalendarUnit) in Xcode in your playground, it will pull up the Swift version of the header.


And as far as the example code goes, OptionSets are handled differently now in Swift 2.0 and use set syntax instead of bitwise operators to join and test them.

See the Xcode 7 beta release notes or https://forums.developer.apple.com/thread/3623 for more info on that.

Ok.. that finally clicked. Thanks!!!


For those following along, the proper code that doesn't flag a warning is:


if (leaseTermYears>0) {
    leaseTermEnd = userCalendar.dateByAddingUnit(
    NSCalendarUnit.Year,
    value: leaseTermYears,
    toDate: startLease,
    options: .WrapComponents )!
}


I'm still attempting to fix the second example, but I think I understand the change now. I'll post the solution when I get it working for everyone else who may be interested as well.

I'm getting a bit futher.


let dayHourMinuteSecond = NSDateComponentsFormatter()
    dayHourMinuteSecond.allowedUnits = [.Day, .Hour, .Minute, .Second]


Now conforms to the new way Swift 2.0 wants things formatted.


I'm trying to figure out the calculated calendar time difference between two dates. Pretty simple. I think the function is the following: (using the autofill template),


let userCalendar = NSCalendar.currentCalendar()
let difference = userCalendar.components(
    <#T##unitFlags: NSCalendarUnit##NSCalendarUnit#>,
    fromDate: <#T##NSDate#>,
    toDate: <#T##NSDate#>,
    options: <#T##NSCalendarOptions#>)

So my code with my variables would read (with the components used from the formatter above (which the compiler is happy with)

let difference = userCalendar.components(
    dayHourMinuteSecond,
    fromDate: goHomeYoureDrunkTime,
    toDate: badPoetryDay,
    options: NSCalendarOptions)

But I can't find any value that satisfies the Options for this.

In my other code, WrapComponents was the only value that worked, even though I really did want it to carry forward and overflow months into years.

It's a UInt Struct (I've listed the header file below). nil doesn't work, 0 doesn't work, UInt 0 doesn't work, _ doesn't work. None of the values in the struct (WrapComponents, MatchStrictly, etc) work...


Thoughts?


struct NSCalendarOptions : OptionSetType {
    init(rawValue: UInt)

    static var WrapComponents: NSCalendarOptions { get } /

    @available(iOS 7.0, *)
    static var MatchStrictly: NSCalendarOptions { get }
    @available(iOS 7.0, *)
    static var SearchBackwards: NSCalendarOptions { get }

    @available(iOS 7.0, *)
    static var MatchPreviousTimePreservingSmallerUnits: NSCalendarOptions { get }
    @available(iOS 7.0, *)
    static var MatchNextTimePreservingSmallerUnits: NSCalendarOptions { get }
    @available(iOS 7.0, *)
    static var MatchNextTime: NSCalendarOptions { get }

    @available(iOS 7.0, *)
    static var MatchFirst: NSCalendarOptions { get }
    @available(iOS 7.0, *)
    static var MatchLast: NSCalendarOptions { get }
}

You'll need to pass the formatter's unit OptionSet as the first parameter, and then need to make an OptionSet using the same syntax for the NSCalendarOptions.


You can pass an empty set [ ] for no options, or [.WrapComponents, .MatchFirst] etc...


let difference = NSCalendar.currentCalendar().components(
    dayHourMinuteSecond.allowedUnits,
    fromDate: goHomeYoureDrunkTime,
    toDate: badPoetryDay,
    options: [])

LCS,


Many Many thanks for your help! This now works as expected:


For those following along and someone else can hopefully learn from the thrashing I did to get this to work...


let userCalendar = NSCalendar.currentCalendar()

let difference = userCalendar.components(
    [NSCalendarUnit.Day, NSCalendarUnit.Hour, NSCalendarUnit.Minute, NSCalendarUnit.Second],
    fromDate: goHomeYoureDrunkTime,
    toDate: badPoetryDay,
    options: [] )


To pass in the units as with a constant representing the array, I had to use:


let dayHourMinuteSecond =    NSCalendarUnit([ .Day, .Hour, .Minute, .Second] )


Please note, I was wrong above, using a NSDateComponentsFormatter array was not acceptable for this call.


let dayHourMinuteSecond = NSDateComponentsFormatter()
    dayHourMinuteSecond.allowedUnits = [.Day, .Hour, .Minute, .Second]  // DOES NOT WORK FOR NSCalendar.currentCalendar.components


This also worked for the Options, to pass them in as a constant array:


let calendarOptions = NSCalendarOptions ([.WrapComponents])
let calendarOptions2 = NSCalendarOptions ([ ])


This also helped me get rid of my ugly work around to having to use the .WrapComponents to get it to compile.


I can now pass in the months at 38 and it will calculate the length of the term, with it all just adding up as I had hoped.


let termMonths = 38;
let CalendarUnitsMonthOnly =    NSCalendarUnit( [.Month] )

let calendarOptions = NSCalendarOptions ([.WrapComponents])
let calendarOptions2 = NSCalendarOptions ([ ])

TermEnd = userCalendar.dateByAddingUnit(
        CalendarUnitsMonthOnly,
        value: termMonths,
        toDate: startDate,
        options: calendarOptions2)!

print(TermEnd)


I still don't fully understand why userCalendar.dateByAddingUnit returns an Optional, so I had to use the ! operator to unwrap it, (I think?) whereas the userCalendar.componets didn't.


Thoughts?


It's working now, and I have a much better grasp of how to call everything in Swift / Swift 2.0


Thanks VERY much LCS.

wrt returning nil. I think dateByAddingUnit works by calling dateByAddingComponents, and that returns nil because (quoting from the docs):


Returns nil if date falls outside the defined range of the receiver or if the computation cannot be performed.


This would be much easier to figure out if only Apple would document these "new" NSCalendar methods which have been around now for a couple years.