14

我正在寻找一种简单的方法来解析在 Objective C 中包含 ISO-8601持续时间的字符串。结果应该是可用的,例如NSTimeInterval.

ISO-8601 持续时间示例:P1DT13H24M17S,表示 1 天 13 小时 24 分钟 17 秒。

4

12 回答 12

11

Swift3,4,5实现: https ://github.com/Igor-Palaguta/YoutubeEngine/blob/master/Source/YoutubeEngine/Parser/NSDateComponents%2BISO8601.swift

例子: let components = try DateComponents(ISO8601String: "P1Y2M3DT4H5M6S")

测试: https ://github.com/Igor-Palaguta/YoutubeEngine/blob/master/Tests/YoutubeEngineTests/ISO8601DurationTests.swift

更新:修复了 DougSwith 案例"P3W3DT20H31M21"

于 2016-08-05T12:25:58.937 回答
10

纯粹的 Objective 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]);
于 2012-12-08T08:48:57.573 回答
8

此版本解析每个 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];
}
于 2013-09-16T13:01:10.393 回答
7

如果您确切知道将获得哪些字段,则可以使用一次调用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;
于 2009-07-18T02:40:40.133 回答
3

稍微修改用户的功能

谢尔盖·佩卡尔

+ (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];
}
于 2015-07-22T14:37:12.010 回答
1

这是 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)
}
于 2015-06-10T11:33:16.147 回答
1

这是 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)

}
于 2017-03-03T13:44:55.543 回答
0

我查阅了这篇Wikipedia 文章,以获取有关 ISO-8601 实际工作原理的参考。我不是 Cocoa 专家,但我敢打赌,如果您可以解析该字符串并提取小时、分钟、秒、天等组件,那么将其放入 NSTimeInterval 应该很容易。棘手的部分是解析它。我可能会这样做:

首先,将字符串拆分为两个单独的字符串:一个代表天数,一个代表时间。NSString 有一个实例方法 componentsSeparatedByString:NSString ,它返回一个由您传入的参数分隔的原始 NSString 的子字符串的 NSArray。它看起来像这样:

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

接下来,在 iso8601Parts 的第一个元素中搜索每个可能的持续时间指标(Y、M、W 和 D)。当你找到一个时,抓取所有前面的数字(可能还有一个小数点),将它们转换为浮点数,并将它们存储在某个地方。请记住,如果只有一个时间元素,那么 iso8601Parts[0] 将是空字符串。

然后,做同样的事情,在 iso8601Parts 的第二个元素中寻找可能的时间指标(H、M、S)的时间部分。请记住,如果只有一个 day 组件(即原始字符串中没有 'T' 字符),那么 iso8601Parts 的长度仅为 1,并且尝试访问第二个元素将导致越界异常.

NSTimeInterval 只是一个很长的存储秒数,因此将您拉出的各个部分转换为秒,将它们加在一起,将它们存储在您的 NSTimeInterval 中,然后您就设置好了。

抱歉,我知道您要求一种“简单”的方法来做到这一点,但是根据我(诚然轻巧)的搜索和对 API 的了解,这是最简单的方法。

于 2009-07-18T02:19:11.900 回答
0

已经有了答案,但我最终使用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;
}
于 2015-04-07T19:57:37.973 回答
0

快速而肮脏的实施

    - (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;
}
于 2015-06-27T15:58:06.797 回答
0

现在在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
}
于 2015-09-02T23:12:23.067 回答
0

斯威夫特 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
}

}

于 2018-12-20T08:44:08.790 回答