NSDate Comparison, Calculation of “Midnight Between” in a Specific (Local) Time Zone

Am I missing something? It seems that the method provided by Apple only works for UTC, regardless of the default time for the machine or what you installed it on.

Here's the output I get:

Output: 2013-02-01 10:41:24.152 Scratch[17640:c07] cal=gregorian, cal.timeZone=America/Los_Angeles (PST) offset -28800 2013-02-01 10:41:24.154 Scratch[17640:c07] date_Feb1_1400PST=2013-02-01 14:00 -0800 2013-02-01 10:41:24.156 Scratch[17640:c07] date_Feb2_1200PST=2013-02-02 12:00 -0800 2013-02-01 10:41:24.157 Scratch[17640:c07] midnights between=1 2013-02-01 10:41:24.158 Scratch[17640:c07] and then... 2013-02-01 10:41:24.159 Scratch[17640:c07] date_Feb1_2000PST=2013-02-01 22:00 -0800 2013-02-01 10:41:24.161 Scratch[17640:c07] date_Feb2_1000PST=2013-02-02 10:00 -0800 2013-02-01 10:41:24.161 Scratch[17640:c07] midnights between=0 

What I really want to know is “how many midnight” (ie how many days on the calendar) between two days for a given time zone (local or otherwise, not necessarily UTC)

This seems like such a general and reasonably simple question that I am surprised to see how disorderly and difficult to understand.

I'm not looking for an answer that includes "mod 86400" or something so dirty. The structure should be able to tell me this seriously.

 - (void)doDateComparisonStuff { NSCalendar *cal = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; cal.timeZone = [NSTimeZone timeZoneWithName:@"America/Los_Angeles"]; NSLog(@"cal=%@, cal.timeZone=%@", cal.calendarIdentifier, cal.timeZone); NSDate *date_Feb1_1400PST = [self dateFromStr:@"20130201 1400"]; NSLog(@"date_Feb1_1400PST=%@", [self stringFromDate:date_Feb1_1400PST]); NSDate *date_Feb2_1200PST = [self dateFromStr:@"20130202 1200"]; NSLog(@"date_Feb2_1200PST=%@", [self stringFromDate:date_Feb2_1200PST]); NSLog(@"midnights between=%d", [self daysWithinEraFromDate:date_Feb1_1400PST toDate:date_Feb2_1200PST usingCalendar:cal]); NSLog(@"and then..."); NSDate *date_Feb1_2000PST = [self dateFromStr:@"20130201 2200"]; NSLog(@"date_Feb1_2000PST=%@", [self stringFromDate:date_Feb1_2000PST]); NSDate *date_Feb2_1000PST = [self dateFromStr:@"20130202 1000"]; NSLog(@"date_Feb2_1000PST=%@", [self stringFromDate:date_Feb2_1000PST]); NSLog(@"midnights between=%d", [self daysWithinEraFromDate:date_Feb1_2000PST toDate:date_Feb2_1000PST usingCalendar:cal]); } // based on "Listing 13" at // https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/DatesAndTimes/Articles/dtCalendricalCalculations.html#//apple_ref/doc/uid/TP40007836-SW1 - (NSInteger)daysWithinEraFromDate:(NSDate *)startDate toDate:(NSDate *)endDate usingCalendar:(NSCalendar *)cal { NSInteger startDay=[cal ordinalityOfUnit:NSDayCalendarUnit inUnit: NSEraCalendarUnit forDate:startDate]; NSInteger endDay=[cal ordinalityOfUnit:NSDayCalendarUnit inUnit: NSEraCalendarUnit forDate:endDate]; return endDay-startDay; } - (NSDate *)dateFromStr:(NSString *)dateStr { NSDateFormatter *df = nil; df = [[NSDateFormatter alloc] init]; df.timeZone = [NSTimeZone timeZoneWithName:@"America/Los_Angeles"]; df.dateFormat = @"yyyyMMdd HHmm"; return [df dateFromString:dateStr]; } - (NSString *)stringFromDate:(NSDate *)date { NSDateFormatter *df = nil; df = [[NSDateFormatter alloc] init]; df.timeZone = [NSTimeZone timeZoneWithName:@"America/Los_Angeles"]; // native timezone here df.dateFormat = @"yyyy-MM-dd HH:mm Z"; return [df stringFromDate:date]; } 
+6
source share
6 answers

I wrote a small category to find the number of days between two NSDate objects. If I understand your question correctly, what do you want.

NSDate + DaysDifference.h

 #import <Foundation/Foundation.h> @interface NSDate (DaysDifference) - (NSInteger)differenceInDaysToDate:(NSDate *)otherDate; @end 

NSDate + DaysDifference.m

 #import "NSDate+DaysDifference.h" @implementation NSDate (DaysDifference) - (NSInteger)differenceInDaysToDate:(NSDate *)otherDate { NSCalendar *cal = [NSCalendar autoupdatingCurrentCalendar]; NSUInteger unit = NSDayCalendarUnit; NSDate *startDays, *endDays; [cal rangeOfUnit:unit startDate:&startDays interval:NULL forDate:self]; [cal rangeOfUnit:unit startDate:&endDays interval:NULL forDate:otherDate]; NSDateComponents *comp = [cal components:unit fromDate:startDays toDate:endDays options:0]; return [comp day]; } @end 
+1
source

Using this answer as a starting point, I just added a calendar as an additional argument (you can just pass the time zone instead of a calendar if you want):

 - (NSInteger)daysBetweenDate:(NSDate*)fromDateTime andDate:(NSDate*)toDateTime usingCalendar:(NSCalendar *)calendar { NSDate *fromDate; NSDate *toDate; [calendar rangeOfUnit:NSDayCalendarUnit startDate:&fromDate interval:NULL forDate:fromDateTime]; [calendar rangeOfUnit:NSDayCalendarUnit startDate:&toDate interval:NULL forDate:toDateTime]; NSDateComponents *difference = [calendar components:NSDayCalendarUnit fromDate:fromDate toDate:toDate options:0]; return [difference day]; } 

This will use the calendar that you pass in to determine the amount of midnight between fromDateTime and toDateTime and return it as an NSInteger . Make sure you set the time zone accordingly.

Using the above examples, this is the conclusion:

 2013-03-07 17:23:54.619 Testing App[69968:11f03] cal=gregorian, cal.timeZone=America/Los_Angeles (PST) offset -28800 2013-03-07 17:23:54.621 Testing App[69968:11f03] date_Feb1_1400PST=20130201 1400 2013-03-07 17:23:54.621 Testing App[69968:11f03] date_Feb2_1200PST=20130202 1200 2013-03-07 17:23:54.622 Testing App[69968:11f03] midnights between=1 2013-03-07 17:23:54.622 Testing App[69968:11f03] and then... 2013-03-07 17:23:54.623 Testing App[69968:11f03] date_Feb1_2000PST=20130201 2200 2013-03-07 17:23:54.624 Testing App[69968:11f03] date_Feb2_1000PST=20130202 1000 2013-03-07 17:23:54.624 Testing App[69968:11f03] midnights between=1 

So yes, I support my vote in order to close this question as a duplicate, since it is VERY close to what you are doing. :-)

+3
source

Here's how I would do it:

 // pick a random timezone // obviously you'd replace this with your own desired timeZone NSArray *timeZoneNames = [NSTimeZone knownTimeZoneNames]; NSTimeZone *randomZone = [NSTimeZone timeZoneWithName:[timeZoneNames objectAtIndex:(arc4random() % [timeZoneNames count])]]; // create a copy of the current calendar // (because you should consider the +currentCalendar to be immutable) NSCalendar *calendar = [[NSCalendar currentCalendar] copy]; // change the timeZone of the calendar // this causes all computations to be done relative to this timeZone [calendar setTimeZone:randomZone]; // your start and end dates // obviously you'd replace this with your own dates NSDate *startDate = [NSDate dateWithTimeIntervalSinceReferenceDate:1234567890.0]; NSDate *endDate = [NSDate dateWithTimeIntervalSinceReferenceDate:1234890567.0]; // compute the midnight BEFORE the start date NSDateComponents *midnightComponentsPriorToStartDate = [calendar components:NSEraCalendarUnit | NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit fromDate:startDate]; NSDate *midnightPriorToStartDate = [calendar dateFromComponents:midnightComponentsPriorToStartDate]; // this will keep track of how many midnights there are NSUInteger numberOfMidnights = 0; // loop FOREVER while (1) { // compute the nth midnight NSDateComponents *dayDiff = [[NSDateComponents alloc] init]; [dayDiff setDay:numberOfMidnights+1]; NSDate *nextMidnight = [calendar dateByAddingComponents:dayDiff toDate:midnightPriorToStartDate options:0]; // if this midnight is after the end date, we stop looping if ([endDate laterDate:nextMidnight] == nextMidnight) { // this next midnight is after the end date break; // ok, maybe not forever } else { // this midnight is between the start and end date numberOfMidnights++; } } NSLog(@"There are %lu midnights between %@ and %@", numberOfMidnights, startDate, endDate); 
+2
source

This is the best I could come up with.

It works regardless of the current calendar and time of day.

 - (NSInteger)daysApartFrom:(NSDate *)startDate toDate:(NSDate *)endDate usingCalendar:(NSCalendar *)localizedTZCal { static NSTimeZone *timeZoneUtc; static NSCalendar *utcCal; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ timeZoneUtc = [NSTimeZone timeZoneWithName:@"UTC"]; utcCal = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; utcCal.timeZone = timeZoneUtc; }); NSInteger componentFlags = NSEraCalendarUnit | NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit; NSDateComponents *componentsStartDate = [localizedTZCal components:componentFlags fromDate:startDate]; NSDateComponents *componentsEndDate = [localizedTZCal components:componentFlags fromDate:endDate]; NSDate *utcStartDate = [utcCal dateFromComponents:componentsStartDate]; NSDate *utcEndDate = [utcCal dateFromComponents:componentsEndDate]; NSInteger utcDaysDiff = [self daysWithinEraFromDate:utcStartDate toDate:utcEndDate usingCalendar:utcCal]; return utcDaysDiff; } - (NSInteger)daysWithinEraFromDate:(NSDate *)startDate toDate:(NSDate *)endDate usingCalendar:(NSCalendar *)cal { NSInteger startDay = [cal ordinalityOfUnit:NSDayCalendarUnit inUnit: NSEraCalendarUnit forDate:startDate]; NSInteger endDay = [cal ordinalityOfUnit:NSDayCalendarUnit inUnit:NSEraCalendarUnit forDate:endDate]; return endDay - startDay; } 
+1
source

This works well:

Here is a category to find the amount of midnight between two NSDate objects.

NSDate + DaysDifference.h

 #import <Foundation/Foundation.h> @interface NSDate (DaysDifference) + (int) midnightsBetweenDate:(NSDate*)fromDateTime andDate:(NSDate*)toDateTime; @end 

NSDate + DaysDifference.m

 #import "NSDate+DaysDifference.h" @implementation NSDate (DaysDifference) + (int) midnightsBetweenDate:(NSDate*)fromDateTime andDate:(NSDate*)toDateTime; { NSDate* sourceDate = [NSDate date]; NSTimeZone* destinationTimeZone = [NSTimeZone systemTimeZone]; NSInteger timeZoneOffset = [destinationTimeZone secondsFromGMTForDate:sourceDate]; NSCalendar *calendar = [NSCalendar currentCalendar]; NSInteger startDay = [calendar ordinalityOfUnit:NSDayCalendarUnit inUnit:NSEraCalendarUnit forDate:[fromDateTime dateByAddingTimeInterval:timeZoneOffset]]; NSInteger endDay = [calendar ordinalityOfUnit:NSDayCalendarUnit inUnit:NSEraCalendarUnit forDate:[toDateTime dateByAddingTimeInterval:timeZoneOffset]]; return endDay - startDay; } @end 
0
source

Has the extension "Fast 3" in the "Calendar", which will perform the trick

 extension Calendar { /** Calculates the number og midnights between two date-times - parameter firstDateTime: the date to start counting, defaults to today - parameter lastDateTime: the date to end counting - returns: the number of midnights between the given date-times - note: If firstDateTime is after lastDateTime the result may be negative */ public func midnights(from firstDateTime: Date = Date(), until lastDateTime: Date) -> Int { let firstStartOfDay = startOfDay(for: firstDateTime) let lastStartOfDay = startOfDay(for: lastDateTime) return Int(lastStartOfDay.timeIntervalSince(firstStartOfDay)/(60*60*24)) } } 
-1
source

All Articles