-->

Determine NSDate for Memorial Day in a given year

2019-05-23 06:11发布

问题:

Is there a better way to determine the NSDate for Memorial Day (the last Monday in May) in a given year?

NSInteger aGivenYear = 2013 ;

NSCalendar* calendar = [NSCalendar currentCalendar] ;
NSDateComponents* firstMondayInJuneComponents = [NSDateComponents new] ;
firstMondayInJuneComponents.month = 6 ;
// Thanks, Martin R., for pointing out that `weekOfMonth` is wrong for returning the first Monday in June.
firstMondayInJuneComponents.weekOfMonth = 1 ;
firstMondayInJuneComponents.weekday = 2 ; //Monday
firstMondayInJuneComponents.year = aGivenYear ;
NSDate* firstMondayInJune = [calendar dateFromComponents:firstMondayInJuneComponents] ;

NSDateComponents* subtractAWeekComponents = [NSDateComponents new] ;
subtractAWeekComponents.week = 0 ;
NSDate* memorialDay = [calendar dateByAddingComponents:subtractAWeekComponents toDate:firstMondayInJune options:0] ;

Edit

I see now that firstMondayInJune in the above example doesn't work for all years; it returns May 28 for the year 2012.

Thanks, Martin R. weekdayOrdinal does exactly what I hoped, and it returns Memorial Day with 3 fewer lines of code:

NSInteger aGivenYear = 2013 ;

NSDateComponents* memorialDayComponents = [NSDateComponents new] ;
memorialDayComponents.year = aGivenYear ;
memorialDayComponents.month = 5 ;
memorialDayComponents.weekday = 2 ; //Monday
memorialDayComponents.weekdayOrdinal = -1 ; //The last instance of the specified weekday in the specified month & year.
NSDate* memorialDay = [calendar dateFromComponents:memorialDayComponents] ;

回答1:

To get the first Monday in a month, set weekdayOrdinal = 1 instead of weekOfMonth = 1:

NSInteger aGivenYear = 2012 ;

NSCalendar* calendar = [NSCalendar currentCalendar] ;
NSDateComponents* firstMondayInJuneComponents = [NSDateComponents new] ;
firstMondayInJuneComponents.month = 6 ;
firstMondayInJuneComponents.weekdayOrdinal = 1 ;
firstMondayInJuneComponents.weekday = 2 ; //Monday
firstMondayInJuneComponents.year = aGivenYear ;
NSDate* firstMondayInJune = [calendar dateFromComponents:firstMondayInJuneComponents] ;
// --> 2012-06-04

NSDateComponents* subtractAWeekComponents = [NSDateComponents new] ;
subtractAWeekComponents.week = -1 ;
NSDate* memorialDay = [calendar dateByAddingComponents:subtractAWeekComponents toDate:firstMondayInJune options:0] ;
// --> 2012-05-28

From the NSDateComponents documentation:

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.



回答2:

What I'd probably do is get the day of the week of the first of the month (perhaps using NSDateFormatter), then calculate the date of the first Monday/Tuesday/Thursday/whatever of the month, then add (week# - 1) x 7 to that date to get the Nth Monday/whatever of the month.

For Memorial day you'd first check if the 5th Monday was still in May, if not use the 4th. (Or check going in, knowing that if Memorial day is on the 31st then the first Monday must be on the 3rd or earlier.)