Как проверить, соответствует ли строка шаблону даты, используя API времени?

Моя программа анализирует входную строку в LocalDate объект. Большую часть времени строка выглядит как 30.03.2014, но иногда она выглядит как 3/30/2014. В зависимости от этого мне нужно использовать другой шаблон для вызова DateTimeFormatter.ofPattern(String pattern). По сути, мне нужно проверить, соответствует ли строка шаблону dd.MM.yyyy или M/dd/yyyy, прежде чем выполнять синтаксический анализ.

Подход с регулярным выражением будет выглядеть примерно так:

LocalDate date;
if (dateString.matches("^\\d?\\d/\\d{2}/\\d{4}$")) {
  date = LocalDate.parse(dateString, DateTimeFormatter.ofPattern("M/dd/yyyy"));  
} else {
  date = LocalDate.parse(dateString, DateTimeFormatter.ofPattern("dd.MM.yyyy"));  
}

Это работает, но было бы неплохо использовать строку шаблона даты при сопоставлении строки.

Существуют ли какие-либо стандартные способы сделать это с помощью нового API времени Java 8, не прибегая к сопоставлению регулярных выражений? Я просмотрел документы для DateTimeFormatter но я ничего не нашел.


person Community    schedule 06.05.2014    source источник
comment
Почему бы вам не набрать replace("//", ".") ?   -  person Robert Niestroj    schedule 06.05.2014
comment
Я не думаю, что существует определенный метод проверки того, соответствует ли дата заданному шаблону. Я бы использовал метод SimpleDateFormat.parse и проверил ParseException. Если вы уверены, что дата верна и находится в одном из двух шаблонов, вы можете просто проверить, есть ли '.' в строке.   -  person maxvv    schedule 06.05.2014
comment
@RobertNiestroj Потому что месяц и день не расположены в одном и том же месте в обоих шаблонах.   -  person    schedule 06.05.2014
comment
@HermanTorjussen Я не публикую это как ответ, потому что не знаю, будет ли для вас подходящим вариантом создать класс для этого. Это? Вот так: tryjava8.com/app/snippets/5368a343e4b0753f2af0f806   -  person Alexis C.    schedule 06.05.2014
comment
@Max Класс SimpleDateFormat не может напрямую анализировать LocalDate, а только java.util.Date, что сильно отличается.   -  person Meno Hochschild    schedule 06.05.2014
comment
@ZouZou Это возможное решение, если других решений уже не существует. Пожалуйста, опубликуйте это как ответ в любом случае. Кстати, тот сайт, на который вы ссылались, потерпел неудачу, когда я запустил пример.   -  person    schedule 06.05.2014
comment
@HermanTorjussen Посмотрите на мой обновленный ответ, это может быть проще, чем создать для этого класс.   -  person Alexis C.    schedule 08.05.2014
comment
@ZouZou Спасибо, это хорошее решение.   -  person    schedule 08.05.2014


Ответы (3)


С помощью DateTimeFormatter необязательные шаблоны могут указывать с помощью квадратных скобок.

Демонстрация:

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("[d.M.u][M/d/u][u-M-d]", Locale.ENGLISH);
        Stream.of(
                    "3/30/2014",
                    "30.03.2014",
                    "2014-05-14",
                    "14.05.2014"
        ).forEach(s -> System.out.println(LocalDate.parse(s, dtf)));
    }
}

Вывод:

2014-03-30
2014-03-30
2014-05-14
2014-05-14

Узнайте больше о современном API даты и времени* из Trail: Дата и время.


* По какой-либо причине, если вам нужно придерживаться Java 6 или Java 7, вы можете использовать ThreeTen-Backport, который переносит большую часть функций java.time на Java 6 и 7. Если вы работаете над проектом Android и ваш уровень Android API по-прежнему не соответствует Java-8, проверьте Java 8+ API доступно через дешугаринг и Как использовать ThreeTenABP в Android Project.

person Arvind Kumar Avinash    schedule 08.05.2021

Подход @ZouZou — возможное решение.

Чтобы максимально избежать использования исключений для логики программы (что также не очень хорошо с точки зрения производительности), следуйте альтернатива может быть рассмотрена:

static final String[] PATTERNS = {"dd.MM.yyyy", "M/dd/yyyy"};
static final DateTimeFormatter[] FORMATTERS = new DateTimeFormatter[PATTERNS.length];

static {
  for (int i = 0; i < PATTERNS.length; i++) {
    FORMATTERS[i] = DateTimeFormatter.ofPattern(PATTERNS[i]);
  }
}

public static LocalDate parse(String input) {
  ParsePosition pos = new ParsePosition();
  for (int i = 0; i < patterns.length; i++) {
    try {
      TemporalAccessor tacc = FORMATTERS[i].parseUnresolved(input, pos);
      if (pos.getErrorIndex < 0) {
        return LocalDate.from(tacc); // possibly throwing DateTimeException => validation failure
      }
    } catch (DateTimeException ex) { // catches also possible DateTimeParseException
      // go to next pattern
    }
    pos.setIndex(0);
    pos.setErrorIndex(-1);
  }
  throw new IllegalArgumentException("Input does not match any pattern: " + input);
}

Подробнее о методе parseUnresolved():

Этот метод выполняет только первую фазу синтаксического анализа, поэтому вторая фаза, содержащая предварительную проверку или объединение проанализированных полей, отсутствует. Однако LocalDate.from() проверяет каждый ввод, поэтому я думаю, что этого все еще достаточно. И преимущество в том, что parseUnresolved() использует индекс ошибки ParsePosition. Это согласуется с традиционным java.text.Format-поведением.

К сожалению, альтернативный и более интуитивно понятный метод DateTimeFormater.parse() сначала создает DateTimeParseException, а затем сохраняет индекс ошибки в этом исключении. Поэтому я решил не использовать этот метод, чтобы избежать создания ненужного исключения. Для меня эта API-деталь — сомнительное дизайнерское решение.

person Meno Hochschild    schedule 06.05.2014

person    schedule
comment
Ваши вторые параметры работают блестяще, без обработки исключений и т. д. - person Waize; 21.11.2018