views:

1018

answers:

4

I'm looking for an easy way to parse a string that contains an ISO-8601 duration in Objective C. The result should be something usable like a NSTimeInterval.

An example of an ISO-8601 duration: P1DT13H24M17S, which means 1 day, 13 hours, 24 minutes and 17 seconds.

A: 

If all else fails, Objective C is C underneath when you need it. If you have to write your own parser it shouldn't be all that hard.

Joshua
A: 

I looked up this Wikipedia article for a reference to how ISO-8601 actually works. I'm no Cocoa expert, but I'm betting if you can parse that string and extract the component hour, minute, second, day, etc., getting it in to an NSTimeInterval should be easy. The tricky part is parsing it. I'd probably do it something like this:

First, split the string in to two separate strings: one representing the days, and one representing the times. NSString has an instance method componentsSeparatedByString:NSString that returns an NSArray of substrings of your original NSString separated by the parameter you pass in. It would look something like this:

NSString* iso8601 = /*However you're getting your string in*/
NSArray* iso8601Parts = [iso8601 componentsSeparatedByString:@"T"];

Next, search the first element of iso8601Parts for each of the possible day duration indicators (Y, M, W, and D). When you find one, grab all the preceeding digits (and possibly a decimal point), cast them to a float, and store them somewhere. Remember that if there was only a time element, then iso8601Parts[0] will be the empty string.

Then, do the same thing looking for time parts in the second element of iso8601Parts for possible time indicators (H, M, S). Remember that if there was only a day component (that is, there was no 'T' character in the original string), then iso8601Parts will only be of length one, and an attempt to access the second element will cause an out of bounds exception.

An NSTimeInterval is just a long storing a number of seconds, so convert the individual pieces you pulled out in to seconds, add them together, store them in your NSTimeInterval, and you're set.

Sorry, I know you asked for an "easy" way to do it, but based on my (admittedly light) searching around and knowledge of the API, this is the easiest way to do it.

Warren Pena
+2  A: 

If you know exactly which fields you'll be getting, you can use one invocation of sscanf():

const char *stringToParse = ...;
int days, hours, minutes, seconds;
NSTimeInterval interval;
if(sscanf(stringToParse, "P%dDT%dH%dM%sS", &days, &hours, &minutes, &seconds) == 4)
    interval = ((days * 24 + hours) * 60 + minutes) * 60 + seconds;
else
    ; // handle error, parsing failed

If any of the fields might be omitted, you'll need to be a little smarter in your parsing, e.g.:

const char *stringToParse = ...;
int days = 0, hours = 0, minutes = 0, seconds = 0;

const char *ptr = stringToParse;
while(*ptr)
{
    if(*ptr == 'P' || *ptr == 'T')
    {
        ptr++;
        continue;
    }

    int value, charsRead;
    char type;
    if(sscanf(ptr, "%d%c%n", &value, &type, &charsRead) != 2)
        ;  // handle parse error
    if(type == 'D')
        days = value;
    else if(type == 'H')
        hours = value;
    else if(type == 'M')
        minutes = value;
    else if(type == 'S')
        seconds = value;
    else
        ;  // handle invalid type

    ptr += charsRead;
}

NSTimeInterval interval = ((days * 24 + hours) * 60 + minutes) * 60 + seconds;
Adam Rosenfield
writing a small parser wasn't exactly what I had in mind when I started to search for an easy solution, but it seems that there indeed is no other way. I thought there could be a trick using a special format string with NSDateFormatter, but it doesn't seem to work with time intervals. Anyway, I ended up writing a similar parser. Thanks everybody for helping.
Andreas
A: 

This is an old thread, but for anyone else running into this problem you should check this out http://github.com/square/iso8601parser

It definitely solved my problem.

Mark Sands