Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Dates and Times in Cocoa (CocoaConf Columbus 2014)

Dates and Times in Cocoa (CocoaConf Columbus 2014)

Jeff Kelley

August 08, 2014
Tweet

More Decks by Jeff Kelley

Other Decks in Technology

Transcript

  1. Dates and Times in
    Cocoa
    Jeff Kelley (@SlaunchaMan)
    CocoaConf, August 8th, 2014

    View full-size slide

  2. 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

    View full-size slide

  3. NSDate
    typedef double NSTimeInterval;
    !
    #define NSTimeIntervalSince1970 978307200.0
    !
    @interface NSDate : NSObject
    !
    - (NSTimeInterval)timeIntervalSinceReferenceDate;
    !
    @end

    View full-size slide

  4. Let’s Explore NSDate
    unsigned int ivarCount;
    Ivar *ivars = class_copyIvarList([NSDate class], &ivarCount);
    !
    for (unsigned int i = 0; i < ivarCount; i++) {
    NSLog(@"Ivar: %s Type: %s",
    ivar_getName(ivars[i]),
    ivar_getTypeEncoding(ivars[i]));
    }
    !
    free(ivars);
    !
    !
    Output:
    Program ended with exit code: 0
    !
    …what?

    View full-size slide

  5. 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

    View full-size slide

  6. 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

    View full-size slide

  7. 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.

    View full-size slide

  8. 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

    View full-size slide

  9. Dumb Calendar Trick
    strax:~ jeff$ cal
    February 2014
    Su Mo Tu We Th Fr Sa
    1
    2 3 4 5 6 7 8
    9 10 11 12 13 14 15
    16 17 18 19 20 21 22
    23 24 25 26 27 28
    !

    View full-size slide

  10. Dumb Calendar Trick
    strax:~ jeff$ cal 9 1752
    September 1752
    Su Mo Tu We Th Fr Sa
    1 2 14 15 16
    17 18 19 20 21 22 23
    24 25 26 27 28 29 30
    !
    !
    !

    View full-size slide

  11. Dumb Calendar Trick
    strax:~ jeff$ cal 1752
    1752
    !
    January February March
    Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa
    1 2 3 4 1 1 2 3 4 5 6 7
    5 6 7 8 9 10 11 2 3 4 5 6 7 8 8 9 10 11 12 13 14
    12 13 14 15 16 17 18 9 10 11 12 13 14 15 15 16 17 18 19 20 21
    19 20 21 22 23 24 25 16 17 18 19 20 21 22 22 23 24 25 26 27 28
    26 27 28 29 30 31 23 24 25 26 27 28 29 29 30 31
    April May June
    Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa
    1 2 3 4 1 2 1 2 3 4 5 6
    5 6 7 8 9 10 11 3 4 5 6 7 8 9 7 8 9 10 11 12 13
    12 13 14 15 16 17 18 10 11 12 13 14 15 16 14 15 16 17 18 19 20
    19 20 21 22 23 24 25 17 18 19 20 21 22 23 21 22 23 24 25 26 27
    26 27 28 29 30 24 25 26 27 28 29 30 28 29 30
    31
    July August September
    Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa
    1 2 3 4 1 1 2 14 15 16
    5 6 7 8 9 10 11 2 3 4 5 6 7 8 17 18 19 20 21 22 23
    12 13 14 15 16 17 18 9 10 11 12 13 14 15 24 25 26 27 28 29 30
    19 20 21 22 23 24 25 16 17 18 19 20 21 22
    26 27 28 29 30 31 23 24 25 26 27 28 29
    30 31
    October November December
    Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa
    1 2 3 4 5 6 7 1 2 3 4 1 2
    8 9 10 11 12 13 14 5 6 7 8 9 10 11 3 4 5 6 7 8 9
    15 16 17 18 19 20 21 12 13 14 15 16 17 18 10 11 12 13 14 15 16
    22 23 24 25 26 27 28 19 20 21 22 23 24 25 17 18 19 20 21 22 23
    29 30 31 26 27 28 29 30 24 25 26 27 28 29 30
    31

    View full-size slide

  12. Creating a Date From
    Components
    NSCalendar *gregorianCalendar =
    [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
    !
    NSDateComponents *dateComponents = [[NSDateComponents alloc] init];
    !
    dateComponents.year = 2007;
    dateComponents.month = 1;
    dateComponents.day = 9;
    !
    dateComponents.hour = 9;
    dateComponents.minute = 41;
    !
    dateComponents.timeZone =
    [NSTimeZone timeZoneWithName:@"America/Los_Angeles"];
    !
    NSDate *introductionDate =
    [gregorianCalendar dateFromComponents:dateComponents];

    View full-size slide

  13. Creating a Date From
    Components
    let calendar = NSCalendar(calendarIdentifier: NSGregorianCalendar)
    !
    var components = NSDateComponents()
    !
    components.year = 1969
    components.month = 7
    components.day = 20
    !
    components.hour = 15
    components.minute = 17
    !
    components.timeZone = NSTimeZone(name: "US/Eastern")
    !
    var date = calendar.dateFromComponents(components)

    View full-size slide

  14. Creating a Date From
    Components
    NSCalendar *gregorianCalendar =
    [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
    !
    NSDateComponents *dateComponents = [[NSDateComponents alloc] init];
    !
    dateComponents.year = 1986;
    dateComponents.month = 2;
    dateComponents.day = 9;
    !
    NSDate *lastTime = [gregorianCalendar dateFromComponents:dateComponents];
    !
    NSDateComponents *nextComponents = [[NSDateComponents alloc] init];
    !
    nextComponents.year = 2061;
    nextComponents.month = 7;
    nextComponents.day = 28;
    !
    NSDate *nextTime = [gregorianCalendar dateFromComponents:dateComponents];

    View full-size slide

  15. Creating a Date From
    Components
    let calendar = NSCalendar(calendarIdentifier: NSGregorianCalendar)
    !
    var components = NSDateComponents()
    !
    components.year = 1977
    components.month = 5
    components.day = 25
    !
    var date = calendar.dateFromComponents(components)

    View full-size slide

  16. Extracting Components From a
    Date
    NSDateComponents *components =
    [gregorianCalendar components:NSWeekdayCalendarUnit
    fromDate:introductionDate];
    !
    NSLog(@"%d", components.weekday);
    !
    !
    Output: 3

    View full-size slide

  17. Calendar Units
    typedef NS_OPTIONS(NSUInteger, NSCalendarUnit) {
    NSCalendarUnitEra,
    NSCalendarUnitYear,
    NSCalendarUnitMonth,
    NSCalendarUnitDay,
    NSCalendarUnitHour,
    NSCalendarUnitMinute,
    NSCalendarUnitSecond,
    NSCalendarUnitWeekday,
    NSCalendarUnitWeekdayOrdinal,
    NSCalendarUnitQuarter,
    NSCalendarUnitWeekOfMonth,
    NSCalendarUnitWeekOfYear,
    NSCalendarUnitYearForWeekOfYear,
    NSCalendarUnitNanosecond,
    NSCalendarUnitCalendar,
    NSCalendarUnitTimeZone
    };

    View full-size slide

  18. Calendar Units
    struct NSCalendarUnit : RawOptionSet {
    init(_ value: UInt)
    var value: UInt
    static var CalendarUnitEra: NSCalendarUnit { get }
    static var CalendarUnitYear: NSCalendarUnit { get }
    static var CalendarUnitMonth: NSCalendarUnit { get }
    static var CalendarUnitDay: NSCalendarUnit { get }
    static var CalendarUnitHour: NSCalendarUnit { get }
    static var CalendarUnitMinute: NSCalendarUnit { get }
    static var CalendarUnitSecond: NSCalendarUnit { get }
    static var CalendarUnitWeekday: NSCalendarUnit { get }
    static var CalendarUnitWeekdayOrdinal: NSCalendarUnit { get }
    static var CalendarUnitQuarter: NSCalendarUnit { get }
    static var CalendarUnitWeekOfMonth: NSCalendarUnit { get }
    static var CalendarUnitWeekOfYear: NSCalendarUnit { get }
    static var CalendarUnitYearForWeekOfYear: NSCalendarUnit { get }
    static var CalendarUnitNanosecond: NSCalendarUnit { get }
    static var CalendarUnitCalendar: NSCalendarUnit { get }
    static var CalendarUnitTimeZone: NSCalendarUnit { get }
    }

    View full-size slide

  19. 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

    View full-size slide

  20. Comparing Dates
    • Use NSDate to see which of two dates occurred
    first:


    NSDate *date1, *date2;


    if ([date1 compare:date2] == NSOrderedAscending) {

    // date1 happened first

    }

    View full-size slide

  21. Comparing Dates
    • Use NSCalendar and NSDateComponents to
    perform relative arithmetic on dates:


    NSDate *now = [NSDate date];


    NSCalendar *gregorianCalendar =

    [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];


    NSDateComponents *diff = [[NSDateComponents alloc] init];

    diff.day = 7;


    NSDate *aWeekFromNow =

    [gregorianCalendar dateByAddingComponents:diff

    toDate:now

    options:kNilOptions];

    View full-size slide

  22. Never Do This
    NSDate *now = [NSDate date];
    !
    NSTimeInterval secondsInADay = 24.0 * 60.0 * 60.0;
    !
    NSDate *aWeekFromNow = [now dateByAddingTimeInterval:secondsInADay * 7.0];

    View full-size slide

  23. Because You’ll End Up Doing
    This
    NSDate *now = [NSDate date];
    !
    NSTimeInterval secondsInADay = 24.0 * 60.0 * 60.0;
    !
    NSDate *aWeekFromNow = [now dateByAddingTimeInterval:secondsInADay * 7.0];
    !
    NSDate *dstTransition =
    [[NSTimeZone defaultTimeZone] nextDaylightSavingTimeTransitionAfterDate:now];
    !
    if ([dstTransition compare:aWeekFromNow] == NSOrderedAscending) {
    if ([[NSTimeZone defaultTimeZone] isDaylightSavingTimeForDate:now]) {
    aWeekFromNow = [aWeekFromNow dateByAddingTimeInterval:60.0 * 60.0];
    }
    else {
    aWeekFromNow = [aWeekFromNow dateByAddingTimeInterval:-60.0 * 60.0];
    }
    }

    View full-size slide

  24. 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

    View full-size slide

  25. 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 1010369 days since the Roman empire was founded.

    View full-size slide

  26. 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 2766 years, 109 days since the Roman empire was founded.

    View full-size slide

  27. 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, 2766 years, 109 days since the Roman empire was founded.

    View full-size slide

  28. 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

    View full-size slide

  29. 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

    View full-size slide

  30. Indiana Time Zones
    • America/Indiana/Indianapolis
    • America/Indiana/Vincennes
    • America/Indiana/Winamac
    • America/Indiana/Marengo
    • America/Indiana/Petersburg
    • America/Indiana/Vevay
    • America/Indiana/Tell_City
    • America/Indiana/Knox

    View full-size slide

  31. 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!


    NSDate *timeZoneChange = [tz nextDaylightSavingTimeTransition];

    View full-size slide

  32. 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

    View full-size slide

  33. More NSDateFormatter
    NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:0.0];
    !
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    formatter.dateStyle = NSDateFormatterFullStyle;
    formatter.timeStyle = NSDateFormatterFullStyle;
    formatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
    !
    NSLog(@"%@: %@",
    [formatter.locale localeIdentifier],
    [formatter stringFromDate:date]);
    !
    !
    Output:
    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

    View full-size slide

  34. More NSDateFormatter
    NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:0.0];
    !
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    formatter.dateStyle = NSDateFormatterLongStyle;
    formatter.timeStyle = NSDateFormatterLongStyle;
    formatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
    !
    NSLog(@"%@: %@",
    [formatter.locale localeIdentifier],
    [formatter stringFromDate:date]);
    !
    !
    Output:
    en_US: January 1, 2001 at 12:00:00 AM GMT
    en_GB: 1 January 2001 00:00:00 GMT
    fr_FR: 1 janvier 2001 00:00:00 UTC

    View full-size slide

  35. More NSDateFormatter
    NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:0.0];
    !
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    formatter.dateStyle = NSDateFormatterMediumStyle;
    formatter.timeStyle = NSDateFormatterMediumStyle;
    formatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
    !
    NSLog(@"%@: %@",
    [formatter.locale localeIdentifier],
    [formatter stringFromDate:date]);
    !
    !
    Output:
    en_US: Jan 1, 2001, 12:00:00 AM
    en_GB: 1 Jan 2001 00:00:00
    fr_FR: 1 janv. 2001 00:00:00

    View full-size slide

  36. More NSDateFormatter
    NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:0.0];
    !
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    formatter.dateStyle = NSDateFormatterShortStyle;
    formatter.timeStyle = NSDateFormatterShortStyle;
    formatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
    !
    NSLog(@"%@: %@",
    [formatter.locale localeIdentifier],
    [formatter stringFromDate:date]);
    !
    !
    Output:
    en_US: 1/1/01, 12:00 AM
    en_GB: 01/01/2001 00:00
    fr_FR: 01/01/2001 00:00

    View full-size slide

  37. Never Do This
    int month, day, year;
    !
    NSString *poorlyFormattedDate =
    [NSString stringWithFormat:@"%d/%d/%d",
    month, day, year];

    View full-size slide

  38. Because You’ll End Up With This
    int month, day, year;
    !
    NSString *poorlyFormattedDate;
    !
    if ([[[NSLocale systemLocale] identifier] isEqualToString:@"en_US"]) {
    poorlyFormattedDate =
    [NSString stringWithFormat:@"%d/%d/%d",
    month, day, year];
    }
    else {
    poorlyFormattedDate =
    [NSString stringWithFormat:@"%d/%d/%d",
    day, month, year];
    }

    View full-size slide

  39. More NSDateFormatter
    • Expensive to create
    • Create one, then re-use as needed.
    • You can specify your own format
    • Easy, cheap localization + internationalization

    View full-size slide

  40. Custom NSDateFormatter
    Formats
    NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:0.0];
    !
    NSDateFormatter *rfc3339DateFormatter = [[NSDateFormatter alloc] init];
    NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
    !
    [rfc3339DateFormatter setLocale:enUSPOSIXLocale];
    [rfc3339DateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"];
    [rfc3339DateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
    !
    NSLog(@"Date: %@", [rfc3339DateFormatter stringFromDate:date]);
    !
    !
    Output:
    Date: 2001-01-01T00:00:00Z

    View full-size slide

  41. 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

    View full-size slide

  42. 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

    View full-size slide

  43. New Formatters in iOS 8
    • NSDateComponentsFormatter

    var componentsFormatter = NSDateComponentsFormatter()
    !
    var components = NSDateComponents()
    components.hour = 1
    !
    var formattedComponents =
    componentsFormatter.stringFromDateComponents(components)

    View full-size slide

  44. New Formatters in iOS 8
    • NSDateIntervalFormatter

    var intervalFormatter = NSDateIntervalFormatter()
    !
    var currentDate = NSDate()
    var future =
    currentDate.dateByAddingTimeInterval(10_000)
    !
    var formattedInterval =
    intervalFormatter.stringFromDate(currentDate, toDate:
    future)

    View full-size slide

  45. 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?

    View full-size slide

  46. Computus
    Source: Wikipedia

    View full-size slide

  47. Objective-Computus
    - (NSDateComponents *)dateComponentsForEasterInYear:(NSInteger)year
    {
    NSDateComponents *components = [[NSDateComponents alloc] init];
    components.year = year;
    // Source: http://en.wikipedia.org/wiki/Computus#Anonymous_Gregorian_algorithm
    NSInteger a = year % 19;
    NSInteger b = year / 100;
    NSInteger c = year % 100;
    NSInteger d = b / 4;
    NSInteger e = b % 4;
    NSInteger f = (b + 8) / 25;
    NSInteger g = (b - f + 1) / 3;
    NSInteger h = ((19 * a) + b - d - g + 15) % 30;
    NSInteger i = c / 4;
    NSInteger k = c % 4;
    NSInteger L = (32 + (2 * e) + (2 * i) - h - k) % 7;
    NSInteger m = (a + (11 * h) + (22 * L)) / 451;
    components.month = (h + L - (7 * m) + 114) / 31;
    components.day = ((h + L - (7 * m) + 114) % 31) + 1;
    return components;
    }

    View full-size slide

  48. 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

    View full-size slide

  49. Swift-Computus
    func dateComponentsForEaster(year: Int) -> NSDateComponents {
    var components = NSDateComponents();
    components.year = year
    let a = year % 19
    let b = year / 100
    let c = year % 100
    let d = b / 4
    let e = b % 4
    let f = (b + 8) / 25
    let g = (b - f + 1) / 3
    let h = ((19 * a) + b - d - g + 15) % 30
    let i = c / 4
    let k = c % 4
    let L = (32 + (2 * e) + (2 * i) - h - k) % 7
    let m = (a + (11 * h) + (22 * L)) / 451
    !
    components.month = (h + L - (7 * m) + 114) / 31;
    components.day = ((h + L - (7 * m) + 114) % 31) + 1;
    return components
    }
    !
    dateComponentsForEaster(2015).month // 4
    dateComponentsForEaster(2015).day // 5

    View full-size slide

  50. Further Reading
    • Date and Time Programming Guide
    • NSHipster: NSDateComponents
    • NSHipster: NSFormatter
    • NSHipster: NSDataDetector

    View full-size slide