Разбор указателя даты (st, nd, rd, th) в строке даты
Я проверил SimpleDateFormat
javadoc, но я не могу найти способ разобрать порядковый индикатор в формате даты, подобный этому:
Feb 13th 2015 9:00AM
Я пробовал "MMM dd yyyy hh:mma"
, но дни должны быть в числе, чтобы это было правильно?
Возможно ли разобрать "13-ю" дату с помощью SimpleDateFormat
без необходимости усечения строки?
Ответы
Ответ 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.