Проблемы при анализе даты с помощью NSRegularExpression

Я пытаюсь проанализировать строку с форматом даты, например MM/YY, и сохранить переменные месяца и года.

Я написал этот код и не могу понять, почему, когда я передаю строку типа «1»:

  • match.numberOfRanges == 3
  • match.rangeAtindex(2) == (9223372036854775807,0)

Вот мой код (regex имеет только две группы, поэтому я не понимаю, как количество диапазонов может быть даже теоретически больше 2).

let regex = NSRegularExpression(pattern: "^(\\d{1,2})?[\\s/]*(\\d{1,2})?", options: NSRegularExpressionOptions.allZeros, error: nil)
// Expiry date string is "1"
let match = regex?.firstMatchInString(expiryDate, options: NSMatchingOptions.allZeros, range: NSMakeRange(0, expiryDateNS.length))

if let match = match {
    let monthRange = match.rangeAtIndex(1)
    // next string works correct - month contains "1"
    var month = expiryDateNS.substringWithRange(monthRange)

    if match.numberOfRanges > 1 {  // match.numberOfRanges returns 3
        let yearRange = match.rangeAtIndex(2) // returns LONG_MAX as location, 0 as length
        // next line will crash
        expiryYear = expiryDateNS.substringWithRange(yearRange)
    }
}

ОБНОВЛЕНИЕ По просьбе @matt я добавляю сюда несколько примеров.

  • Строка "1" должна быть проанализирована и сохранена как month == "1" и expiryYear == "".
  • Строка "12" должна быть проанализирована и сохранена как month == "12" и expiryYear == "".
  • Строка "12/45" должна быть проанализирована и сохранена как month == "12" и expiryYear == "45".

Когда я анализирую строку «1» с кодом выше, match.numberOfRanges равно 3, а match.rangeAtindex(2) равно (9223372036854775807,0)


person OgreSwamp    schedule 18.05.2015    source источник
comment
Если строка является датой, гораздо лучше использовать NSDateFormatter для ее разбора, потому что для этого она и предназначена. Это просто превратит строку в дату, ка-бум.   -  person matt    schedule 18.05.2015
comment
Было бы намного полезнее, если бы вы показали несколько целевых строк, которые вы пытаетесь проанализировать.   -  person matt    schedule 18.05.2015
comment
@matt спасибо, но мне нужно пойти этим путем.   -  person OgreSwamp    schedule 18.05.2015
comment
Ага, понятно. Итак, вопрос в том, почему вы падаете с этой ошибочной строкой?   -  person matt    schedule 18.05.2015
comment
Ну, очевидно, что ваш pattern совершенно неверен. Если бы это было правильно, вы бы вообще не совпадали с 1.   -  person matt    schedule 18.05.2015
comment
поэтому я не понимаю, как количество диапазонов может быть даже теоретически больше 2). Потому что один из диапазонов — это все совпадение. Пожалуйста, прочитайте документы.   -  person matt    schedule 18.05.2015
comment
почему бы вам просто не разделить строку / и не получить первый и последний компоненты?   -  person Leo Dabus    schedule 18.05.2015
comment
Так что я до сих пор не понимаю, почему вы используете здесь регулярные выражения. Но если вам нужно... Вот онлайн-тестер регулярных выражений: regex101.com Есть много других. Первый шаг для вас должен состоять в том, чтобы использовать шаблоны регулярных выражений и целевые строки, пока вы не получите шаблон, который соответствует только строкам, которые должны совпадать, т. е. построены так, как вы укажете.   -  person matt    schedule 18.05.2015
comment
@LeonardoSavioDabus, спасибо. Он может содержать пробелы вокруг разделителя /. Вероятно, сплит + обрезка - это путь. Но я до сих пор не могу понять проблему в регулярном выражении. Спасибо.   -  person OgreSwamp    schedule 18.05.2015
comment
@matt Я протестировал шаблон на regex101.com, и он делает именно то, что мне нужно. Скриншот: dropbox.com/ s/g7ar3eg9odo94kc/   -  person OgreSwamp    schedule 18.05.2015


Ответы (3)


Для входной строки "1" вторая группа захвата (\\d{1,2})? соответствует ноль раз. В этом случае match.rangeAtIndex(2).location равно NSNotFound (которое оказывается Int.max = 9223372036854775807).

Для входной строки "/12" первая группа захвата (\\d{1,2})? будет совпадать ноль раз. Итак, вы должны проверить эти случаи:

var month = ""
var year = ""
if let match = match {
    let monthRange = match.rangeAtIndex(1)
    if monthRange.location != NSNotFound {
        month = expiryDateNS.substringWithRange(monthRange)
    }

    let yearRange = match.rangeAtIndex(2)
    if yearRange.location != NSNotFound {
        year = expiryDateNS.substringWithRange(yearRange)
    }
}
person Martin R    schedule 18.05.2015
comment
Спасибо, Мартин, я подумал о NSNotFound и уже добавил эту проверку. Вы знаете, почему numberOfRanges в этом случае равно 3? - person OgreSwamp; 18.05.2015
comment
@OgreSwamp: диапазон № 0 — это весь шаблон, диапазон № 1 — это то, что соответствует первой группе захвата (\\d{1,2}), а диапазон № 2 — это то, что соответствует второй группе захвата. Вы всегда получаете один диапазон для всего шаблона плюс один для каждой группы захвата. - person Martin R; 18.05.2015
comment
Спасибо. Теперь это имеет для меня смысл. - person OgreSwamp; 18.05.2015

Для такой простой строки и шаблона NSScanner проще. Эта функция дает указанные вами выходные данные для указанных вами входных данных:

func analyze(s:String) -> (String,String) {
    var result = ("","")
    let sc = NSScanner(string: s)
    var first:Int32 = 0
    let ok = sc.scanInt(&first)
    if ok {
        result.0 = String(first)
        let ok = sc.scanUpToCharactersFromSet(NSCharacterSet.decimalDigitCharacterSet(), intoString: nil)
        if !sc.atEnd {
            var second:Int32 = 0
            let ok = sc.scanInt(&second)
            if ok {
                result.1 = String(second)
            }
        }
    }
    return result
}
person matt    schedule 18.05.2015

Поэтому, если вы решите разделить свою строку, вы можете сделать следующее:

let date = "12 / 45".stringByReplacingOccurrencesOfString(" ", withString: "", options: .LiteralSearch, range: nil)

let components = date.componentsSeparatedByString("/")
let month =  components.count > 0 ? components.first! : ""
let expiryYear = components.count > 1 ? components.last! : ""
person Leo Dabus    schedule 18.05.2015
comment
Спасибо, @leonardo. Я хотел использовать регулярное выражение, потому что оно проверяет и разделяет значения. В случае с разбиением строки на компоненты мне нужно добавить эту проверку вручную. - person OgreSwamp; 18.05.2015