문제

ISO-8601이 포함 된 문자열을 구문 분석하는 쉬운 방법을 찾고 있습니다. 지속 객관적인 C. 결과는 NSTimeInterval.

ISO-8601 기간의 예 : P1DT13H24M17S, 이것은 1 일, 13 시간, 24 분, 17 초를 의미합니다.

도움이 되었습니까?

해결책

어떤 필드를 얻을 것인지 정확히 알고 있다면 하나의 호출을 사용할 수 있습니다. 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

필드를 생략 할 수 있다면 구문 분석에서 조금 더 똑똑해야합니다.

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;

다른 팁

순수한 목표 C 버전 ...

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]);

이 버전은 오류없이 모든 YouTube 지속 시간을 구문 분석합니다.
중요한: 이 버전은 ARC를 사용합니다.

- (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 구현: https://github.com/igor-palaguta/youtubeengine/blob/swift-2.3/youtubeengine/classes/parser/nsdatecomponents+iso8601.swift

예시:let components = NSDateComponents(ISO8601String: "P1Y2M3DT4H5M6S")

테스트 : https://github.com/igor-palaguta/youtubeengine/blob/swift-2.3/example/tests/iso8601duraytests.swift

또한 "P1M"및 "PT1M"케이스도 올바르게 처리합니다.

스위프트 3 구현:https://github.com/igor-palaguta/youtubeengine/blob/mas

예시:let components = dateComponents(ISO8601String: "P1Y2M3DT4H5M6S")

테스트 :https://github.com/igor-palaguta/youtubeengine/blob/mas

업데이트 20.01.2017 : 몇 주 동안 지원이 추가되었습니다

사용자의 약간 수정 기능

세르게이 페카

+ (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];
}

다음은 Swift의 예입니다. (몇 시간, 몇 초만)

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)
}

다음은 Swift 3 버전의 Headkaze 예입니다.이 형식은 내 경우에 가장 적합했습니다.

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)

}

나는 이것을 찾았다 위키 백과 기사 ISO-8601이 실제로 어떻게 작동하는지에 대한 참조. 나는 코코아 전문가는 아니지만 그 문자열을 구문 분석하고 구성 요소 시간, 분, 두 번째, 하루 등을 추출 할 수 있다면 내기를 베팅하면 NstimeInterval에 들어가는 것이 쉬워야합니다. 까다로운 부분은 그것을 구문 분석하는 것입니다. 나는 아마 이것과 같이 할 것입니다 :

먼저, 문자열을 두 개의 개별 문자열로 나눕니다. 하나는 날을 나타내고 하나는 시간을 나타냅니다. NSString은 인스턴스 메소드 구성 요소 SSEPARATEDBYSTRING : NSSTRING가 전달한 매개 변수로 분리 된 원래 NSString의 NSARRAY를 반환합니다. 이와 같이 보일 것입니다.

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

다음으로, 가능한 일일 기간 지표 (y, m, w 및 d)에 대해 ISO8601Parts의 첫 번째 요소를 검색하십시오. 하나를 찾을 때, 모든 선행 숫자 (그리고 아마도 소수점)를 잡고 부유물에 던져서 어딘가에 보관하십시오. 시간 요소 만 있으면 iso8601parts [0]이 빈 문자열이 될 것입니다.

그런 다음 가능한 시간 표시기 (H, M, S)를 위해 ISO8601Parts의 두 번째 요소에서 시간 부품을 찾는 것과 동일한 작업을 수행하십시오. 하루 구성 요소 만 있으면 (즉, 원래 문자열에 'T'문자가 없음), ISO8601Parts는 길이가 하나 일 것이며 두 번째 요소에 액세스하려는 시도는 경계 예외를 유발합니다. .

NSTIMEINTERVAL은 몇 초만 저장하는 긴 저장이므로 꺼낸 개별 조각을 몇 초로 변환하고 함께 추가하고 NSTIMEINTERVAL에 보관하면 설정됩니다.

죄송합니다. "쉬운"방법을 요청했지만 API에 대한 검색과 지식을 기반으로하는 가장 쉬운 방법입니다.

빠르고 더러운 구현

    - (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;
}

이미 답변이 있지만 결국 다른 버전을 구현하게되었습니다. NSScanner. 이 버전은 연도와 월이 몇 초로 변환 될 수 없기 때문에 무시합니다.

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;
}

지금에 Swift! (예, 조금 길지만 모든 사례와 단수/복수를 처리합니다).

연도, 몇 달, 몇 주, 요일, 시간, 몇 분 및 초를 처리합니다!

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
}

스위프트 4.2 버전

몇 년, 달, 요일, 시간, 몇 분, 초로 일합니다. 초는 플로트 번호가 될 수 있습니다.

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)
}      

기간 구조물 :

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
}

}

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top