Как проанализировать длительность ISO-8601 в Objective C?
-
16-09-2019 - |
Вопрос
Я ищу простой способ разобрать строку, содержащую 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")
Он также корректно обрабатывает случаи "P1M" и "PT1M"
Swift3 реализация:https://github.com/Igor-Palaguta/YoutubeEngine/blob/master/Source/YoutubeEngine/Parser/NSDateComponents%2BISO8601.swift
Пример:let components = dateComponents(ISO8601String: "P1Y2M3DT4H5M6S")
Обновление от 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 имеет метод экземпляра ComponentsSesparatedByString: NSString, который возвращает NSArray подстроков вашего исходного NSString, разделенного параметром, который вы проходите. Он будет выглядеть примерно так:
NSString* iso8601 = /*However you're getting your string in*/
NSArray* iso8601Parts = [iso8601 componentsSeparatedByString:@"T"];
Затем найдите первый элемент ISO8601 частей для каждого из возможных индикаторов продолжительности дня (Y, M, W и D). Когда вы найдете один, возьмите все предварительные цифры (и, возможно, десятичную точку), отбросьте их на поплавок и храните где -нибудь. Помните, что если был только элемент времени, то ISO8601Parts [0] будут пустой строкой.
Затем сделайте то же самое, что ищет временные детали во втором элементе ISO8601 частей для возможных индикаторов времени (H, M, S). Помните, что если бы был только однодневный компонент (то есть не было символа «t» в исходной строке), то ISO8601 Части будут только первыми, и попытка получить доступ ко второму элементу вызовет исключение из границ Анкет
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
}
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
}
}