在日期时间字符串中解析日期的序数指示符(st,nd,rd,th)

时间:2015-02-14 09:48:54

标签: java simpledateformat date-parsing

我检查了SimpleDateFormat javadoc,但我无法找到以这样的日期格式解析ordinal indicator的方法:

 Feb 13th 2015 9:00AM

我试过了"MMM dd yyyy hh:mma",但为了正确,日期必须是多少?

是否可以使用SimpleDateFormat解析“第13个”日期而不必截断字符串?

4 个答案:

答案 0 :(得分:19)

Java的SimpleDateFormat不支持序数后缀,但序数后缀只是眼睛糖果 - 它是多余的,可以轻松删除以允许简单的解析:

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

替换正则表达式非常简单,因为这些序列不会出现在有效日期的其他任何位置。


处理任何语言附加任何语言的任意长度的序数指示符作为后缀:

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

有些语言,例如普通话,在它们的顺序指示符之前,但也可以使用替换处理 - 左侧作为读者的练习:)

答案 1 :(得分:5)

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);

使用此问题的样本日期:

2015-02-13T09:00

格式模式[]表示可选部分,''表示文字部分。因此,该模式表示该数字可能后跟stndrdth

要在Java 6或7中使用它,您需要ThreeTen Backport。或者针对Android ThreeTenABP

由于这些后缀对于英语是特殊的,并且其他语言/语言环境完全有其他用于写日期和时间的用法(也不使用AM / PM),我相信除非您有其他要求,否则您应该尝试仅针对英语日期和时间实施此操作。此外,您应该明确地提供一个说英语的语言环境,以便它可以独立于您的计算机或JVM的语言环境设置工作。

我尝试将Hugomyself的答案的最佳部分组合成一个重复的问题。在that duplicate question下,还有更多的java 8答案。上述代码的一个限制是它没有非常严格的验证:您将获得Feb 13rd甚至Feb 13stndrdth

答案 2 :(得分:2)

如果有人发现它有用:DateTimeFormatter构建器。此格式化程序允许您使用序数后缀格式化和解析英国日期(例如,&#34; 2017年1月1日和#34;):

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。我用Greg Mattes&#39;这里有序后缀的解决方案: How do you format the day of the month to say "11th", "21st" or "23rd" in Java? (ordinal indicator)

答案 3 :(得分:0)

您应该使用RuleBasedNumberFormat。它完美无缺,并且尊重Locale。