How do I break down an NSTimeInterval into year, months, days, hours, minutes and seconds on iPhone?

IphoneCocoaTimeNstimeinterval

Iphone Problem Overview


I have a time interval that spans years and I want all the time components from year down to seconds.

My first thought is to integer divide the time interval by seconds in a year, subtract that from a running total of seconds, divide that by seconds in a month, subtract that from the running total and so on.

That just seems convoluted and I've read that whenever you are doing something that looks convoluted, there is probably a built-in method.

Is there?

I integrated Alex's 2nd method into my code.

It's in a method called by a UIDatePicker in my interface.

NSDate *now = [NSDate date];
NSDate *then = self.datePicker.date;
NSTimeInterval howLong = [now timeIntervalSinceDate:then];

NSDate *date = [NSDate dateWithTimeIntervalSince1970:howLong];
NSString *dateStr = [date description];
const char *dateStrPtr = [dateStr UTF8String];
int year, month, day, hour, minute, sec;

sscanf(dateStrPtr, "%d-%d-%d %d:%d:%d", &year, &month, &day, &hour, &minute, &sec);
year -= 1970;

NSLog(@"%d years\n%d months\n%d days\n%d hours\n%d minutes\n%d seconds", year, month, day, hour, minute, sec);

When I set the date picker to a date 1 year and 1 day in the past, I get:

> 1 years 1 months 1 days 16 hours 0 > minutes 20 seconds

which is 1 month and 16 hours off. If I set the date picker to 1 day in the past, I am off by the same amount.

Update: I have an app that calculates your age in years, given your birthday (set from a UIDatePicker), yet it was often off. This proves there was an inaccuracy, but I can't figure out where it comes from, can you?

Iphone Solutions


Solution 1 - Iphone

Brief Description

  1. Just another approach to complete the answer of JBRWilkinson but adding some code. It can also offers a solution to Alex Reynolds's comment.

  2. Use NSCalendar method:

  • (NSDateComponents *)components:(NSUInteger)unitFlags fromDate:(NSDate *)startingDate toDate:(NSDate *)resultDate options:(NSUInteger)opts

  • "Returns, as an NSDateComponents object using specified components, the difference between two supplied dates". (From the API documentation).

  1. Create 2 NSDate whose difference is the NSTimeInterval you want to break down. (If your NSTimeInterval comes from comparing 2 NSDate you don't need to do this step, and you don't even need the NSTimeInterval, just apply the dates to the NSCalendar method).

  2. Get your quotes from NSDateComponents

Sample Code

// The time interval 
NSTimeInterval theTimeInterval = ...;

// Get the system calendar
NSCalendar *sysCalendar = [NSCalendar currentCalendar];

// Create the NSDates
NSDate *date1 = [[NSDate alloc] init];
NSDate *date2 = [[NSDate alloc] initWithTimeInterval:theTimeInterval sinceDate:date1]; 

// Get conversion to months, days, hours, minutes
NSCalendarUnit unitFlags = NSHourCalendarUnit | NSMinuteCalendarUnit | NSDayCalendarUnit | NSMonthCalendarUnit;
	
NSDateComponents *breakdownInfo = [sysCalendar components:unitFlags fromDate:date1  toDate:date2  options:0];
NSLog(@"Break down: %i min : %i hours : %i days : %i months", [breakdownInfo minute], [breakdownInfo hour], [breakdownInfo day], [breakdownInfo month]);

Solution 2 - Iphone

This code is aware of day light saving times and other possible nasty things.

NSCalendar *gregorianCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *components = [gregorianCalendar components: (NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit | NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit )
                                                    fromDate:startDate
                                                      toDate:[NSDate date]
                                                     options:0];


NSLog(@"%ld", [components year]);
NSLog(@"%ld", [components month]);
NSLog(@"%ld", [components day]);
NSLog(@"%ld", [components hour]);
NSLog(@"%ld", [components minute]);
NSLog(@"%ld", [components second]);

Solution 3 - Iphone

From iOS8 and above you can use NSDateComponentsFormatter

It has methods to convert time difference in user friendly formatted string.

NSDateComponentsFormatter *formatter = [[NSDateComponentsFormatter alloc] init];
formatter.unitsStyle = NSDateComponentsFormatterUnitsStyleFull;
    
NSLog(@"%@", [formatter stringFromTimeInterval:1623452]);

This gives the output - 2 weeks, 4 days, 18 hours, 57 minutes, 32 seconds

Solution 4 - Iphone

Convert your interval into an NSDate using +dateWithIntervalSince1970, get the date components out of that using NSCalendar's -componentsFromDate method.

SDK Reference

Solution 5 - Iphone

This works for me:

	float *lenghInSeconds = 2345.234513;
	NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:lenghInSeconds];
	NSDateFormatter *formatter = [[NSDateFormatter alloc] init];


	[formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];

	[formatter setDateFormat:@"HH:mm:ss"];
	NSLog(@"%@", [formatter stringFromDate:date]); 
	[formatter release];

The main difference here is that you need to adjust for the timezone.

Solution 6 - Iphone

Or there is my class method. It doesn't handle years, but that could easily be addedn though it's better for small timelaps like days, hours and minutes. It take plurals into account and only shows what's needed:

+(NSString *)TimeRemainingUntilDate:(NSDate *)date {
    
    NSTimeInterval interval = [date timeIntervalSinceNow];
    NSString * timeRemaining = nil;
    
    if (interval > 0) {
        
        div_t d = div(interval, 86400);
        int day = d.quot;
        div_t h = div(d.rem, 3600);
        int hour = h.quot;
        div_t m = div(h.rem, 60);
        int min = m.quot;
        
        NSString * nbday = nil;
        if(day > 1)
            nbday = @"days";
        else if(day == 1)
            nbday = @"day";
        else
            nbday = @"";
        NSString * nbhour = nil;
        if(hour > 1)
            nbhour = @"hours";
        else if (hour == 1)
            nbhour = @"hour";
        else
            nbhour = @"";
        NSString * nbmin = nil;
        if(min > 1)
            nbmin = @"mins";
        else
            nbmin = @"min";

        timeRemaining = [NSString stringWithFormat:@"%@%@ %@%@ %@%@",day ? [NSNumber numberWithInt:day] : @"",nbday,hour ? [NSNumber numberWithInt:hour] : @"",nbhour,min ? [NSNumber numberWithInt:min] : @"00",nbmin];
    }
    else
        timeRemaining = @"Over";
    
    return timeRemaining;
}

Solution 7 - Iphone

NSDate *date = [NSDate dateWithTimeIntervalSince1970:timeInterval];

// format: YYYY-MM-DD HH:MM:SS ±HHMM
NSString *dateStr = [date description];
NSRange range;

// year
range.location = 0;
range.length = 4;
NSString *yearStr = [dateStr substringWithRange:range];
int year = [yearStr intValue] - 1970;

// month
range.location = 5;
range.length = 2;
NSString *monthStr = [dateStr substringWithRange:range];
int month = [monthStr intValue];

// day, etc.
...

Solution 8 - Iphone

- (NSString *)convertTimeFromSeconds:(NSString *)seconds {
    
    // Return variable.
    NSString *result = @"";

    // Int variables for calculation.
    int secs = [seconds intValue];
    int tempHour    = 0;
    int tempMinute  = 0;
    int tempSecond  = 0;
 
    NSString *hour      = @"";
    NSString *minute    = @"";
    NSString *second    = @"";

    // Convert the seconds to hours, minutes and seconds.
    tempHour    = secs / 3600;
    tempMinute  = secs / 60 - tempHour * 60;
    tempSecond  = secs - (tempHour * 3600 + tempMinute * 60);
    
    hour    = [[NSNumber numberWithInt:tempHour] stringValue];
    minute  = [[NSNumber numberWithInt:tempMinute] stringValue];
    second  = [[NSNumber numberWithInt:tempSecond] stringValue];
    
    // Make time look like 00:00:00 and not 0:0:0
    if (tempHour < 10) {
        hour = [@"0" stringByAppendingString:hour];
    } 
    
    if (tempMinute < 10) {
        minute = [@"0" stringByAppendingString:minute];
    }
    
    if (tempSecond < 10) {
        second = [@"0" stringByAppendingString:second];
    }
    
    if (tempHour == 0) {
        
        NSLog(@"Result of Time Conversion: %@:%@", minute, second);
        result = [NSString stringWithFormat:@"%@:%@", minute, second];
        
    } else {
       
        NSLog(@"Result of Time Conversion: %@:%@:%@", hour, minute, second); 
        result = [NSString stringWithFormat:@"%@:%@:%@",hour, minute, second];
        
    }
    
    return result;

}

Solution 9 - Iphone

Here's another possibility, somewhat cleaner:

NSDate *date = [NSDate dateWithTimeIntervalSince1970:timeInterval];
NSString *dateStr = [date description];
const char *dateStrPtr = [dateStr UTF8String];

// format: YYYY-MM-DD HH:MM:SS ±HHMM
int year, month, day, hour, minutes, seconds;
sscanf(dateStrPtr, "%d-%d-%d %d:%d:%d", &year, &month, &day, &hour, &minutes, &seconds);
year -= 1970;

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionwillc2View Question on Stackoverflow
Solution 1 - IphoneAlbaregarView Answer on Stackoverflow
Solution 2 - IphonevikingosegundoView Answer on Stackoverflow
Solution 3 - IphoneOmkarView Answer on Stackoverflow
Solution 4 - IphoneJBRWilkinsonView Answer on Stackoverflow
Solution 5 - IphonedoogilasovichView Answer on Stackoverflow
Solution 6 - IphoneNicolas ManziniView Answer on Stackoverflow
Solution 7 - IphoneAlex ReynoldsView Answer on Stackoverflow
Solution 8 - Iphonetaus-iDeveloperView Answer on Stackoverflow
Solution 9 - IphoneAlex ReynoldsView Answer on Stackoverflow