4

I created a method to calculate the first and last day of week, by adding a time interval of one day (86,400) to a date, multiplied by the required number of days.

A colleague commented "adding multiples of 86,400 is never the answer". Why is this so?

Here's the category method that I created, how can I improve this?

- (NSDate*)firstDayOfWeek
{
    return [self dateWithDaysAddedGivenDayOfWeek:@{
        @1 : @-6,
        @2 : @0,
        @3 : @-1,
        @4 : @-2,
        @5 : @-3,
        @6 : @-4,
        @7 : @-5
    }];
}

- (NSDate*)lastDayOfWeek
{
    return [self dateWithDaysAddedGivenDayOfWeek:@{
        @1 : @0,
        @2 : @6,
        @3 : @5,
        @4 : @4,
        @5 : @3,
        @6 : @2,
        @7 : @1
    }];
}

- (NSDate*)dateWithDaysAddedGivenDayOfWeek:(NSDictionary*)daysToAddGivenDay
{
    NSDateComponents* components = [[NSCalendar currentCalendar]
        components:NSYearCalendarUnit | NSMonthCalendarUnit | NSWeekCalendarUnit | NSWeekdayCalendarUnit fromDate:self];

    NSInteger daysToAdd = [[daysToAddGivenDay objectForKey:@([components weekday])] integerValue];
    return [self dateByAddingTimeInterval:daysToAdd * 60 * 60 * 24];
}

Do I need to use a different time interval for the day? (eg 23 hours, 56 minutes, 4.1 seconds)

Community
  • 1
  • 1
Jasper Blues
  • 28,258
  • 22
  • 102
  • 185
  • 1
    I believe his comment was directed at the fact that your solution does not take into account daylight savings shifts. It's always better to use system provided functions, if available. – Brian Shamblen Nov 07 '13 at 02:43
  • @BrianShamblen you mean if I go back one day, and daylight saving started or ended on that day, then it would provide the wrong result? – Jasper Blues Nov 07 '13 at 02:45
  • 1
    what do u think? does it produce a wrong result, if you assume a day to have 24 hours, but days with 23 or 25 hours occur? – vikingosegundo Nov 07 '13 at 02:47
  • 1
    calendar calculation isn't easy. and if you try to be smarter than apple's folks, it is a strong indication that you are doing them wrong already. – vikingosegundo Nov 07 '13 at 02:49
  • 1
    Just want to note: good for you for asking the question and wanting to know why! Many developers, when receiving a comment like that, get very defensive of their code and their assumptions. Major kudos to you for wanting to know more. :) – Dave DeLong Nov 07 '13 at 03:47
  • 1
    @DaveDeLong As the career progresses, status increases. However I try to maintain the "student attitude" that brought learning and enjoyment in years past. Despite ever growing experience we can never be an expert in all topics - one of the worst things that can happen is having to maintain the image of "expert" even when a particular topic is actually quite weak. . . humility and honesty works best in the long run. and the truth is I sucked at calendars. Now not so much ;) – Jasper Blues Nov 07 '13 at 03:54

2 Answers2

7

You cannot assume that days have the same length. Due to daylight saving times they can i.e. be 23, 24 or 25 hours long. It is easy to imagine the trouble it might cause if we assume a day to be exactly 24*60*60 seconds long.

Also your code might be faulty as in different countries and cultures the first day of the week might differ (Sunday vs. Monday). So time and date calculations must be in respect to the calendar and the users locale. It is not simply done by counting seconds.

And we haven't even mentioned more nasty things as leap seconds, politicians changing a states time zone (Russia under Putin, Spain under Franco) or even shifting them across the international date line (refer to Samoa — they did it twice).

So you should trust the framework's tool, nicely explained in WWDC2011: Performing Calendar Calculations.

For the code you posted in the question I would suggest some thing like

NSCalendar *cal = [NSCalendar currentCalendar];
NSDate *now = [NSDate date];
NSDate *startOfTheWeek;
NSDate *endOfWeek;
NSTimeInterval interval;
[cal rangeOfUnit:NSWeekCalendarUnit 
       startDate:&startOfTheWeek 
        interval:&interval //<-- interval will hold the length of the time unit, 
         forDate:now];     //    here week, taking DST et al into account
//startOfWeek holds now the first day of the week, according to locale (monday vs. sunday)

endOfWeek = [startOfTheWeek dateByAddingTimeInterval:interval-1];
// holds 23:59:59 of last day in week.

In your case another option would be to skip NSDate, as they are not representing a day but a specific moment in time and work with NSDateComponents. Also explained in the video.

vikingosegundo
  • 52,040
  • 14
  • 137
  • 178
  • +1 great answer. My personal style is to set `endOfWeek = startOfWeek + 1 week` (using `NSDateComponents` of course), but then do a strictly less than comparison on the upper bound. IOW: `startOfWeek <= date && date < endOfWeek`. – Dave DeLong Nov 07 '13 at 03:45
  • NSDateComponents are just as fine. what I like about the `rangeOfUnit:startDate:interval:forDate:` way is the fact that it is just one line that has some logic and that I just need to change one parameter to get ranges for days, month (and they are also hard to get right, do to a length 28,29,30 or 31 days), years… – vikingosegundo Nov 07 '13 at 04:12
4

You can't predict how many seconds are in a given day due to a wide variety of factors (leap seconds, DST, etc...). Instead, you should let the system handle this for you using this method on NSCalendar:

- (NSDate *)dateByAddingComponents:(NSDateComponents *)comps toDate:(NSDate *)date options:(NSUInteger)opts

By setting the day to a positive or negative number, you can add or subtract days accurately from a given date.

Catfish_Man
  • 41,261
  • 11
  • 67
  • 84