Разбор указателя даты (st, nd, rd, th) в строке даты - программирование

Разбор указателя даты (st, nd, rd, th) в строке даты

Я проверил SimpleDateFormat javadoc, но я не могу найти способ разобрать порядковый индикатор в формате даты, подобный этому:

 Feb 13th 2015 9:00AM

Я пробовал "MMM dd yyyy hh:mma", но дни должны быть в числе, чтобы это было правильно?

Возможно ли разобрать "13-ю" дату с помощью SimpleDateFormat без необходимости усечения строки?

4b9b3361

Ответ 1

Java SimpleDateFormat не поддерживает порядковый суффикс, но порядковый суффикс - просто глазная конфета - он избыточен и может быть легко удален, чтобы обеспечить простой анализ:

Date date = new SimpleDateFormat("MMM dd yyyy hh:mma")
    .parse(str.replaceAll("(?<=\\d)(st|nd|rd|th)", ""));

Заменить regex так просто, потому что эти последовательности не появятся нигде в допустимую дату.


Для обработки любого языка, который добавляет любую длину символов порядкового индикатора с любого языка в качестве суффикса:

Date date = new SimpleDateFormat("MMM dd yyyy hh:mma")
    .parse(str.replaceAll("(?<=\\d)(?=\\D* \\d+ )\\p{L}+", ""));

Некоторые языки, например, мандарин, добавляют свой порядковый указатель, но это может быть использовано также с чередованием - слева как упражнение для читателя:)

Ответ 2

Ответ Java 8 (и Java 6 и 7) (поскольку, когда этот вопрос задавался в 2015 году, замена для SimpleDateFormat уже отсутствовала):

    DateTimeFormatter parseFormatter = DateTimeFormatter
            .ofPattern("MMM d['st']['nd']['rd']['th'] uuuu h:mma", Locale.ENGLISH);
    LocalDateTime dateTime = LocalDateTime.parse(dateTimeString, parseFormatter);

С датой выборки из вопроса этот yiedls:

2015-02-13T09:00

В шаблоне формата [] обозначены необязательные части, а '' обозначает литеральные части. Таким образом, шаблон говорит, что за числом может следовать st, nd, rd или th.

Чтобы использовать это в Java 6 или 7, вам нужно ThreeTen Backport. Или для Android ThreeTenABP.

Поскольку эти суффиксы являются специальными для английского языка, а другие языки/локали имеют совершенно другие способы написания дат и времени (также они не используют AM/PM), я считаю, что, если у вас нет других требований, вы должны попытаться реализовать это для английских дат и времени. Кроме того, вы должны четко указать язык, говорящий на английском языке, поэтому он будет работать независимо от языкового стандарта вашего компьютера или JVM.

Я попытался объединить лучшие части ответов Hugo и , чтобы дублировать вопрос. В в этом дублирующем вопросе есть еще больше ответов на java 8. Одно из ограничений вышеуказанного кода заключается в том, что он не имеет очень строгой проверки: вы сойдете с Feb 13rd и даже Feb 13stndrdth.

Ответ 3

В случае, если кто-то сочтет это полезным: построитель DateTimeFormatter. Этот форматтер позволяет вам форматировать и анализировать даты в Великобритании с порядковыми суффиксами (например, "1 января 2017 года" ):

public class UkDateFormatterBuilder
{
    /**
     * The UK date formatter that formats a date without an offset, such as '14th September 2020' or '1st January 2017'.
     * @return an immutable formatter which uses the {@link ResolverStyle#SMART SMART} resolver style. It has no override chronology or zone.
     */
    public DateTimeFormatter build()
    {
        return new DateTimeFormatterBuilder()
                .parseCaseInsensitive()
                .parseLenient()
                .appendText(DAY_OF_MONTH, dayOfMonthMapping())
                .appendLiteral(' ')
                .appendText(MONTH_OF_YEAR, monthOfYearMapping())
                .appendLiteral(' ')
                .appendValue(YEAR, 4)
                .toFormatter(Locale.UK);
    }

    private Map<Long, String> monthOfYearMapping()
    {
        Map<Long, String> monthOfYearMapping = new HashMap<>();
        monthOfYearMapping.put(1L, "January");
        monthOfYearMapping.put(2L, "February");
        monthOfYearMapping.put(3L, "March");
        monthOfYearMapping.put(4L, "April");
        monthOfYearMapping.put(5L, "May");
        monthOfYearMapping.put(6L, "June");
        monthOfYearMapping.put(7L, "July");
        monthOfYearMapping.put(8L, "August");
        monthOfYearMapping.put(9L, "September");
        monthOfYearMapping.put(10L, "October");
        monthOfYearMapping.put(11L, "November");
        monthOfYearMapping.put(12L, "December");
        return monthOfYearMapping;
    }

    private Map<Long, String> dayOfMonthMapping()
    {
        Map<Long, String> suffixes = new HashMap<>();
        for (int day=1; day<=31; day++)
        {
            suffixes.put((long)day, String.format("%s%s", (long) day, dayOfMonthSuffix(day)));
        }
        return suffixes;
    }

    private String dayOfMonthSuffix(final int day)
    {
        Preconditions.checkArgument(day >= 1 && day <= 31, "Illegal day of month: " + day);
        if (day >= 11 && day <= 13)
        {
            return "th";
        }
        switch (day % 10)
        {
            case 1:  return "st";
            case 2:  return "nd";
            case 3:  return "rd";
            default: return "th";
        }
    }
}

Плюс фрагмент тестового класса:

public class UkDateFormatterBuilderTest
{
    DateTimeFormatter formatter = new UkDateFormatterBuilder().build();

    @Test
    public void shouldFormat1stJanuaryDate()
    {
        final LocalDate date = LocalDate.of(2017, 1, 1);

        final String formattedDate = date.format(formatter);

        Assert.assertEquals("1st January 2017", formattedDate);
    }

    @Test
    public void shouldParse1stJanuaryDate()
    {
        final String formattedDate = "1st January 2017";

        final LocalDate parsedDate = LocalDate.parse(formattedDate, formatter);

        Assert.assertEquals(LocalDate.of(2017, 1, 1), parsedDate);
    }
}

PS. Я использовал решение Грега Маттиса для порядковых суффиксов отсюда: Как вы форматируете день месяца, чтобы сказать "11-й" , "21-й" , или "23rd" в Java? (порядковый показатель)

Ответ 4

Вы должны использовать RuleBasedNumberFormat. Он отлично работает и уважает Locale.