5

I'm trying to do something that, for me, is a bit difficult. But I'm sure someone has some insight.

Given a date, say January 17, 2011, I'm trying to figure out the day that corresponds to this date one year ago. So January 17, 2011 is a Monday, and one year ago, this day fell on January 18, 2010 (a Monday as well). It turns out that January 18, 2010 is 354 days before January 17, 2011. I originally thought to just simply subtract 365 days for a non-leap year and 366 days for a leap year, but if you do that in this case, you would get January 17, 2010, which is a Sunday, not a Monday.

So, in Objective-C with NSDate and NSCalendar, how could I implement a function such as:

-(NSDate *)logicalOneYearAgo:(NSDate *)from {
}

In other words, the nth "weekday" of the nth month (where "weekday" is Monday or Tuesday or Wednesday etc)

Sergey Kuryanov
  • 6,114
  • 30
  • 52
CodeGuy
  • 28,427
  • 76
  • 200
  • 317
  • 3
    You're going to have to define better exactly what your expectations are; one year back from January 17, 2011 is January 17, 2010. Do you want (for instance) the nth Monday of the nth month? – Steven Fisher Jan 18 '11 at 22:38
  • yes, the nth "weekday" of the nth month. – CodeGuy Jan 18 '11 at 23:12
  • This month has five Mondays, while January 2010 only had four. What's the date one year before January 31, 2011, in this date scheme? – Chuck Jan 18 '11 at 23:37
  • Potentially the http://en.wikipedia.org/wiki/ISO_week_date calendar could be useful here. In general though, beware; calendrical arithmetic is *extraordinarily* hard to get right in all cases. – Catfish_Man Jan 19 '11 at 00:05
  • I'd try using `NSCalendar` to break the date into `NSDateComponents` using the `NSYearCalendarUnit | NSMonthCalendarUnit | NSWeekdayOrdinalCalendarUnit | NSWeekdayCalendarUnit`. But I haven't tried this, so you'll have to see if you can make it work. – Steven Fisher Jan 19 '11 at 08:27
  • There is no such thing as "logical" one year ago. You can't just make up terms without explicitly defining them. – Wayne May 23 '14 at 19:56

5 Answers5

22

You use NSDateComponents like so:

- (NSDate *)logicalOneYearAgo:(NSDate *)from {

    NSCalendar *gregorian = [[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] autorelease];

    NSDateComponents *offsetComponents = [[[NSDateComponents alloc] init] autorelease];
    [offsetComponents setYear:-1];

    return [gregorian dateByAddingComponents:offsetComponents toDate:from options:0];

}
grahamparks
  • 16,130
  • 5
  • 49
  • 43
  • this results in the problem I stated in the question. If "from" is January 17, 2011, then this method will return January 17, 2010 (however I need it to return January 18, 2010). – CodeGuy Jan 18 '11 at 23:16
11

This is covered in the Date and Time Programming Guide section on Calendrical Calculations, Adding Components to a Date

Specifically, the method of interest is dateByAddingComponents:toDate:options.

Using the Apple example as a basis, to subtract one year from the current date, you would do the following:

NSDate *today = [[NSDate alloc] init];
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];

/*
  Create a date components to represent the number of years to add to the current date.
  In this case, we add -1 to subtract one year.     
*/

NSDateComponents *addComponents = [[NSDateComponents alloc] init];
addComponents.year = - 1;

return [calendar dateByAddingComponents:addComponents toDate:today options:0];
Max MacLeod
  • 26,115
  • 13
  • 104
  • 132
3

I can't guarantee this is the most efficient way, but you could do it using NSCalendar and NSDateComponents. Pass your NSDate instance into [yourCalendar components:yourComponents fromDate:yourDate], subtract a year from the components, and then use [yourCalendar dateFromComponents:yourComp].

Sorry for not providing full code, but it's been a while since I've worked with those methods, and I don't want to give you code that won't work.

Jeff Kelley
  • 19,021
  • 6
  • 70
  • 80
WoodenKitty
  • 6,521
  • 8
  • 53
  • 73
  • this also results in the problem I stated in the question. If "from" is January 17, 2011, then this idea will return January 17, 2010 (however I need it to return January 18, 2010). – CodeGuy Jan 18 '11 at 23:17
0

Substract 1 year. Then substract 1 day until you end up the same day of week you want.

Each year the "days of week" goes back 1 day, except when there is a feb 29th in between, they go back 2 days.

It's because a year is 52 weeks + 1 day (+2 for leap years).

So if you apply this logic indefinitely, you will end up, years after years, having a june 16th at may 24th.

If you don't want that to happen, you have to somewhere memorize the reference year and base your arithmetic on it.

For example, if the "new" monday is 4 days back (because 3 years have passed), you might prefer to chose the monday 3 days ahead in time, so as not to indefinitely substract days, years after years.

Hope it helps.

Gryzorz
  • 320
  • 2
  • 5
-8

The answer turned out to be pretty simple. It turned out from trial and error. Here's the answer.

if the year is a leap year, subtract 365 days.

if the year is not a leap year, subtract 364 days.

CodeGuy
  • 28,427
  • 76
  • 200
  • 317
  • This doesn't even do what you requested. This question and its answer are a trainwreck. – Wayne May 23 '14 at 19:54