views:

905

answers:

2

I'm creating a report generator in Cocoa, and I need to produce convenient date ranges such as "Today", "This Week", "This Month", "This Year", etc.

Is there a good way to do this? Here is my skeleton thus far:

@interface DateRange : NSObject
{
    NSDate startDate;
    NSDate endDate;
}

@property (assign) NSDate * startDate;
@property (assign) NSDate * endDate;

+ (DateRange *)rangeForDayContainingDate:(NSDate *)date;
+ (DateRange *)rangeForWeekContainingDate:(NSDate *)date;
+ (DateRange *)rangeForMonthContainingDate:(NSDate *)date;
+ (DateRange *)rangeForYearContainingDate:(NSDate *)date;

@end

Some example use cases would be as follows:

DateRange * thisWeek = [DateRange rangeForDayContainingDate:[NSDate date]];
DateRange * thisYear = [DateRange rangeForYearContainingDate:[NSDate date]];

Essentially, I want the returned DateRange object to contain the start and end dates for the week, month or year surrounding the target date. For example (in pseudocode):

NSDate * today = [August 25, 2009];
DateRange * thisWeek = [DateRange rangeForWeekContainingDate:today];
assert(thisWeek.startDate == [August 23, 3009]);
assert(thisWeek.endDate == [August 29, 3009]);

update:

I was able to get this working thanks to the answer provided by Kendall Helmstetter Geln. Here is the complete method for a one-week range:

+ (DateRange *)rangeForWeekContainingDate:(NSDate *)date
{
    DateRange * range = [[self alloc] init];

    // start of the week
    NSDate * firstDay;
    [[self calendar] rangeOfUnit:NSWeekCalendarUnit
                       startDate:&firstDay
                        interval:0
                         forDate:date];
    [range setStartDate:firstDay];

    // end of the week
    NSDateComponents * oneWeek = [[NSDateComponents alloc] init];
    [oneWeek setWeek:1];
    [range setEndDate:[[self calendar] dateByAddingComponents:oneWeek
                                                       toDate:firstDay
                                                      options:0]];
    [oneWeek release];

    return [range autorelease];
}
+2  A: 

Well since timeInterval is in seconds, just do the math to figure out how many seconds are in a day:

60 seconds * 60 minutes * 24 hours = 1 day.

Then in your rangeForDayContainingDate method you could extract date components, get the current day, create a new date based on the day with hours and minutes set to 0:00, and the create the end date adding the time interval as calculated above.

Kendall Helmstetter Gelner
+1 That looks promising. Date components might be exactly what I was looking for.
e.James
Thank you, sir! Your answer pointed me in the right direction. I have posted the full solution for a single-week range as an edit to the original question.
e.James
You're welcome, please feel free to mark your own answer as the actual solution instead of my rough directions... I'll not be offended.
Kendall Helmstetter Gelner
+2  A: 

For the sake of completeness, here is my final solution (with thanks to Kendall Helmstetter Geln and jbrennan):

+ (NSCalendar *)calendar
{
    NSCalendar * gregorian = [[NSCalendar alloc]
                              initWithCalendarIdentifier:NSGregorianCalendar];
    return [gregorian autorelease];
}

////////////////////////////////////////////////////////////////

+ (NSDateComponents *)singleComponentOfUnit:(NSCalendarUnit)unit
{
    NSDateComponents * component = [[NSDateComponents alloc] init];

    switch (unit)
    {
        case NSDayCalendarUnit: [component setDay:1]; break;
        case NSWeekCalendarUnit: [component setWeek:1]; break;
        case NSMonthCalendarUnit: [component setMonth:1]; break;
        case NSYearCalendarUnit: [component setYear:1]; break;
    }

    return [component autorelease];
}

////////////////////////////////////////////////////////////////

+ (WM_DateRange *)rangeForUnit:(NSCalendarUnit)unit
               surroundingDate:(NSDate *)date
{
    WM_DateRange * range = [[self alloc] init];

    // start of the period
    NSDate * firstDay;
    [[self calendar] rangeOfUnit:unit
                       startDate:&firstDay
                        interval:0
                         forDate:date];
    [range setStartDate:firstDay];

    // end of the period
    [range setEndDate:[[self calendar]
        dateByAddingComponents:[self singleComponentOfUnit:unit]
                        toDate:firstDay
                       options:0]];

    return [range autorelease];
}

////////////////////////////////////////////////////////////////

+ (WM_DateRange *)rangeForDayContainingDate:(NSDate *)date
{ return [self rangeForUnit:NSDayCalendarUnit surroundingDate:date]; }

+ (WM_DateRange *)rangeForWeekContainingDate:(NSDate *)date
{ return [self rangeForUnit:NSWeekCalendarUnit surroundingDate:date]; }

+ (WM_DateRange *)rangeForMonthContainingDate:(NSDate *)date
{ return [self rangeForUnit:NSMonthCalendarUnit surroundingDate:date]; }

+ (WM_DateRange *)rangeForYearContainingDate:(NSDate *)date
{ return [self rangeForUnit:NSYearCalendarUnit surroundingDate:date]; }
e.James