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

问题描述 投票:16回答:4

我检查了SimpleDateFormat javadoc,但我无法找到一种方法来解析ordinal indicator的日期格式如下:

 Feb 13th 2015 9:00AM

我尝试过"MMM dd yyyy hh:mma",但是它的日子必须在数量上才能正确吗?

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

java simpledateformat date-parsing
4个回答
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}+", ""));

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


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
投票

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

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。我使用了格雷格马特斯的序数后缀解决方案:How do you format the day of the month to say "11th", "21st" or "23rd" in Java? (ordinal indicator)


0
投票

你应该使用RuleBasedNumberFormat。它工作得很好,并且尊重Locale。

© www.soinside.com 2019 - 2024. All rights reserved.