Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

dejo

Moderator emeritus
Original poster
Sep 2, 2004
15,982
452
The Centennial State
Anybody already know how to determine, given a particular year, the third Monday in January, or other similar "relative" dates?

I'm looking to built a method along the lines of:
Code:
- (int)getMLKDayForYear:(int)year;

And when called for this year:
Code:
[self getMLKDayForYear:2014];

would return 20.

In the meantime, I am going to play around with NSDateComponents etc. to see what I can figure out, but if someone has a quick answer, that would be great!
 

chown33

Moderator
Staff member
Aug 9, 2009
10,750
8,422
A sea of green
If you don't find anything else, you could use simple arithmetic and logic.

For any month, there are no more than 7 possible mappings of day-of-month to weekday. That is, any month can start on any of the 7 days of the week. For example, January can start on Sunday, Monday, Tuesday, etc. Thus, there are only 7 possible dates in January that could match "third Monday in January".

The same is true for every month, and no month is longer than 31 days. So if you had an archetypal "month" of 31 days, and then make a prototype "month" for each possible starting day-of-week, you'll have 7 prototype "months" you can use to calculate with. Then you just ask NSCalendar what day of the week is the first of the month for the year of interest, and this selects one of the 7 prototypes. You then do the calculations on that month, keeping track of the actual days in the real month (i.e. if the real month is Feb, then there's never a Feb 30 or 31, even though your prototype month has 31 days).

It's not really necessary to have "month" objects, since the calculations basically involve base-7 calculations and modular addition. Base-7 because the "tens place" in a base-7 number represents the number of weeks, and the "units place" represents the day of the week. If the month starts on Sunday, that's a base-7 value of 00. If it starts on Monday, that's a base-7 value of 01. So you then add 21(base-7) to the starting day of the month, and convert to binary to get the date. 21(base-7) represents Monday (day 1) of the third week (20). It's not 31, because 30 in base-7 is the start of the fourth group of 7, not the third.

I hope that's not too confusing. It's been years since I had to do anything for this calculation, and it's probably written in something like 6800 assembly language or Forth, assuming I could even find it again (line-printer listing or 8-inch floppy disk).
 

dejo

Moderator emeritus
Original poster
Sep 2, 2004
15,982
452
The Centennial State
Thanks, chown33. That helps confirm that I think I'm heading down the right path. At first I was thinking of kinda brute-forcing it with a simple lookup. So, if New Year's Day is a Sunday for the year, the third Monday (MLK Day) is the 16th. If it's a Monday, the third Monday is the 17th. If it's a Tuesday, the third Monday is the 21st, etc.

But since I'm going to need to do more dates than just MLK Day (Washington's Birthday, Memorial Day, etc.) with "trickier" relative dates, I think I may rework my method definition to look more like:
Code:
- (int)getDay:(NSString *)referenceString inMonth:(int)month forYear:(int)year
where referenceString could be something like "third" or "last" or, in the case of Fire Fighter's Memorial Day, "before or on the 9th".
 

ArtOfWarfare

macrumors G3
Nov 26, 2007
9,560
6,059
I may be thinking of a Java library, but I was pretty certain this kind of thing was provided in the iOS SDK.
 

dejo

Moderator emeritus
Original poster
Sep 2, 2004
15,982
452
The Centennial State
But since I'm going to need to do more dates than just MLK Day (Washington's Birthday, Memorial Day, etc.) with "trickier" relative dates, I think I may rework my method definition to look more like:
Code:
- (int)getDay:(NSString *)referenceString inMonth:(int)month forYear:(int)year
where referenceString could be something like "third" or "last" or, in the case of Fire Fighter's Memorial Day, "before or on the 9th".

Oops, looks like my method definition is missing a parameter: dayOfWeek.

So, let's try that again:
Code:
- (int)getReferenceString:(NSString *)referString dayOfWeek:(int)weekday inMonth:(int)month forYear:(int)year

So, to get MLK Day for this year, I would call:
Code:
int mlkDay = [self getReferenceString:@"third" dayOfWeek:2 inMonth:1 forYear:2014];

I suppose I could make dayOfWeek and inMonth strings as well if I wanted calls to be more obviously self-documenting. Something like:
Code:
int mlkDay = [self getReferenceString:@"third" dayOfWeek:@"monday" inMonth:@"january" forYear:2014];

but that just seems to introduce more chances for errors (bad inputs).
 

chown33

Moderator
Staff member
Aug 9, 2009
10,750
8,422
A sea of green
I think the answer is the NSCalendar method dateFromComponents: and passing an NSDateComponents that sets the year, month, weekday, and weekdayOrdinal. The dateFromComponents: method returns an NSDate*, from which one can easily obtain the day-of-month.

From the discussion for weekdayOrdinal:
Weekday ordinal units represent the position of the weekday within the next larger calendar unit, such as the month. For example, 2 is the weekday ordinal unit for the second Friday of the month.

Setting the year and month are sufficient to identify a specific month. Setting weekday identifies which weekday one is interested in (e.g. the "Friday" in the "second Friday of the month" example). Setting weekdayOrdinal then identifies the ordinal number of the desired weekday.

I would do some experiments before committing to this.
 

dejo

Moderator emeritus
Original poster
Sep 2, 2004
15,982
452
The Centennial State
I think the answer is the NSCalendar method dateFromComponents: and passing an NSDateComponents that sets the year, month, weekday, and weekdayOrdinal. The dateFromComponents: method returns an NSDate*, from which one can easily obtain the day-of-month.

From the discussion for weekdayOrdinal:
Weekday ordinal units represent the position of the weekday within the next larger calendar unit, such as the month. For example, 2 is the weekday ordinal unit for the second Friday of the month.

Setting the year and month are sufficient to identify a specific month. Setting weekday identifies which weekday one is interested in (e.g. the "Friday" in the "second Friday of the month" example). Setting weekdayOrdinal then identifies the ordinal number of the desired weekday.


I would do some experiments before committing to this.

Excellent! I'm pretty sure this is what I am looking for. Guess I just missed it in the Date and Time Programming Guide, since it's in the "Converting between Dates and Date Components" section. Thanks a ton for your help! :D

Experimenting now...
 

chown33

Moderator
Staff member
Aug 9, 2009
10,750
8,422
A sea of green
Yup, using weekdayOrdinal seems to work! Thanks again, chown33!
Glad it worked.

Now to work on the "last x in y" special case (for example: the last Monday in May).
I vaguely recall that negative ordinals are interpreted relative to the end of the month. So -1 is the last Xday, -2 is the next-to-last Xday, etc. I could be wrong or misremembering.

P.S. Easter should be extra fun!
A solved problem:
http://en.wikipedia.org/wiki/Computus

I distinctly recall implementing Gauss's algorithm in Tiny Basic, on a Z80.
 

dejo

Moderator emeritus
Original poster
Sep 2, 2004
15,982
452
The Centennial State
I vaguely recall that negative ordinals are interpreted relative to the end of the month. So -1 is the last Xday, -2 is the next-to-last Xday, etc. I could be wrong or misremembering.
Wow, you're full of it. "It" being better information than Apple's documentation. -1 works for the last!

A solved problem:
http://en.wikipedia.org/wiki/Computus

I distinctly recall implementing Gauss's algorithm in Tiny Basic, on a Z80.
Yup. Now to find/compose an Objective-C version...
 

dejo

Moderator emeritus
Original poster
Sep 2, 2004
15,982
452
The Centennial State
Doesn't it follow a regular pattern on a different calendar? Will NSCalendar allow you to translate between them?

You can convert between calendars but I don't think it follows a regular pattern on a different calendar. I found some code that used the Chinese calendar for the full moon component but it wasn't accurate so I ended up coding an Objective-C version of the Anonymous Gregorian algorithm:

Code:
- (NSDate *)getAnonymousGregorianEasterForYear:(int)year
{
	NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
	NSDateComponents *easterDateComponents = [[NSDateComponents alloc] init];
	[easterDateComponents setYear:year];
	
	int a = year % 19;
	int b = floor(year / 100);
	int c = year % 100;
	int d = floor(b / 4);
	int e = b % 4;
	int f = floor((b + 8) / 25);
	int g = floor((b - f + 1) / 3);
	int h = (19*a + b - d - g + 15) % 30;
	int i = floor(c / 4);
	int k = c % 4;
	int L = (32 + 2*e + 2*i - h - k) % 7;
	int m = floor((a + 11*h + 22*L) / 451);
	int month = floor((h + L - 7*m + 114) / 31);
	int day = ((h + L - 7*m + 114) % 31) + 1;
	
	[easterDateComponents setMonth:month];
	[easterDateComponents setDay:day];
	
	NSDate *easterDate = [gregorian dateFromComponents:easterDateComponents];
	
	return easterDate;
}
 

Duncan C

macrumors 6502a
Jan 21, 2008
853
0
Northern Virginia
dejo,

I'm late to this party. I didn't discover this thread until just now. There is actually some very good info in Xcode about this sort of thing. Do a search on "Calendrical Calculations". That should lead you to a very informative article in the help system.

NSCalendar is an incredibly powerful class, especially when combined with NSDateComponents, NSDate, and NSDateFormatter.


You can convert between calendars but I don't think it follows a regular pattern on a different calendar. I found some code that used the Chinese calendar for the full moon component but it wasn't accurate so I ended up coding an Objective-C version of the Anonymous Gregorian algorithm:

Code:
- (NSDate *)getAnonymousGregorianEasterForYear:(int)year
{
	NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
	NSDateComponents *easterDateComponents = [[NSDateComponents alloc] init];
	[easterDateComponents setYear:year];
	
	int a = year % 19;
	int b = floor(year / 100);
	int c = year % 100;
	int d = floor(b / 4);
	int e = b % 4;
	int f = floor((b + 8) / 25);
	int g = floor((b - f + 1) / 3);
	int h = (19*a + b - d - g + 15) % 30;
	int i = floor(c / 4);
	int k = c % 4;
	int L = (32 + 2*e + 2*i - h - k) % 7;
	int m = floor((a + 11*h + 22*L) / 451);
	int month = floor((h + L - 7*m + 114) / 31);
	int day = ((h + L - 7*m + 114) % 31) + 1;
	
	[easterDateComponents setMonth:month];
	[easterDateComponents setDay:day];
	
	NSDate *easterDate = [gregorian dateFromComponents:easterDateComponents];
	
	return easterDate;
}
 

chown33

Moderator
Staff member
Aug 9, 2009
10,750
8,422
A sea of green
... I found some code that used the Chinese calendar for the full moon component but it wasn't accurate so I ended up coding an Objective-C version of the Anonymous Gregorian algorithm. ...

The Chinese lunar calendar probably won't match the ecclesiastical lunar calendar used to calculate Easter. I certainly wouldn't expect it to. And there are differences between the Eastern Church (aka Eastern Orthodox) and the Western Church (Roman Catholic and Protestant), regarding the placement of March 21 (Julian vs. Gregorian calendars).

The link I posted on Computus contains links to the ecclesiastical new moon and full moon calculation.

FWIW, this is also why Easter Sunday isn't necessarily the Sunday after Passover, even though the Last Supper is a Passover meal. In short, see the Computus article; the calculation isn't as obvious as it may appear.

Things like this make conversion between decimal, binary, hex, IEEE-754, etc. seem like child's play, and remind me that there are long legacies of history, tradition, and accumulated error (e.g. Julian calendar) that have influenced us for centuries, and will likely continue to do so for many centuries or millenia more.
 
Last edited:

dejo

Moderator emeritus
Original poster
Sep 2, 2004
15,982
452
The Centennial State
dejo,

I'm late to this party. I didn't discover this thread until just now. There is actually some very good info in Xcode about this sort of thing. Do a search on "Calendrical Calculations". That should lead you to a very informative article in the help system.

NSCalendar is an incredibly powerful class, especially when combined with NSDateComponents, NSDate, and NSDateFormatter.

Thanks, Duncan. I'll look that up and do some more reading. I do find NSDateComponents incredibly intriguing.

The Chinese lunar calendar probably won't match the ecclesiastical lunar calendar used to calculate Easter. I certainly wouldn't expect it to. And there are differences between the Eastern Church (aka Eastern Orthodox) and the Western Church (Roman Catholic and Protestant), regarding the placement of March 21 (Julian vs. Gregorian calendars).

The link I posted on Computus contains links to the ecclesiastical new moon and full moon calculation.

FWIW, this is also why Easter Sunday isn't necessarily the Sunday after Passover, even though the Last Supper is a Passover meal. In short, see the Computus article; the calculation isn't as obvious as it may appear.

Things like this make conversion between decimal, binary, hex, IEEE-754, etc. seem like child's play, and remind me that there are long legacies of history, tradition, and accumulated error (e.g. Julian calendar) that have influenced us for centuries, and will likely continue to do so for many centuries or millenia more.

Yeah, that Computus article is a lot to take in, but it looks like the Nicene Council strikes again! (I was raised Roman Catholic).

I'm really only interested in western dates for Easter since I'm working on an app that is targeted at U.S. customers.
 

xArtx

macrumors 6502a
Mar 30, 2012
764
1
Hi, What was the need to calculate moon phases?
I have moon phase 1-7 (less accurate) and percentage illumination (very accurate)
both working if anyone wants it. Both are in C though.

This doesn't tell you the day of full moon though,
you still have to cycle some days to find the 100% day.
 

moonman239

Cancelled
Mar 27, 2009
1,541
32
Anybody already know how to determine, given a particular year, the third Monday in January, or other similar "relative" dates?

I'm looking to built a method along the lines of:
Code:
- (int)getMLKDayForYear:(int)year;

And when called for this year:
Code:
[self getMLKDayForYear:2014];

would return 20.

In the meantime, I am going to play around with NSDateComponents etc. to see what I can figure out, but if someone has a quick answer, that would be great!

I found the following code in a page on Stack Overflow.

Code:
- (NSDate *)getFirstDayOfTheWeekFromDate:(NSDate *)givenDate
{
    NSCalendar *calendar = [NSCalendar currentCalendar];


   NSDateComponents *components = [calendar components:NSYearCalendarUnit|NSMonthCalendarUnit|NSWeekCalendarUnit|NSWeekdayCalendarUnit fromDate:givenDate];
    [components setWeekday:2]; // 1 == Sunday, 7 == Saturday
    if([[calendar dateFromComponents:components] compare: curDate] == NSOrderedDescending) // if start is later in time than end
    {
        [components setWeek:[components week]-1];
    }

    return [calendar dateFromComponents:components];
}
 

dejo

Moderator emeritus
Original poster
Sep 2, 2004
15,982
452
The Centennial State
I found the following code in a page on Stack Overflow.

Code:
- (NSDate *)getFirstDayOfTheWeekFromDate:(NSDate *)givenDate
{
    NSCalendar *calendar = [NSCalendar currentCalendar];


   NSDateComponents *components = [calendar components:NSYearCalendarUnit|NSMonthCalendarUnit|NSWeekCalendarUnit|NSWeekdayCalendarUnit fromDate:givenDate];
    [components setWeekday:2]; // 1 == Sunday, 7 == Saturday
    if([[calendar dateFromComponents:components] compare: curDate] == NSOrderedDescending) // if start is later in time than end
    {
        [components setWeek:[components week]-1];
    }

    return [calendar dateFromComponents:components];
}

Hmm, that's not much different than the following code from Apple's Date and Time Programming Guide:

Listing 10 Getting the Sunday in the current week
Code:
NSDate *today = [[NSDate alloc] init];
NSCalendar *gregorian = [[NSCalendar alloc]
          initWithCalendarIdentifier:NSGregorianCalendar];
 
// Get the weekday component of the current date
NSDateComponents *weekdayComponents = [gregorian components:NSWeekdayCalendarUnit
          fromDate:today];
 
/*
Create a date components to represent the number of days to subtract from the current date.
The weekday value for Sunday in the Gregorian calendar is 1, so subtract 1 from the number of days to subtract from the date in question.  (If today is Sunday, subtract 0 days.)
*/
NSDateComponents *componentsToSubtract = [[NSDateComponents alloc] init];
[componentsToSubtract setDay: 0 - ([weekdayComponents weekday] - 1)];
 
NSDate *beginningOfWeek = [gregorian dateByAddingComponents:componentsToSubtract
          toDate:today options:0];
 
/*
Optional step:
beginningOfWeek now has the same hour, minute, and second as the original date (today).
To normalize to midnight, extract the year, month, and day components and create a new date from those components.
*/
NSDateComponents *components =
     [gregorian components:(NSYearCalendarUnit | NSMonthCalendarUnit |
          NSDayCalendarUnit) fromDate: beginningOfWeek];
beginningOfWeek = [gregorian dateFromComponents:components];

Anyways, I'm marking this thread Resolved. I'm way past still looking for suggestions. Thanks for everyone's help, though!
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.