NSDate: Right way to work with time of day?

£可爱£侵袭症+ 提交于 2019-12-01 21:12:21

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)

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.

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!