Why are dates and times so hard? • NSDate does not represent what you think it does • Web services using other date libraries • Time zones exist • Daylight Savings Time exists
What is an NSDate? • Basically just a double • A single moment in time, representing the number of seconds since a reference date • By default, the first moment of 2001
Where’s All That Date Stuff? • NSCalendar takes care of the time and date APIs that are more than just a number: • Splitting a date into month, day, year, etc. • Creating an NSDate from those components
NSCalendar Calendars encapsulate information about systems of reckoning time in which the beginning, length, and divisions of a year are defined. They provide information about the calendar and support for calendrical computations such as determining the range of a given calendrical unit and adding units to a given absolute time.
NSCalendar • We use the Gregorian calendar, as do most • Japanese, Buddhist calendars supported on iOS • Split dates into calendrical components, can perform arithmetic operations on them
Dates vs. Date Components • A date knows when it is relative to another time, but not where it is on a calendar • Date components know where they are on a calendar, but not relative to a moment in time
Calendrical Calculations • Use NSCalendar to get the NSDateComponents that represent the difference between two dates • The resulting components are different based on what you ask for
Calendrical Calculations NSDate *now = [NSDate date]; ! NSDateComponents *romeFoundedComponents = [[NSDateComponents alloc] init]; romeFoundedComponents.era = 0; romeFoundedComponents.year = 753; romeFoundedComponents.month = 4; romeFoundedComponents.day = 21; ! NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; ! NSDate *romeFoundedDate = [calendar dateFromComponents:romeFoundedComponents]; ! NSDateComponents *difference = [calendar components:(NSDayCalendarUnit) fromDate:romeFoundedDate toDate:now options:kNilOptions]; ! NSLog(@"It has been %d days since the Roman empire was founded.", difference.day); ! ! Output: It has been 1010193 days since the Roman empire was founded.
Calendrical Calculations NSDate *now = [NSDate date]; ! NSDateComponents *romeFoundedComponents = [[NSDateComponents alloc] init]; romeFoundedComponents.era = 0; romeFoundedComponents.year = 753; romeFoundedComponents.month = 4; romeFoundedComponents.day = 21; ! NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; ! NSDate *romeFoundedDate = [calendar dateFromComponents:romeFoundedComponents]; ! NSDateComponents *difference = [calendar components:(NSDayCalendarUnit|NSYearCalendarUnit) fromDate:romeFoundedDate toDate:now options:kNilOptions]; ! NSLog(@"It has been %d years, %d days since the Roman empire was founded.", difference.year, difference.day); ! ! Output: It has been 2765 years, 298 days since the Roman empire was founded.
Calendrical Calculations NSDate *now = [NSDate date]; ! NSDateComponents *romeFoundedComponents = [[NSDateComponents alloc] init]; romeFoundedComponents.era = 0; romeFoundedComponents.year = 753; romeFoundedComponents.month = 4; romeFoundedComponents.day = 21; ! NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; ! NSDate *romeFoundedDate = [calendar dateFromComponents:romeFoundedComponents]; ! NSDateComponents *difference = [calendar components:(NSDayCalendarUnit|NSYearCalendarUnit|NSEraCalendarUnit) fromDate:romeFoundedDate toDate:now options:kNilOptions]; ! NSLog(@"It has been %d era, %d years, %d days since the Roman empire was founded.", difference.era, difference.year, difference.day); ! ! Output: It has been 1 era, 2765 years, 298 days since the Roman empire was founded.
Where’s All That Date Stuff? • NSTimeZone handles time zones (obviously) • You can get the user’s current time zone or use an ID like “America/Detroit” • Need to get a time zone for a geolocation? https://developers.google.com/maps/ documentation/timezone/ • Time zones are a pain in the ass always
NSTimeZone • Represent “geopolitical regions” • America/Detroit • America/Indiana/Petersburg • Indiana is weird about Daylight Savings Time • Represent offsets from GMT • EST is -5 hours, EDT is -4
NSTimeZone • NSTimeZone objects created with an ID know about Daylight Savings Time:
NSTimeZone *tz = [NSTimeZone timeZoneWithName:@"America/Detroit"]; BOOL isCurrentlyDST = [tz isDaylightSavingTimeForDate:[NSDate date]]; • This may change during the time your application is open!
Where’s All That Date Stuff? • NSDateFormatter takes a date and turns it into a string • Since a date also includes time, this is also how you print a time of day • Takes advantage of user’s locale to print it properly • en_US: Monday, January 1, 2001 at 12:00:00 AM GMT • en_GB: Monday, 1 January 2001 00:00:00 GMT • fr_FR: lundi 1 janvier 2001 00:00:00 UTC
More NSDateFormatter • Expensive to create • Create one, then re-use as needed. • You can specify your own format • Easy, cheap localization + internationalization
API Shortcomings • Limited to the planet Earth • Space travelers will need to account for relativity, position relative to Earth, etc. Talk to NASA; I bet they have a library. • Time Zones • Always store your timestamps relative to GMT, then convert to your user’s time zone
Relative Dates • You can use date components to populate labels with text like “2 hours ago,” “30 seconds ago,” etc. like Mail.app • Or just use @Mattt’s FormatterKit project
Easter • How hard can all this stuff really be, anyway? • Let’s write a method to find the date components for Easter given a year. • Easter falls on the first Sunday after the full moon following the March equinox. • Easy, right?
Objective-Computus NSDateComponents *easterThisYear = [self dateComponentsForEasterInYear:2014]; ! NSLog(@"Easter this year is on day %d of month %d", easterThisYear.day, easterThisYear.month); ! ! Output: Easter this year is on day 20 of month 4