Pergunta

Estou procurando uma maneira fácil de analisar uma string que contém um ISO-8601 duração no objetivo C. o resultado deve ser algo utilizável como um NSTimeInterval.

Um exemplo de uma duração ISO-8601: P1DT13H24M17S, que significa 1 dia, 13 horas, 24 minutos e 17 segundos.

Foi útil?

Solução

Se você souber exatamente quais campos estará obtendo, você pode usar uma invocação de 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

Se algum dos campos puder ser omitido, você precisará ser um pouco mais inteligente em sua análise, por exemplo:

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;

Outras dicas

Uma versão C de Objetivo Puro ...

NSString *duration = @"P1DT10H15M49S";

int i = 0, days = 0, hours = 0, minutes = 0, seconds = 0;

while(i < duration.length)
{
    NSString *str = [duration substringWithRange:NSMakeRange(i, duration.length-i)];

    i++;

    if([str hasPrefix:@"P"] || [str hasPrefix:@"T"])
        continue;

    NSScanner *sc = [NSScanner scannerWithString:str];
    int value = 0;

    if ([sc scanInt:&value])
    {
        i += [sc scanLocation]-1;

        str = [duration substringWithRange:NSMakeRange(i, duration.length-i)];

        i++;

        if([str hasPrefix:@"D"])
            days = value;
        else if([str hasPrefix:@"H"])
            hours = value;
        else if([str hasPrefix:@"M"])
            minutes = value;
        else if([str hasPrefix:@"S"])
            seconds = value;
    }
}

NSLog(@"%@", [NSString stringWithFormat:@"%d days, %d hours, %d mins, %d seconds", days, hours, minutes, seconds]);

Esta versão analisa toda duração do YouTube sem erros.
Importante: Esta versão usa arco.

- (NSString*)parseISO8601Time:(NSString*)duration
{
    NSInteger hours = 0;
    NSInteger minutes = 0;
    NSInteger seconds = 0;

    //Get Time part from ISO 8601 formatted duration http://en.wikipedia.org/wiki/ISO_8601#Durations
    duration = [duration substringFromIndex:[duration rangeOfString:@"T"].location];

    while ([duration length] > 1) { //only one letter remains after parsing
        duration = [duration substringFromIndex:1];

        NSScanner *scanner = [[NSScanner alloc] initWithString:duration];

        NSString *durationPart = [[NSString alloc] init];
        [scanner scanCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@"0123456789"] intoString:&durationPart];

        NSRange rangeOfDurationPart = [duration rangeOfString:durationPart];

        duration = [duration substringFromIndex:rangeOfDurationPart.location + rangeOfDurationPart.length];

        if ([[duration substringToIndex:1] isEqualToString:@"H"]) {
            hours = [durationPart intValue];
        }
        if ([[duration substringToIndex:1] isEqualToString:@"M"]) {
            minutes = [durationPart intValue];
        }
        if ([[duration substringToIndex:1] isEqualToString:@"S"]) {
            seconds = [durationPart intValue];
        }
    }

    return [NSString stringWithFormat:@"%02d:%02d:%02d", hours, minutes, seconds];
}

Swift2 implementação: https://github.com/igor-palaguta/youtubeengine/blob/swift-2.3/youtubeengine/classes/parser/nsdatecomponents+iso8601.swift

Exemplo:let components = NSDateComponents(ISO8601String: "P1Y2M3DT4H5M6S")

Testes: https://github.com/igor-palaguta/youtubeengine/blob/swift-2.3/example/tests/iso8601DurationTests.swift

Também lida corretamente "casos P1M e" Pt1M "

Swift3 implementação:https://github.com/igor-palaguta/youtubeengine/blob/master/source/youtubeengine/parser/nsdatecomponent%2biso8601.swift

Exemplo:let components = dateComponents(ISO8601String: "P1Y2M3DT4H5M6S")

Testes:https://github.com/igor-palaguta/youtubeengine/blob/master/tests/youtubeenginetests/iso8601durationtests.swift

Atualização 20.01.2017: Adicionado suporte para semanas

Função ligeiramente modificadora do usuário

Sergei Pekar

+ (NSString*)parseISO8601Time:(NSString*)duration
{
    NSInteger hours = 0;
    NSInteger minutes = 0;
    NSInteger seconds = 0;

    //Get Time part from ISO 8601 formatted duration http://en.wikipedia.org/wiki/ISO_8601#Durations
    if ([duration rangeOfString:@"T"].location == NSNotFound || [duration rangeOfString:@"P"].location == NSNotFound) {
        NSLog(@"Time is not a part from ISO 8601 formatted duration");
        return @"0:00 Error";
    }

    duration = [duration substringFromIndex:[duration rangeOfString:@"T"].location];

    while ([duration length] > 1) { //only one letter remains after parsing
        duration = [duration substringFromIndex:1];

        NSScanner *scanner = [[NSScanner alloc] initWithString:duration];
        NSString *durationPart = [[NSString alloc] init];
        [scanner scanCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@"0123456789"] intoString:&durationPart];

        NSRange rangeOfDurationPart = [duration rangeOfString:durationPart];

        if ((rangeOfDurationPart.location + rangeOfDurationPart.length) > duration.length) {
            NSLog(@"Time is not a part from ISO 8601 formatted duration");
            return @"0:00 Error";
        }

        duration = [duration substringFromIndex:rangeOfDurationPart.location + rangeOfDurationPart.length];

        if ([[duration substringToIndex:1] isEqualToString:@"H"]) {
            hours = [durationPart intValue];
        }
        if ([[duration substringToIndex:1] isEqualToString:@"M"]) {
            minutes = [durationPart intValue];
        }
        if ([[duration substringToIndex:1] isEqualToString:@"S"]) {
            seconds = [durationPart intValue];
        }
    }

    if (hours != 0)
        return [NSString stringWithFormat:@"%ld:%02ld:%02ld", (long)hours, (long)minutes, (long)seconds];
    else
        return [NSString stringWithFormat:@"%ld:%02ld", (long)minutes, (long)seconds];
}

Aqui está um exemplo para Swift: (apenas por horas, minutos e segundos)

func parseDuration(duration: String) -> Int {

var days = 0
var hours = 0
var minutes = 0
var seconds = 0

var decisionMaker = 0
var factor = 1

let specifiers: [Character] = ["M", "H", "T", "P"]

let length = count(duration)

for i in 1...length {

    let index = advance(duration.startIndex, length - i)
    let char = duration[index]

    for specifier in specifiers {
        if char == specifier {
            decisionMaker++
            factor = 1
        }
    }

    if let value = String(char).toInt() {

        switch decisionMaker {
            case 0:
                seconds += value * factor
                factor *= 10
            case 1:
                minutes += value * factor
                factor *= 10
            case 2:
                hours += value * factor
                factor *= 10
            case 4:
                days += value * factor
                factor *= 10
            default:
                break
        }
    }

}

return seconds + (minutes * 60) + (hours * 3600) + (days * 3600 * 24)
}

Aqui está o Swift 3 versão do Headkaze Exemplo: Este formato foi mais adequado no meu caso:

private func parseISO8601Time(iso8601: String) -> String {

    let nsISO8601 = NSString(string: iso8601)

    var days = 0, hours = 0, minutes = 0, seconds = 0
    var i = 0

    while i < nsISO8601.length  {

        var str = nsISO8601.substring(with: NSRange(location: i, length: nsISO8601.length - i))

        i += 1

        if str.hasPrefix("P") || str.hasPrefix("T") { continue }

        let scanner = Scanner(string: str)
        var value = 0

        if scanner.scanInt(&value) {

            i += scanner.scanLocation - 1

            str = nsISO8601.substring(with: NSRange(location: i, length: nsISO8601.length - i))

            i += 1

            if str.hasPrefix("D") {
                days = value
            } else if str.hasPrefix("H") {
                hours = value
            } else if str.hasPrefix("M") {
                minutes = value
            } else if str.hasPrefix("S") {
                seconds = value
            }
        }
    }

    if days > 0 {
        hours += 24 * days
    }

    if hours > 0 {
        return String(format: "%d:%02d:%02d", hours, minutes, seconds)
    }

    return String(format: "%d:%02d", minutes, seconds)

}

Eu olhei para cima isso Artigo da Wikipedia Para uma referência a como o ISO-8601 realmente funciona. Não sou especialista em cacau, mas estou apostando se você pode analisar essa corda e extrair a hora do componente, minuto, segundo, dia etc., colocá -la em um número interinterval deve ser fácil. A parte complicada é analisá -lo. Eu provavelmente faria algo assim:

Primeiro, divida a corda em duas cordas separadas: uma representando os dias e outra representando os tempos. O NSString possui um método de instância componentSeparatedBystring: NSString que retorna um NSARRAY de substrings do seu NSString original separado pelo parâmetro que você passa. Pareceria algo assim:

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

Em seguida, pesquise o primeiro elemento do ISO8601PARTS para cada um dos possíveis indicadores de duração do dia (Y, M, W e D). Quando você encontrar um, pegue todos os dígitos precedentes (e possivelmente um ponto decimal), lance -os a um bóia e armazene -os em algum lugar. Lembre -se de que, se houvesse apenas um elemento de tempo, o ISO8601PARTS [0] será a sequência vazia.

Em seguida, faça a mesma coisa que procura peças de tempo no segundo elemento do ISO8601PARTS para possíveis indicadores de tempo (H, M, S). Lembre -se de que, se houvesse apenas um componente de dia (ou seja, não havia caráter 'T' na string original), então ISO8601Parts será apenas do comprimento um, e uma tentativa de acessar o segundo elemento causará uma exceção fora dos limites .

Um NSTimeInterval é apenas um longo armazenamento de vários segundos; portanto, converta as peças individuais em que você puxou em segundos, adicione -as, armazene -as no seu tempo interinterval e você está definido.

Desculpe, eu sei que você pediu uma maneira "fácil" de fazê -lo, mas com base na minha (reconhecidamente leve) pesquisando e no conhecimento da API, essa é a maneira mais fácil de fazê -lo.

Implementação rápida e suja

    - (NSInteger)integerFromYoutubeDurationString:(NSString*)duration{

    if(duration == nil){
        return 0;
    }

    NSString *startConst = @"PT";
    NSString *hoursConst = @"H";
    NSString *minutesConst = @"M";
    NSString *secondsConst = @"S";
    NSString *hours = nil;
    NSString *minutes = nil;
    NSString *seconds = nil;
    NSInteger totalSeconds = 0;

    NSString *clean = [duration componentsSeparatedByString:startConst][1];

    if([clean containsString:hoursConst]){
        hours = [clean componentsSeparatedByString:hoursConst][0];
        clean = [clean componentsSeparatedByString:hoursConst][1];
        totalSeconds = [hours integerValue]*3600;
    }
    if([clean containsString:minutesConst]){
        minutes = [clean componentsSeparatedByString:minutesConst][0];
        clean = [clean componentsSeparatedByString:minutesConst][1];
        totalSeconds = totalSeconds + [minutes integerValue]*60;
    }
    if([clean containsString:secondsConst]){
        seconds = [clean componentsSeparatedByString:secondsConst][0];
        totalSeconds = totalSeconds + [seconds integerValue];
    }

    return totalSeconds;
}

Já existem respostas, mas acabei implementando mais uma versão usando NSScanner. Esta versão ignora ano e mês, pois eles não podem ser convertidos em número de segundos.

static NSTimeInterval timeIntervalFromISO8601Duration(NSString *duration) {
    NSTimeInterval timeInterval = 0;
    NSScanner *scanner = [NSScanner scannerWithString:duration];

    NSCharacterSet *designators = [NSCharacterSet characterSetWithCharactersInString:@"PYMWDTHMS"];
    BOOL isScanningTime = NO;

    while (![scanner isAtEnd]) {
        double scannedNumber = 0;
        BOOL didScanNumber = [scanner scanDouble:&scannedNumber];

        NSString *scanned = nil;
        if ([scanner scanCharactersFromSet:designators intoString:&scanned]) {
            if (didScanNumber) {
                switch ([scanned characterAtIndex:0]) {
                    case 'D':
                        timeInterval += scannedNumber * 60 * 60 * 24;
                        break;
                    case 'H':
                        timeInterval += scannedNumber * 60 * 60;
                        break;
                    case 'M':
                        if (isScanningTime) {
                            timeInterval += scannedNumber * 60;
                        }
                        break;
                    case 'S':
                        timeInterval += scannedNumber;
                        break;
                    default:
                        break;
                }
            }

            if ([scanned containsString:@"T"]) {
                isScanningTime = YES;
            }
        }
    }

    return timeInterval;
}

Agora em Swift! (Sim, é um pouco longo, mas lida com todos os casos e singular/plural).

Lida anos, meses, semanas, dias, horas, minutos e segundos!

func convertFromISO8601Duration(isoValue: AnyObject) -> String? {

    var displayedString: String?
    var hasHitTimeSection = false
    var isSingular = false

    if let isoString = isoValue as? String {

        displayedString = String()

        for val in isoString {


            if val == "P" {
                // Do nothing when parsing the 'P'
                continue

            }else if val == "T" {
                // Indicate that we are now dealing with the 'time section' of the ISO8601 duration, then carry on.
                hasHitTimeSection = true
                continue
            }

            var tempString = String()

            if val >= "0" && val <= "9" {

                // We need to know whether or not the value is singular ('1') or not ('11', '23').
                if let safeDisplayedString = displayedString as String!
                    where count(displayedString!) > 0 && val == "1" {

                    let lastIndex = count(safeDisplayedString) - 1

                    let lastChar = safeDisplayedString[advance(safeDisplayedString.startIndex, lastIndex)]

                        //test if the current last char in the displayed string is a space (" "). If it is then we will say it's singular until proven otherwise.
                    if lastChar == " " {
                        isSingular = true
                    } else {
                        isSingular = false
                    }
                }
                else if val == "1" {
                    // if we are just dealing with a '1' then we will say it's singular until proven otherwise.
                    isSingular = true
                }
                else {
                    // ...otherwise it's a plural duration.
                    isSingular = false
                }

                tempString += "\(val)"

                displayedString! += tempString

            } else {

                // handle the duration type text. Make sure to use Months & Minutes correctly.
                switch val {

                case "Y", "y":

                    if isSingular {
                        tempString += " Year "
                    } else {
                        tempString += " Years "
                    }

                    break

                case "M", "m":

                    if hasHitTimeSection {

                        if isSingular {
                            tempString += " Minute "
                        } else {
                            tempString += " Minutes "
                        }
                    }
                    else {

                        if isSingular {
                            tempString += " Month "
                        } else {
                            tempString += " Months "
                        }
                    }

                    break

                case "W", "w":

                    if isSingular {
                        tempString += " Week "
                    } else {
                        tempString += " Weeks "
                    }

                    break

                case "D", "d":

                    if isSingular {
                        tempString += " Day "
                    } else {
                        tempString += " Days "
                    }

                    break

                case "H", "h":

                    if isSingular {
                        tempString += " Hour "
                    } else {
                        tempString += " Hours "
                    }

                    break

                case "S", "s":

                    if isSingular {
                        tempString += " Second "
                    } else {
                        tempString += " Seconds "
                    }

                    break

                default:
                    break

                }

                // reset our singular flag, since we're starting a new duration.
                isSingular = false

                displayedString! += tempString

            }

        }

    }

    return displayedString
}

Versão Swift 4.2

Trabalha com anos, meses, dias, horas, minutos, segundos. Segundos podem ser o número de flutuação.

extension String{
    public func parseISO8601Time() -> Duration {

        let nsISO8601 = NSString(string: self)

        var days = 0, hours = 0, minutes = 0, seconds: Float = 0, weeks = 0, months = 0, years = 0
        var i = 0

        var beforeT:Bool = true

        while i < nsISO8601.length  {

            var str = nsISO8601.substring(with: NSRange(location: i, length: nsISO8601.length - i))

            i += 1

            if str.hasPrefix("P") || str.hasPrefix("T") {
                beforeT = !str.hasPrefix("T")
                continue
            }

            let scanner = Scanner(string: str)
            var value: Float = 0

            if scanner.scanFloat(&value) {

                i += scanner.scanLocation - 1

                str = nsISO8601.substring(with: NSRange(location: i, length: nsISO8601.length - i))

                i += 1

                if str.hasPrefix("Y") {
                    years = Int(value)
                } else if str.hasPrefix("M") {
                    if beforeT{
                        months = Int(value)
                    }else{
                        minutes = Int(value)
                    }
                } else if str.hasPrefix("W") {
                    weeks = Int(value)
                } else if str.hasPrefix("D") {
                    days = Int(value)
                } else if str.hasPrefix("H") {
                    hours = Int(value)
                } else if str.hasPrefix("S") {
                    seconds = value
                }
            }
        }

        return Duration(years: years, months: months, weeks: weeks, days: days, hours: hours, minutes: minutes, seconds: seconds)
}      

Estrutura de duração:

public struct Duration {

let daysInMonth: Int = 30
let daysInYear: Int = 365

var years: Int
var months: Int
var weeks: Int
var days: Int
var hours: Int
var minutes: Int
var seconds: Float

public func getMilliseconds() -> Int{
    return Int(round(seconds*1000)) + minutes*60*1000 + hours*60*60*1000 + days*24*60*60*1000 + weeks*7*24*60*60*1000 + months*daysInMonth*24*60*60*1000 + years*daysInYear*24*60*60*1000
}

public func getFormattedString() -> String{

    var formattedString = ""

    if years != 0{
        formattedString.append("\(years)")
        formattedString.append(" ")
        formattedString.append(years == 1 ? "year".localized() : "years".localized())
        formattedString.append(" ")
    }

    if months != 0{
        formattedString.append("\(months)")
        formattedString.append(" ")
        formattedString.append(months == 1 ? "month".localized() : "months".localized())
        formattedString.append(" ")
    }

    if weeks != 0{
        formattedString.append("\(weeks)")
        formattedString.append(" ")
        formattedString.append(weeks == 1 ? "week".localized() : "weeks".localized())
        formattedString.append(" ")
    }

    if days != 0{
        formattedString.append("\(days)")
        formattedString.append(" ")
        formattedString.append(days == 1 ? "day".localized() : "days".localized())
        formattedString.append(" ")
    }

    if seconds != 0{
        formattedString.append(String(format: "%02d:%02d:%.02f", hours, minutes, seconds))
    }else{
        formattedString.append(String(format: "%02d:%02d", hours, minutes))
    }

    return formattedString
}

}

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top