Dates. More than any other data type, the gulf between the initial banality of dates and their true, multifaceted complexity looms terrifyingly large. Combining sub-second precision, overlapping units, geopolitical time zone boundaries, localization differences in both language and grammar, and Daylight Saving shifts and leap year adjustments that literally add and remove whole chunks of time from measured existence, there’s a lot to process.

To embark on any date-heavy task, then, requires a solid understanding of the tools already at your fingertips. Better to use a Foundation method than to write the n-thousandth version of date Is Tomorrow . Are you using NSDate Components ? Did you specify all the right calendar units? Will your code still work correctly on February 28, 2100?

But here’s the thing: the APIs you’re already using have been holding out on you. Unless you’re digging through release notes and API diffs, you wouldn’t know that over the last few releases of OS X, NSCalendar has quietly built a powerful set of methods for accessing and manipulating dates, and that the latest release brought them all to iOS.

Swift Objective-C let calendar = NSCalendar . current Calendar () NSCalendar * calendar = [ NSCalendar current Calendar ];

From new ways of accessing individual date components and flexibly comparing dates to powerful date interpolation and enumeration methods, there’s far too much to ignore. Make some room in your calendar and read on for more.

Convenient Component Access

Oh, NSDate Components . So practical and flexible, yet so cumbersome when I just. Want to know. What the hour is. NSCalendar to the rescue!

Swift Objective-C let hour = calendar . component ( . Calendar Unit Hour , from Date : NSDate ()) NSInteger hour = [ calendar component : NSCalendar Unit Hour from Date :[ NSDate date ]];

That’s much better. NSCalendar , what else can you do?

get Era(_:year:month:day:from Date:) : Returns the era, year, month, and day of the given date by reference. Pass nil / NULL for any parameters you don’t need.

: Returns the era, year, month, and day of the given date by reference. Pass / for any parameters you don’t need. get Era(_:year For Week Of Year:week Of Year:weekday:from Date:) : Returns the era, year (for week of year), week of year, and weekday of the given date by reference. Pass nil / NULL for any parameters you don’t need.

: Returns the era, year (for week of year), week of year, and weekday of the given date by reference. Pass / for any parameters you don’t need. get Hour(_:minute:second:nanosecond:from Date:) : Returns time information for the given date by reference. nil / NULL , you get the idea.

And just kidding, NSDate Components , I take it all back. There are a couple methods for you, too:

components In Time Zone(_:from Date:) : Returns an NSDate Components instance with components of the given date shifted to the given time zone.

: Returns an instance with components of the given date shifted to the given time zone. components(_:from Date Components:to Date Components:options:) : Returns the difference between two NSDate Components instances. The method will use base values for any components that are not set, so provide at the least the year for each parameter. The options parameter is unused; pass nil / 0 .

Date Comparison

While direct NSDate comparison has always been a simple matter, more meaningful comparisons can get surprisingly complex. Do two NSDate instances fall on the same day? In the same hour? In the same week?

Fret no more, NSCalendar has you covered with an extensive set of comparison methods:

is Date In Today(_:) : Returns true if the given date is today.

: Returns if the given date is today. is Date In Tomorrow(_:) : Returns true if the given date is tomorrow.

: Returns if the given date is tomorrow. is Date In Yesterday(_:) : Returns true if the given date is a part of yesterday.

: Returns if the given date is a part of yesterday. is Date In Weekend(_:) : Returns true if the given date is part of a weekend, as defined by the calendar.

: Returns if the given date is part of a weekend, as defined by the calendar. is Date(_:in Same Day As Date:) : Returns true if the two NSDate instances are on the same day—delving into date components is unnecessary.

: Returns if the two instances are on the same day—delving into date components is unnecessary. is Date(_:equal To Date:to Unit Granularity:) : Returns true if the dates are identical down to the given unit of granularity. That is, two date instances in the same week would return true if used with calendar.is Date(tuesday, equal To Date: thursday, to Unit Granularity: .Calendar Unit Week Of Year) , even if they fall in different months.

: Returns if the dates are identical down to the given unit of granularity. That is, two date instances in the same week would return true if used with , even if they fall in different months. compare Date(_:to Date:to Unit Granularity:) : Returns an NSComparison Result , treating as equal any dates that are identical down to the given unit of granularity.

: Returns an , treating as equal any dates that are identical down to the given unit of granularity. date(_:matches Components:) : Returns true if a date matches the specific components given.

Date Interpolation

Next up is a set of methods that allows you to find the next date(s) based on a starting point. You can find the next (or previous) date based on an NSDate Components instance, an individual date component, or a specific hour, minute, and second. Each of these methods takes an NSCalendar Options bitmask parameter that provides fine-grained control over how the next date is selected, particularly in cases where an exact match isn’t found at first.

NSCalendar Options

The easiest option of NSCalendar Options is .Search Backwards , which reverses the direction of each search, for all methods. Backward searches are constructed to return dates similar to forward searches. For example, searching backwards for the previous date with an hour of 11 would give you 11:00, not 11:59, even though 11:59 would technically come “before” 11:00 in a backwards search. Indeed, backward searching is intuitive until you think about it and then unintuitive until you think about it a lot more. That .Search Backwards is the easy part should give you some idea of what’s ahead.

The remainder of the options in NSCalendar Options help deal with “missing” time instances. Time can be missing most obviously if one searches in the short window when time leaps an hour forward during a Daylight Saving adjustment, but this behavior can also come into play when searching for dates that don’t quite add up, such as the 31st of February or April.

When encountering missing time, if NSCalendar Options.Match Strictly is provided, the methods will continue searching to find an exact match for all components given, even if that means skipping past higher order components. Without strict matching invoked, one of .Match Next Time , .Match Next Time Preserving Smaller Units , and .Match Previous Time Preserving Smaller Units must be provided. These options determine how a missing instance of time will be adjusted to compensate for the components in your request.

In this case, an example will be worth a thousand words:

Swift Objective-C // begin with Valentine's Day, 2015 at 9:00am let valentines = cal . date With Era ( 1 , year : 2015 , month : 2 , day : 14 , hour : 9 , minute : 0 , second : 0 , nanosecond : 0 ) ! // to find the last day of the month, we'll set up a date components instance with // `day` set to 31: let components = NSDate Components () components . day = 31 NSDate * valentines = [ calendar date With Era : 1 year : 2015 month : 2 day : 14 hour : 9 minute : 0 second : 0 nanosecond : 0 ]; NSDate Components * components = [[ NSDate Components alloc ] init ]; components . day = 31 ;

Using strict matching will find the next day that matches 31 , skipping into March to do so:

Swift Objective-C calendar . next Date After Date ( valentines , matching Components : components , options : . Match Strictly ) // Mar 31, 2015, 12:00 AM NSDate * date = [ calendar next Date After Date : valentines matching Components : components options : NSCalendar Match Strictly ]; // Mar 31, 2015, 12:00 AM

Without strict matching, next Date After Date will stop when it hits the end of February before finding a match—recall that the highest unit specified was the day, so the search will only continue within the next highest unit, the month. At that point, the option you’ve provided will determine the returned date. For example, using .Match Next Time will pick the next possible day:

Swift Objective-C calendar . next Date After Date ( valentines , matching Components : components , options : . Match Next Time ) // Mar 1, 2015, 12:00 AM date = [ calendar next Date After Date : valentines matching Components : components options : NSCalendar Match Next Time ]; // Mar 1, 2015, 12:00 AM

Similarly, using .Match Next Time Preserving Smaller Units will pick the next day, but will also preserve all the units smaller than the given NSCalendar Unit Day :

Swift Objective-C calendar . next Date After Date ( valentines , matching Components : components , options : . Match Next Time Preserving Smaller Units ) // Mar 1, 2015, 9:00 AM date = [ calendar next Date After Date : valentines matching Components : components options : NSCalendar Match Next Time Preserving Smaller Units ]; // Mar 1, 2015, 9:00 AM

And finally, using .Match Previous Time Preserving Smaller Units will resolve the missing date by going the other direction, choosing the first possible previous day, again preserving the smaller units:

Swift Objective-C calendar . next Date After Date ( valentines , matching Components : components , options : . Match Previous Time Preserving Smaller Units ) // Feb 28, 2015, 9:00 AM date = [ calendar next Date After Date : valentines matching Components : components options : NSCalendar Match Previous Time Preserving Smaller Units ]; // Feb 28, 2015, 9:00 AM

Besides the NDate Components version shown here, it’s worth noting that next Date After Date has two other variations:

Swift Objective-C // matching a particular calendar unit cal . next Date After Date ( valentines , matching Unit : . Calendar Unit Day , value : 31 , options : . Match Strictly ) // March 31, 2015, 12:00 AM // matching an hour, minute, and second cal . next Date After Date ( valentines , matching Hour : 15 , minute : 30 , second : 0 , options : . Match Next Time ) // Feb 14, 2015, 3:30 PM // matching a particular calendar unit date = [ calendar next Date After Date : valentines matching Unit : NSCalendar Unit Day value : 31 options : NSCalendar Match Strictly ]; // March 31, 2015, 12:00 AM // matching an hour, minute, and second date = [ calendar next Date After Date : valentines matching Hour : 15 minute : 30 second : 0 options : NSCalendar Match Next Time ]; // Feb 14, 2015, 3:30 PM

Enumerating Interpolated Dates

Rather than using next Date After Date iteratively, NSCalendar provides an API for enumerating dates with the same semantics. enumerate Dates Starting After Date(_:matching Components:options:using Block:) computes the dates that match the given set of components and options, calling the provided closure with each date in turn. The closure can set the stop parameter to true , thereby stopping the enumeration.

Putting this new NSCalendar Options knowledge to use, here’s one way to list the last fifty leap days:

Swift Objective-C let leap Year Components = NSDate Components () leap Year Components . month = 2 leap Year Components . day = 29 var date Count = 0 cal . enumerate Dates Starting After Date ( NSDate (), matching Components : leap Year Components , options : . Match Strictly | . Search Backwards ) { ( date : NSDate ! , exact Match : Bool , stop : Unsafe Mutable Pointer < Obj CBool > ) in println ( date ) if ++ date Count == 50 { // .memory gets at the value of an Unsafe Mutable Pointer stop . memory = true } } // 2012-02-29 05:00:00 +0000 // 2008-02-29 05:00:00 +0000 // 2004-02-29 05:00:00 +0000 // 2000-02-29 05:00:00 +0000 … NSDate Components * leap Year Components = [[ NSDate Components alloc ] init ]; leap Year Components . month = 2 ; leap Year Components . day = 29 ; __block int date Count = 0 ; [ calendar enumerate Dates Starting After Date :[ NSDate date ] matching Components: leap Year Components options: NSCalendar Match Strictly | NSCalendar Search Backwards using Block: ^ ( NSDate * date , BOOL exact Match , BOOL * stop ) { NSLog ( @"%@" , date ); if ( ++ date Count == 50 ) { * stop = YES ; } }]; // 2012-02-29 05:00:00 +0000 // 2008-02-29 05:00:00 +0000 // 2004-02-29 05:00:00 +0000 // 2000-02-29 05:00:00 +0000 …

Working for the Weekend

If you’re always looking forward to the weekend, look no further than our final two NSCalendar methods:

next Weekend Start Date(_:interval:options:after Date) : Returns the starting date and length of the next weekend by reference via the first two parameters. This method will return false if the current calendar or locale doesn’t support weekends. The only relevant option here is .Search Backwards . (See below for an example.)

: Returns the starting date and length of the next weekend by reference via the first two parameters. This method will return false if the current calendar or locale doesn’t support weekends. The only relevant option here is . (See below for an example.) range Of Weekend Start Date(_:interval:containing Date) : Returns the starting date and length of the weekend containing the given date by reference via the first two parameters. This method returns false if the given date is not in fact on a weekend or if the current calendar or locale doesn’t support weekends.

Localized Calendar Symbols

As if all that new functionality wasn’t enough, NSCalendar also provides access to a full set of properly localized calendar symbols, making possible quick access to the names of months, days of the week, and more. Each group of symbols is further enumerated along two axes: (1) the length of the symbol and (2) its use as a standalone noun or as part of a date.

Understanding this second attribute is extremely important for localization, since some languages, Slavic languages in particular, use different noun cases for different contexts. For example, a calendar would need to use one of the standalone Month Symbols variants for its headers, not the month Symbols that are used for formatting specific dates.

For your perusal, here’s a table of all the symbols that are available in NSCalendar —note the different values for standalone symbols in the Russian column:

en_US ru_RU month Symbols January, February, March… января, февраля, марта… short Month Symbols Jan, Feb, Mar… янв., февр., марта… very Short Month Symbols J, F, M, A… Я, Ф, М, А… standalone Month Symbols January, February, March… Январь, Февраль, Март… short Standalone Month Symbols Jan, Feb, Mar… Янв., Февр., Март… very Short Standalone Month Symbols J, F, M, A… Я, Ф, М, А… weekday Symbols Sunday, Monday, Tuesday, Wednesday… воскресенье, понедельник, вторник, среда… short Weekday Symbols Sun, Mon, Tue, Wed… вс, пн, вт, ср… very Short Weekday Symbols S, M, T, W… вс, пн, вт, ср… standalone Weekday Symbols Sunday, Monday, Tuesday, Wednesday… Воскресенье, Понедельник, Вторник, Среда… short Standalone Weekday Symbols Sun, Mon, Tue, Wed… Вс, Пн, Вт, Ср… very Short Standalone Weekday Symbols S, M, T, W… В, П, В, С… AMSymbol AM AM PMSymbol PM PM quarter Symbols 1st quarter, 2nd quarter, 3rd quarter, 4th quarter 1-й квартал, 2-й квартал, 3-й квартал, 4-й квартал short Quarter Symbols Q1, Q2, Q3, Q4 1-й кв., 2-й кв., 3-й кв., 4-й кв. standalone Quarter Symbols 1st quarter, 2nd quarter, 3rd quarter, 4th quarter 1-й квартал, 2-й квартал, 3-й квартал, 4-й квартал short Standalone Quarter Symbols Q1, Q2, Q3, Q4 1-й кв., 2-й кв., 3-й кв., 4-й кв. era Symbols BC, AD до н. э., н. э. long Era Symbols Before Christ, Anno Domini до н.э., н.э.

Note: These same collections are also available via NSDate Formatter .

Your Weekly Swiftification

It’s becoming something of a feature here at NSHipster to close with a slightly Swift-ified version of the discussed API. Even in this brand-new set of NSCalendar APIs, there are some sharp edges to be rounded off, replacing Unsafe Mutable Pointer parameters with more idiomatic tuple return values.

With a useful set of NSCalendar extensions (gist here), the component accessing and weekend finding methods can be used without in-out variables. For example, getting individual date components from a date is much simpler:

// built-in var hour = 0 var minute = 0 calendar . get Hour ( & hour , minute : & minute , second : nil , nanosecond : nil , from Date : NSDate ()) // swiftified let ( hour , minute , _ , _ ) = calendar . get Time From Date ( NSDate ())

As is fetching the range of the next weekend:

// built-in var start Date : NSDate ? var interval : NSTime Interval = 0 let success = cal . next Weekend Start Date ( & start Date , interval : & interval , options : nil , after Date : NSDate ()) if success , let start Date = start Date { println ( "start: \( start Date ) , interval: \( interval ) " ) } // swiftified if let next Weekend = cal . next Weekend After Date ( NSDate ()) { println ( "start: \( next Weekend . start Date ) , interval: \( next Weekend . interval ) " ) }