Sunday, July 12, 2009

A Category on NSDate

Here is a category on NSDate that makes a few somewhat common tasks that require several lines of code and turns them into a single method all. Among these methods are one that takes a date with a datetime value and turns it into a date without time, a method that calculates a new dates that is a certain number of days after the date, and a method that calculates the difference between two dates given in days.

Nothing earth-shattering, but may save you a few lines of code here and there.

NSDate-Misc.h
#import <Foundation/Foundation.h>
@interface NSDate(Misc)
+ (NSDate *)dateWithoutTime;
- (NSDate *)dateByAddingDays:(NSInteger)numDays;
- (NSDate *)dateAsDateWithoutTime;
- (int)differenceInDaysTo:(NSDate *)toDate;
- (NSString *)formattedDateString;
- (NSString *)formattedStringUsingFormat:(NSString *)dateFormat;
@end


NSDate-Misc.m
#import "NSDate-Misc.h"

@implementation NSDate(Misc)
+ (NSDate *)dateWithoutTime
{
return [[NSDate date] dateAsDateWithoutTime];
}

-(NSDate *)dateByAddingDays:(NSInteger)numDays
{
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];

NSDateComponents *comps = [[NSDateComponents alloc] init];
[comps setDay:numDays];

NSDate *date = [gregorian dateByAddingComponents:comps toDate:self options:0];
[comps release];
[gregorian release];
return date;
}

- (NSDate *)dateAsDateWithoutTime
{
NSString *formattedString = [self formattedDateString];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"MMM dd, yyyy"];
NSDate *ret = [formatter dateFromString:formattedString];
[formatter release];
return ret;
}

- (int)differenceInDaysTo:(NSDate *)toDate
{
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];

NSDateComponents *components = [gregorian components:NSDayCalendarUnit
fromDate:self
toDate:toDate
options:0
]
;
NSInteger days = [components day];
[gregorian release];
return days;
}

- (NSString *)formattedDateString
{
return [self formattedStringUsingFormat:@"MMM dd, yyyy"];
}

- (NSString *)formattedStringUsingFormat:(NSString *)dateFormat
{
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:dateFormat];
NSString *ret = [formatter stringFromDate:self];
[formatter release];
return ret;
}

@end




12 comments:

Malcolm Hall said...

Does differenceInDaysTo take into account different nsdate timezones?

Isn't a category header supposed to be named + not -?

burnsoftltd said...

http://github.com/billymeltdown
this chap also has a really helpful nsdate helper class.

i use it to mimic the iphone email app date where today equals the time and previous days the day of the week, and weeks in the past the actual date.

Jeff LaMarche said...

Malcolm:

differenceInDaysTo: should take timezone into account. I believe NSGregorianDate coverts all dates to Zulu time before doing the math, and then applies the timezone offset after the fact.

As for the + vs. - in the filename of the category I've seen both conventions used. I've actually used both myself. I've never seen anything official saying one is the "right" way, so I've standardized with the dash in my code.

burnsoftldt:

Thanks for the link. Looks like a helpful category.

Jeff

pippin said...

Are you sure that "Date Without Time" works in all region settings?
I had trouble with a similar approach and region (not language) set to Swedish due to some strange details not being able to read back the long time format.
I used some code instead that uses the calendar (I borrowed it from another site).

Donald W. Miller, Jr. said...

My apps do quite a bit of date calculations with a singleton Class (but a Category makes more sense) and never used NSGregorian in any way. But I did have several bugs because of problems with Daylight Savings Time when the dates "crossed" DST boundaries. Does NSGregorian really solve that?? I solved my issues with something like this:

NSTimeZone *timeZone = [NSTimeZone defaultTimeZone];
adjustedDate = [adjustedDate addTimeInterval:[timeZone daylightSavingTimeOffsetForDate:adjustedDate]];

Jeff LaMarche said...

pippin:

Nope, not sure at all. I haven't exhaustively tested it and can only say that it works for the time zones that I live in. To the extent it doesn't work, a bug report should be filed with Apple, I would think, because this is the documented way of doing this calculation, just wrapped up in a category method.

If you've got the code you used instead, I'd love to see what the difference is, though.

Donald:

First of all, I misspoke, NSGregorianDate is gone, we haven't had it in a while, and yes, there were several issues with NSGregorianDate when it came to doing date math. I'm actually using NSCalendar, which is supposed to handle date calculations correctly, but I've never thought of testing your scenario. If it doesn't work, then a bug should be filed with Apple, as this code is a variant of the code sample out of the documentation here:

http://developer.apple.com/documentation/Cocoa/Conceptual/DatesAndTimes/Articles/dtCalendars.html

However, the documentation does state that it gives an "approximate" calculation, so it's altogether possible that edge scenarios like daylight savings time boundaries are not handled. I simply don't know.

Jeff

pippin said...

Well, I didn't have the exact same issue since what I needed was a "time without date", so I can't tell whether you will see the same issue.
I needed to generate a date from a "seconds since 00:00" offset - date didn't matter.
What I found was that the output generated in some time zones was not what I had expected it to be from the formatter, what I do now is this:
NSDate *now = [NSDate dateWithTimeIntervalSince1970:0];
NSCalendar *cal = [NSCalendar currentCalendar];
NSDateComponents *comps = [cal components:(NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit ) fromDate:now];

[comps setHour:-[comps hour]];
[comps setMinute:-[comps minute]];
[comps setSecond:-[comps second]];

_today = [[cal dateByAddingComponents:comps toDate:now options:0] retain];

Andrew said...

Here are a couple more I created that I created for a project.


+ (NSDate *)monthStartDateWithOffset:(NSInteger)monthOffset
{
NSDate *beginningOfMonth = nil;
[[NSCalendar currentCalendar] rangeOfUnit:NSMonthCalendarUnit startDate:&beginningOfMonth interval:NULL forDate:[NSDate date]];
NSDateComponents *month = [[NSDateComponents alloc] init];
[month setMonth:monthOffset];
NSDate *monthStartDateWithOffset = [[NSCalendar currentCalendar] dateByAddingComponents:month toDate:beginningOfMonth options:0];
[month release];

return monthStartDateWithOffset;
}

+ (NSDate *)monthEndDateWithOffset:(NSInteger)monthOffset
{
NSDate *beginningOfMonth = nil;
[[NSCalendar currentCalendar] rangeOfUnit:NSMonthCalendarUnit startDate:&beginningOfMonth interval:NULL forDate:[NSDate date]];

// Add 1 month + offset in months to the beginning of the current month
NSDateComponents *oneMonth = [[[NSDateComponents alloc] init] autorelease];
[oneMonth setMonth:(1+monthOffset)];
NSDate *beginningOfNextMonthWithOffset = [[NSCalendar currentCalendar] dateByAddingComponents:oneMonth toDate:beginningOfMonth options:0];

// Subtract 1 second from the beginning next month with offset to get the end of the month with offset
NSDateComponents *negativeOneSecond = [[[NSDateComponents alloc] init] autorelease];
[negativeOneSecond setSecond:-1];
NSDate *monthEndDateWithOffset = [[NSCalendar currentCalendar] dateByAddingComponents:negativeOneSecond toDate:beginningOfNextMonthWithOffset options:0];

return monthEndDateWithOffset;
}


You can use them like:

NSInteger monthOffset = -2;

NSDate *startDateTwoMonthsAgo = [NSDate monthStartDateWithOffset:monthOffset];

NSDate *endDateTwoMonthsAgo = [NSDate monthEndDateWithOffset:monthOffset];


And it will return:

Start Date 2 Months Ago: 2009-05-01 00:00:00 -0400

End Date 2 Months Ago: 2009-05-31 23:59:59 -0400


I'm using it to retrieve data from a Core Data store based upon the month selected, these methods allow me to easily shift the range to last month, two months ago, three months ago, next month, two months ahead, etc.

Edwin said...

scrub m65 kamagra attorney lawyer body scrub field jacket lovegra marijuana attorney injury lawyer

petershine said...

Thank you for sharing this source. This helped me a lot in saving time figuring out how to get NSDate object without time.

JeansPilot said...

JeansPilot offers the chance to buy a large variety of men’s and women’s jeans clothing from the world famous Italian Brands.
Online jeans clothing store looks for original fashion clothing sales and clearances of worldwide known designers. We participate in fashion auctions to get the lowest possible price for Top quality Clothes, Shoes and Accessories.
Buy Jeans

h4ns said...

What youre saying is completely true. I know that everybody must say the same thing, but I just think that you put it in a way that everyone can understand. I also love the images you put in here. They fit so well with what youre trying to say. Im sure youll reach so many people with what youve got to say.

Arsenal vs Huddersfield Town live streaming
Arsenal vs Huddersfield Town live streaming
Wolverhampton Wanderers vs Stoke City Live Streaming
Wolverhampton Wanderers vs Stoke City Live Streaming
Notts County vs Manchester City Live Streaming
Notts County vs Manchester City Live Streaming
Bologna vs AS Roma Live Streaming
Bologna vs AS Roma Live Streaming
Juventus vs Udinese Live Streaming
Juventus vs Udinese Live Streaming
Napoli vs Sampdoria Live Streaming
Napoli vs Sampdoria Live Streaming
Fulham vs Tottenham Hotspur Live Streaming
Fulham vs Tottenham Hotspur Live Streaming
AS Monaco vs Marseille Live Streaming
AS Monaco vs Marseille Live Streaming
Alajuelense vs Perez Zeledon Live Streaming
Alajuelense vs Perez Zeledon Live Streaming
Technology News | News Today | Live Streaming TV Channels