Friday, September 3, 2010

How to calculate Easter Day in iPhone OS

Recently, I have to implement the calculation of Easter Day.

Here is what I got

Easter Day is always the Sunday after the full moon that occurs after the spring equinox on March 21. This full moon may happen on any date between March 21 and April 18 inclusive. If the full moon falls on a Sunday, Easter Day if the Sunday following. But Easter Day cannot be earlier than March 22 or later than April 25.

To find the the full moon day, I have used the NSChineseCalendar in iOS 4.

and my implementation is here
eastercal.m Select all

// eastercal.m
#import <Foundation/Foundation.h>
#include <stdio.h>

int main( int argc, char *argv[] )
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSDate *today = [NSDate date]; // today
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSUInteger unitFlags = NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit;
NSDateComponents *gregorianComps = [gregorian components:unitFlags fromDate:today];
if (argc==2) {
[gregorianComps setYear:atoi(argv[1])];
[gregorianComps setDay:21];
[gregorianComps setMonth:3];
NSDate *easterStartDate = [gregorian dateFromComponents:gregorianComps]; //March 21 for the year

NSCalendar *chinese = [[NSCalendar alloc] initWithCalendarIdentifier:NSChineseCalendar];
// convert from gregorian calendar to chinese calendar
NSDateComponents *chineseComps = [chinese components:unitFlags fromDate:easterStartDate];
NSDate *easterStartDateChineseDate = [chinese dateFromComponents:chineseComps];

NSDate *easterStartDateChineseDateTemp = easterStartDateChineseDate;
if ([chineseComps day] >=15) { // 15 is the full month day in Chinese Calendar
NSDateComponents *offsetComponents = [[NSDateComponents alloc] init];
[offsetComponents setMonth:1]; // set the next month
easterStartDateChineseDateTemp = [chinese dateByAddingComponents:offsetComponents toDate:easterStartDateChineseDate options:0];
NSDateComponents *dayComponents = [chinese components:NSDayCalendarUnit fromDate:easterStartDateChineseDateTemp];
NSDateComponents *componentsToAdd = [[NSDateComponents alloc] init];
[componentsToAdd setDay: (15 - [dayComponents day])];
// find the next full month date
NSDate *springEquinoChineseDate = [chinese dateByAddingComponents:componentsToAdd toDate:easterStartDateChineseDateTemp options:0];

NSDateComponents *springEquinoChineseComps = [chinese components:unitFlags fromDate:springEquinoChineseDate];

NSDateComponents *diffComps = [chinese components:NSDayCalendarUnit fromDate:easterStartDateChineseDate toDate:springEquinoChineseDate options:0];
NSInteger diffDays = [diffComps day];

// calculate the days difference from the March 21 to the next full month day
NSLog(@"diffDays is %ld",diffDays);

NSDateComponents *daysToAdd = [[NSDateComponents alloc] init];
[daysToAdd setDay:diffDays];
NSDate *springEquinoGregorianDate = [gregorian dateByAddingComponents:daysToAdd toDate:easterStartDate options:0];

// convert the next full month date from ChineseDate to GregorianComps
NSDateComponents *springEquinoGregorianComps = [gregorian components:unitFlags fromDate:springEquinoGregorianDate];

NSLog(@"springEquinoGregorian is %ld %ld %ld",[springEquinoGregorianComps year], [springEquinoGregorianComps month], [springEquinoGregorianComps day]);

int weekday = [springEquinoGregorianComps weekday];
NSDate *easterSundayGregorianDateTemp = springEquinoGregorianDate;
NSDateComponents *offsetGregorianComponents = [[NSDateComponents alloc] init];
if (weekday == 7) {
[offsetGregorianComponents setWeek:2]; // If the full moon falls on a Sunday, Easter Day if the Sunday following
else {
[offsetGregorianComponents setWeek:1];
easterSundayGregorianDateTemp = [gregorian dateByAddingComponents:offsetGregorianComponents toDate:springEquinoGregorianDate options:0];
NSDateComponents *weekdayComponents = [gregorian components:NSWeekdayCalendarUnit fromDate:easterSundayGregorianDateTemp];
NSDateComponents *componentsToSubtract = [[NSDateComponents alloc] init];
[componentsToSubtract setDay: 0 - ([weekdayComponents weekday] - 1)];
NSDate *easterDate = [gregorian dateByAddingComponents:componentsToSubtract toDate:easterSundayGregorianDateTemp options:0];

NSDateComponents *easterComps = [gregorian components:unitFlags fromDate:easterDate];
NSLog(@"Easter Date is %d %d %d",[easterComps year], [easterComps month], [easterComps day]);
[pool drain];
return 0;

This code does not run on Mac OS 10.5 or iOS 3 as the ChineseCalendar was not implemented.
It will only run on Mac OS 10.6 or iOS 4

iPhone-4:root# gcc -I/var/toolchain/sys30/usr/include -F/var/toolchain/sys30/System/Library/Frameworks -L/var/toolchain/sys30/usr/lib -framework Foundation eastercal.m -o eastercal
iPhone-4:root# ldid -S eastercal
iPhone-4:root# ./eastercal 2011
eastercal[3005:107] diffDays is 27
eastercal[3005:107] springEquinoGregorian is 2011 4 17
eastercal[3005:107] Easter Date is 2011 4 24