2

My problem is to do with the Time Profile of NSDateComponents operations given in the code snippet below.

  • I have a method that iterates over a large array of NSDate objects
  • Part of the method requires that I discard the Hour, Minute and seconds of each date.
  • To do this I have 3 steps:

    1. I create an NSDateComponents object from my original date.
    2. I set the H:M:S elements to 0
    3. I get my new date object that has H:M:S set to 0 as needed

The Issue:

Steps 1 & 3 above are taking a huge percentage of my overall method execution time, 22.4% and 56.8% respectively.

I'm looking for suggestions as to how I might optimize this part of my code or alternative ways of zeroing the H:M:S that might perform better.

NSDate* date = [[NSDate alloc] init];
// Next line takes 22.4% of the overall method execution time
NSDateComponents* components =  [calendar components:(NSYearCalendarUnit | NSMonthCalendarUnit 
                                | NSDayCalendarUnit | NSHourCalendarUnit  
                                | NSSecondCalendarUnit) fromDate:date]; 

[components setHour:0];
[components setMinute:0];
[components setSecond:0];

// Next line takes 56.8% of the overall method execution time
date = [calendar dateFromComponents:components];
androider
  • 333
  • 5
  • 13
  • 1
    If you want to be crude about it (purists will gag), and you don't mind working in UTC (more or less), you can simply extract the NSTimeInterval, divide by the number of seconds in a day, take the floor, multiply by number of seconds in a day, and convert back to NSDate. Can be done in one (slightly long) line, if you're so disposed. – Hot Licks Mar 05 '14 at 19:20
  • UTC is just fine. I'll implement this in the morning and see how it goes. – androider Mar 05 '14 at 19:54
  • This works great great for zeroing the H:M:S. Looking at another use case where I wanted to keep the hour value, I started to get funny results. Not the question I asked but an observation worth noting If I try and convert 2014-03-03 01:30:00 I get 2014-03-03 00:59:44 – androider Mar 06 '14 at 09:48

1 Answers1

4

Update

Clocks:

Time 1: ≈ 0.000139 - Option 1 - Original

Time 2: ≈ 0.000108 - Option 2

Time 3: ≈ 0.000013 - Option 3

Time 4: ≈ 0.000004 - Option 4

Option 1 - Original

NSDate* originDate = [[NSDate alloc] init];
NSDate* date = [[NSDate alloc] init];
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents* components =  [calendar components:(NSYearCalendarUnit | NSMonthCalendarUnit
                                                      | NSDayCalendarUnit | NSHourCalendarUnit
                                                      | NSSecondCalendarUnit) fromDate:date];

[components setHour:0];
[components setMinute:0];
[components setSecond:0];
date = [calendar dateFromComponents:components];
NSLog(@"Time 1: %f", -[originDate timeIntervalSinceNow]);

Option 2

You don't need

[components setHour:0];
[components setMinute:0];
[components setSecond:0];

Just don't even add these as options to date components

NSDate* originDate2 = [[NSDate alloc] init];
NSDate* date2 = [[NSDate alloc] init];
NSCalendar *calendar2 = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];

NSDateComponents* components2 =  [calendar2 components:(NSYearCalendarUnit | NSMonthCalendarUnit
                                                        | NSDayCalendarUnit
                                                        ) fromDate:date2];
date2 = [calendar2 dateFromComponents:components2];
NSLog(@"Time 2: %f", -[originDate2 timeIntervalSinceNow]);

Sources: Similar Question + David's Comment

Option 3

Basically, I found that if I run the method with an already created NSCalendar, I get 85% to 90% faster speed consistently.

Declare calendarProperty and initialize it before you run the method

NSDate* originDate3 = [[NSDate alloc] init];
NSDate* date3 = [[NSDate alloc] init];
NSDateComponents* components3 =  [calendarProperty components:(NSYearCalendarUnit | NSMonthCalendarUnit
                                                               | NSDayCalendarUnit
                                                               ) fromDate:date3];
date3 = [calendarProperty dateFromComponents:components3];
NSLog(@"Time 3: %f", -[originDate3 timeIntervalSinceNow]);

Option 4

* based on @HotLicks's comment *

Avoid all those annoying dateComponents all together - about 95% faster

I'm not sure about how consistent Option 4 is in practice, but it's clocking in the fastest and it has been reliable for me so far.

NSDate* originDate4 = [[NSDate alloc] init];
NSDate* date4 = [[NSDate alloc] init];
float date4Interval = [date4 timeIntervalSince1970];
int totalDays = date4Interval / (60 * 60 * 24);
date4 = [NSDate dateWithTimeIntervalSince1970:totalDays * 60 * 60 * 24];
NSLog(@"Time 4: %f", -[originDate4 timeIntervalSinceNow]);
Community
  • 1
  • 1
Logan
  • 52,262
  • 20
  • 99
  • 128
  • Gah! Excluding the components, good point,. I'm still concerned with the relative expense of getting the date object from the components compared to my overall method. It's a bottleneck at the moment. I'll profile again in the morning and see. – androider Mar 05 '14 at 19:52
  • I think I found a way to get significantly faster results by initializing the calendar ahead of time. Let me know if it works on your end as well! – Logan Mar 05 '14 at 21:43
  • Reusing the NSCalendar instance is something i do alright so that's not part of the bottleneck. It's interesting to see the timing profiles you have. I'm hoping they are replicated on my side. Soon find out! – androider Mar 06 '14 at 00:37
  • Ya, I'm not sure if its the best practice to time that way, but I think its alright for good estimates. – Logan Mar 06 '14 at 00:53
  • I have marked this as the answer which is Option 4 based on @HotLicks comment. Note my comment above of slightly off results when this approach is tweaked to keep the hour values. To keep the hour values I initialise my NSDateComponents and the add the Hour*60 to the final NSDateInterval before converting back to date. This got rid of the [calendar dateFromComponents:components] call which was a big hog. – androider Mar 06 '14 at 09:52
  • @androider - Of course, to keep the hour value you could just multiply/divide by 3600. – Hot Licks Mar 06 '14 at 12:32
  • I did just that. Calling [NsDate datewithtimeintervalsince1970] was giving me back the unexpected values. Didn't want to spend more time looking at it today. Thanks for the help, you should have posted as an answer! :-) – androider Mar 06 '14 at 12:36
  • Hey Logan, I won't vote down on this, but same error might occur here. do to DST issues this code might return unexpected results. – vikingosegundo Apr 14 '14 at 00:00
  • I am not looking for answers to down vote, or did I down vote? I am just looking for problems this constant might arise. if someone asked to break down a time duration in day, it must at least be mentioned, that the usage of this constant is not always returning what is expected. – vikingosegundo Apr 14 '14 at 00:41
  • Great answer by @HotLicks! Although this may not work when it comes to daylight savings time as some days may have 23 or 25 hours. – Rhuari Glen Dec 13 '18 at 10:27