Как анализировать длительность ISO-8601 в Objective C?

Я ищу простой способ разобрать строку, содержащую продолжительность ISO-8601 в Objective 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 

Если какое-либо из полей может быть опущено, вы должны быть немного умнее в своем parsingе, например:

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

Чистая версия 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]); 

Эта версия анализирует каждую продолжительность без ошибок.
Важно: в этой версии используется 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/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

Обновление 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]; } 

Вот пример для быстрого: (только часы, минуты и секунды)

 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) } схема 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) } 

Вот быстрая версия 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. Я не эксперт по cocoa, но я уверен, что если вы сможете разобрать эту строку и извлечь компонент час, минуту, секунду, день и т. Д., То получить его в NSTimeInterval должно быть легко. Сложная часть разбирает его. Я бы сделал это примерно так:

Сначала разделите строку на две отдельные строки: одну, представляющую дни, и одну, представляющую время. NSString имеет компонент метода экземпляраSeparatedByString: NSString, который возвращает NSArray подстрок вашего исходного NSString, разделенных параметром, который вы передаете. Он будет выглядеть примерно так:

 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 – это просто длительное хранение нескольких секунд, поэтому конвертируйте отдельные fragmentы, которые вы вытащили за секунды, добавьте их вместе, сохраните их в своем 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 } 
  • Автоматически запускать локальные уведомления ежедневно в динамическое время, заданное в массивах. objective ios
  • Что именно делает делегат в проекте xcode ios?
  • Как я могу «разрезать» прозрачное отверстие в UIImage?
  • Как использовать первый символ в качестве имени раздела
  • Как -performSelector: withObject: afterDelay: работать?
  • Пользовательский пузырь выноски MKPinAnnotation похож на пузырь по умолчанию
  • Почему я не должен использовать Objective C 2.0-аксессоры в init / dealloc?
  • Как изменить заголовок кнопки «Назад» на панели навигации
  • Пользовательский интерфейс UINavigationBar
  • Масштабирование MKMapView для подгонки аннотаций?
  • Как я могу заставить UITextField двигаться вверх, когда клавиатура присутствует?
  • Давайте будем гением компьютера.