I am working with a schedule that specifies times of day, such as 10:30 AM. I do not know the dates, however. I\'m going to store these as values in a NSDictionary
So, I guess there's no simple inbuilt way of doing this. It also looks like the only way to get Cocoa to build the time I expect on DST boundaries is with strings.
So for posterity, it looks like I'll be using something like this.
Test harness:
//
// TimeTest.m
//
#import <Foundation/Foundation.h>
#import "Utility.h"
id utility;
void testTime( NSTimeInterval time ) {
id gregorian = [[[NSCalendar alloc] initWithCalendarIdentifier: NSGregorianCalendar] autorelease];
id oneDay = [[[NSDateComponents alloc] init] autorelease];
[oneDay setDay: 1];
id thisDay = [gregorian dateFromComponents: [gregorian components: (NSEraCalendarUnit | NSYearCalendarUnit)
fromDate: [NSDate date]]];
for (NSInteger dayIdx = 0; dayIdx < 365; ++dayIdx ) {
NSDate *dateTime = [utility timeInSeconds: time
onDate: thisDay];
NSLog( @"%@", dateTime );
thisDay = [gregorian dateByAddingComponents: oneDay
toDate: thisDay
options: 0];
}
}
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
utility = [[[Utility alloc] init] autorelease];
testTime( ((10 * 60.0) + 0.0) * 60.0 );
testTime( ((9 * 60.0) + 30.0) * 60.0 );
[pool drain];
return 0;
}
Utility header:
//
// Utility.h
//
#import <Foundation/Foundation.h>
@interface Utility : NSObject {
NSCalendar *gregorian;
NSDateFormatter *dateWithoutTimeFormatter, *dateWithTimeFormatter;
}
- (NSDate *)timeInHours: (NSInteger)hours
minutes: (NSInteger)minutes
seconds: (NSInteger)seconds
onDate: (NSDate *)inDate;
- (NSDate *)timeInSeconds: (NSTimeInterval)inTime
onDate: (NSDate *)inDate;
@end
Utility implementation:
//
// Utility.m
//
#import "Utility.h"
@interface Utility()
@property (nonatomic, readwrite, retain) NSCalendar *gregorian;
@property (nonatomic, readwrite, retain) NSDateFormatter *dateWithoutTimeFormatter, *dateWithTimeFormatter;
@end
@implementation Utility
@synthesize gregorian, dateWithoutTimeFormatter, dateWithTimeFormatter;
- (NSDate *)timeInHours: (NSInteger)hours
minutes: (NSInteger)minutes
seconds: (NSInteger)seconds
onDate: (NSDate *)inDate;
{
id timeStr = [[NSMutableString alloc] initWithFormat: @"%02d:%02d:%02d", hours, minutes, seconds];
id dateStr = [dateWithoutTimeFormatter stringFromDate: inDate];
id dateTimeStr = [[NSString alloc] initWithFormat: @"%@ %@", dateStr, timeStr];
[timeStr release];
id dateTime = [dateWithTimeFormatter dateFromString: dateTimeStr];
[dateTimeStr release];
return dateTime;
}
- (NSDate *)timeInSeconds: (NSTimeInterval)inTime
onDate: (NSDate *)inDate;
{
NSAssert1( inTime < 24.0 * 3600.0, @"Time %f must be less than 24hrs", inTime );
double temp = inTime;
int hours = rintf(floor( temp / 3600.0 ));
temp -= ( hours * 3600 );
int minutes = rintf(floorf( temp / 60.0 ));
temp -= ( minutes * 60 );
int seconds = rintf( temp );
return [self timeInHours: hours
minutes: minutes
seconds: seconds
onDate: inDate];
}
- (id)init;
{
if (( self = [super init] )) {
self.gregorian = [[[NSCalendar alloc] initWithCalendarIdentifier: NSGregorianCalendar] autorelease];
self.dateWithoutTimeFormatter = [[[NSDateFormatter alloc] init] autorelease];
[dateWithoutTimeFormatter setDateFormat: @"yyyy-MM-dd"];
self.dateWithTimeFormatter = [[[NSDateFormatter alloc] init] autorelease];
[dateWithTimeFormatter setDateFormat: @"yyyy-MM-dd HH:mm:ss"];
}
return self;
}
- (void)dealloc;
{
self.gregorian = nil;
self.dateWithoutTimeFormatter = nil;
self.dateWithTimeFormatter = nil;
[super dealloc];
}
@end
Why bother with a separate unit for this? Well, I've written enough date formatting code to know that constructing NSCalendar
and NSDateFormatter
on the fly utterly kills performance.
The short answer is that there's no built-in mechanism for storing time-of-day, so just use whatever is most convenient to you. I've used strings in the past using my own encoding and parsing code, (e.g., "22:00") because they're easy to read and debug, but there's nothing wrong with storing seconds or minutes past midnight as you suggest. Just remember that you'll have to do the math yourself.
How ever you do it, you will need separate year, month, day, hour, minute, and second values so that you can construct an NSDate from NSDateComponents, using NSCalendar's -dateFromComponents:
method.
And as others have said, you cannot set the time-of-day by adding hours and minutes to an existing NSDate
because if you cross a DST boundary you won't get the value you expect. (However, I assume you can still add day and month components without worrying about DST)
You can use NSDateComponents to store only the components you need (hour and minutes for example), and then use NSCalendar
dateByAddingComponents:toDate:options: to create an absolute date reference, when you need using the serialized components and a base date.