2009-07-18 62 views
14

我正在寻找一个简单的方法来分析包含了ISO-8601 时间在Objective C的结果应该是使用像NSTimeInterval的字符串。如何解析目标C中的ISO-8601持续时间?

的ISO-8601持续时间的一个例子:P1DT13H24M17S,这意味着1天,13小时,24分17秒。

回答

6

如果你确切地知道哪些字段,你会得到,你可以使用一个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; 
+0

当我开始寻找一个简单的解决方案时,写一个小的解析器并不是我想到的,但似乎确实没有其他方法。我认为使用NSDateFormatter的特殊格式字符串可能会有一些窍门,但它似乎不适用于时间间隔。无论如何,我最终写了一个类似的解析器。谢谢大家的帮助。 – Zargony 2009-07-23 07:18:34

+2

要记住上面的代码示例(是的,我意识到这可能意味着证明一个观点,而不是实际消费)。 ISO 8601标准允许将持续时间值指定为可以用“。”分隔的分数值(即1.5,0.6等)。要么 ”,”。 – 2012-04-17 02:54:53

0

我抬头一看这个Wikipedia article用于为ISO-8601如何实际工作的参考。我不是可可专家,但是我敢打赌,如果你能解析这个字符串并且提取组件的小时,分​​钟,秒,日等等,那么让它进入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)的所述第二元件寻找时间部分同样的事情。请记住,如果只有一天组件(即原始字符串中没有'T'字符),那么iso8601Parts的长度只有一个,尝试访问第二个元素将导致超出范围的异常。

NSTimeInterval只是一个很长的存储秒数,因此将您拉出的单个作品转换为秒,将它们添加到一起,将它们存储在NSTimeInterval中,然后进行设置。

对不起,我知道你问一个“简单”的方式做到这一点,但基于我的(当然光)搜索周围和API的知识,这是做到这一点的最简单的方法。

10

纯目标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]); 
+0

P1M失败 - (给1分钟,而应该返回1个月) – 2014-02-24 11:12:01

7

这VERSI在解析每一个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:@""] 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]; 
} 
+0

干得好。但是你必须提到它应该在ARC环境中运行。 – Nekto 2013-09-30 14:45:52

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

下面是迅速的例子: (仅几个小时,分钟和秒)

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

用户的略微修改功能

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:@""] 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]; 
} 
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 
} 
5

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/ISO8601DurationTests.swift

它还能够正确处理 “P1M” 和 “PT1M” 案件

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

例子: let components = dateComponents(ISO8601String: "P1Y2M3DT4H5M6S")

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

更新2017年1月20日:增加了对周

1

这里的支持是迅速的第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) 

}