Resolved Third Monday in January, for example

Discussion in 'iOS Programming' started by dejo, Feb 3, 2014.

  1. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #1
    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!
     
  2. chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #2
    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).
     
  3. dejo thread starter Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #3
    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".
     
  4. ArtOfWarfare macrumors 604

    ArtOfWarfare

    Joined:
    Nov 26, 2007
    #4
    I may be thinking of a Java library, but I was pretty certain this kind of thing was provided in the iOS SDK.
     
  5. dejo thread starter Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #5
    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).
     
  6. chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #6
    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.
     
  7. dejo thread starter Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #7
    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...
     
  8. dejo thread starter Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #8
    Yup, using weekdayOrdinal seems to work! Thanks again, chown33!

    Now to work on the "last x in y" special case (for example: the last Monday in May).

    P.S. Easter should be extra fun!
     
  9. chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #9
    Glad it worked.

    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.

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

    I distinctly recall implementing Gauss's algorithm in Tiny Basic, on a Z80.
     
  10. dejo thread starter Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #10
    Wow, you're full of it. "It" being better information than Apple's documentation. -1 works for the last!

    Yup. Now to find/compose an Objective-C version...
     
  11. ArtOfWarfare macrumors 604

    ArtOfWarfare

    Joined:
    Nov 26, 2007
    #11
    Doesn't it follow a regular pattern on a different calendar? Will NSCalendar allow you to translate between them?
     
  12. dejo thread starter Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #12
    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;
    }
    
     
  13. Duncan C macrumors 6502a

    Duncan C

    Joined:
    Jan 21, 2008
    Location:
    Northern Virginia
    #13
    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.


     
  14. chown33, Feb 4, 2014
    Last edited: Feb 4, 2014

    chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #14
    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.
     
  15. dejo thread starter Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #15
    Thanks, Duncan. I'll look that up and do some more reading. I do find NSDateComponents incredibly intriguing.

    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.
     
  16. xArtx macrumors 6502a

    Joined:
    Mar 30, 2012
    #16
    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.
     
  17. moonman239 macrumors 68000

    Joined:
    Mar 27, 2009
    #17
    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];
    }
    
     
  18. dejo thread starter Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #18
    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!
     

Share This Page