我正在寻找一种简单的方法来解析在Objective C中包含ISO-8601 持续时间的字符串。结果应该像NSTimeInterval
一样可用。
ISO-8601持续时间的示例:P1DT13H24M17S
,表示1天,13小时,24分钟和17秒。
答案 0 :(得分: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]);
答案 1 :(得分:7)
此版本解析每个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];
}
答案 2 :(得分:7)
实施例:
let components = NSDateComponents(ISO8601String: "P1Y2M3DT4H5M6S")
它还能正确处理&#34; P1M&#34;和&#34; PT1M&#34;例
实施例:
let components = dateComponents(ISO8601String: "P1Y2M3DT4H5M6S")
2010年1月20日更新:增加了对数周的支持
答案 3 :(得分: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;
答案 4 :(得分: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:@"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];
}
答案 5 :(得分: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)
}
答案 6 :(得分:1)
这是headkaze示例的swift 3版本:这种格式在我的情况下最合适:
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)
}
答案 7 :(得分:0)
我查看了这个Wikipedia article以获取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)。请记住,如果只有一天组件(即原始字符串中没有'T'字符),那么iso8601Parts将只有一个长度,并且尝试访问第二个元素将导致超出范围的异常
NSTimeInterval只需很长时间存储几秒钟,因此将您拔出的各个部分转换为秒,将它们加在一起,将它们存储在NSTimeInterval中,然后进行设置。
很抱歉,我知道你要求一种“简单”的方法,但基于我的(无可辩驳的)搜索和API的知识,这是最简单的方法。
答案 8 :(得分: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;
}
答案 9 :(得分: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;
}
答案 10 :(得分: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
}
答案 11 :(得分:0)
Swift 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
}
}