您如何将月份中的日期格式化为“ 11日”,“ 21日”或“ 23日”(常规指示器)?


146

我知道这会给我这个月的一天,一个数(112123):

SimpleDateFormat formatDayOfMonth = new SimpleDateFormat("d");

但你如何格式化月份的一天,包括序数指标,比方说11th21st还是23rd


13
作为参考,这些被称为序数-en.wikipedia.org/wiki/Ordinal_number_(linguistics)
ocodo

5
仅作记录,任何构建响应而不是在表中查找整个答案的方法几乎都不可能本地化为其他语言。
托尔比约恩Ravn的安徒生

2
答案是不正确的,请看我的答案。
J888

2
现代评论:我建议您避免SimpleDateFormat上课。它不仅已经过时,而且非常麻烦。今天,我们java.time在现代Java日期和时间API及其功能方面有了很多改进DateTimeFormatter
Ole VV

看一下数字API(math.tools/api/numbers)。它有顺序,基数支持,数量不同的语言阐明,在各种语言等阐述了货币
DORS

Answers:


181
// https://github.com/google/guava
import static com.google.common.base.Preconditions.*;

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

@kaliatech的表很好,但是由于重复了相同的信息,因此很可能会出现错误。这样的错误实际上在表中存在7tn17tn27tn(随着时间的推移,由于StackOverflow上的流体性质,所以检查这个错误可能会固定在答案的版本历史,看看错误)。


36
确实感觉像是对简单数据格式的疏忽,不是吗?
stevevls 2011年

51
玩得开心国际化。:D
Trejkaz 2014年

6
@Trejkaz超出问题范围:)
Greg Mattes

3
该解决方案仅支持英语。使用ICU4J中的RuleBasedNumberFormat作为本地化解决方案。
ДмитроЯковлєв

7
你们在杂草丛生中走了,语气有些转弯。没必要。这个问题没有问到国际化的复杂性。当然,国际化是一个很好的讨论话题,但是应该针对具有这个重点的另一个问题。我一直把这个问题解释为要求一种快速而肮脏的方式来用英语完成这一问题。因此,我建议,如果在Java世界中确实没有一个国际化友好的好的解决方案,我们可以在GitHub等上启动一个项目,并创建一个好的解决方案。让我知道你是否有兴趣。
格雷格·马特斯

51

JDK中没有任何东西可以做到这一点。

  static String[] suffixes =
  //    0     1     2     3     4     5     6     7     8     9
     { "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th",
  //    10    11    12    13    14    15    16    17    18    19
       "th", "th", "th", "th", "th", "th", "th", "th", "th", "th",
  //    20    21    22    23    24    25    26    27    28    29
       "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th",
  //    30    31
       "th", "st" };

 Date date = new Date();
 SimpleDateFormat formatDayOfMonth  = new SimpleDateFormat("d");
 int day = Integer.parseInt(formatDateOfMonth.format(date));
 String dayStr = day + suffixes[day];

或使用日历:

 Calendar c = Calendar.getInstance();
 c.setTime(date);
 int day = c.get(Calendar.DAY_OF_MONTH);
 String dayStr = day + suffixes[day];

根据@thorbjørn-ravn-andersen的评论,在本地化时,这样的表格可能会有所帮助:

  static String[] suffixes =
     {  "0th",  "1st",  "2nd",  "3rd",  "4th",  "5th",  "6th",  "7th",  "8th",  "9th",
       "10th", "11th", "12th", "13th", "14th", "15th", "16th", "17th", "18th", "19th",
       "20th", "21st", "22nd", "23rd", "24th", "25th", "26th", "27th", "28th", "29th",
       "30th", "31st" };

8
如果让表包含完整的“ 21st”,“ 23rd”,“ 29th”,则可以将其外部化并本地化为其他语言。对于成功的软件,可能会成为必需。
托尔比约恩Ravn的安徒生

40
private String getCurrentDateInSpecificFormat(Calendar currentCalDate) {
    String dayNumberSuffix = getDayNumberSuffix(currentCalDate.get(Calendar.DAY_OF_MONTH));
    DateFormat dateFormat = new SimpleDateFormat(" d'" + dayNumberSuffix + "' MMMM yyyy");
    return dateFormat.format(currentCalDate.getTime());
}

private String getDayNumberSuffix(int 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";
    }
}

我只是尝试了您的解决方案,同时省略了提供给SimpleDateFormat构造函数的模式字符串中的单引号。我现在知道他们为什么在那里。我java.lang.IllegalArgumentException因为“一个非法的图案字符t”而出了名。
典型的丹尼

17

问题有点老了。由于此问题非常嘈杂,因此请发布我使用静态方法解决的问题。只需复制,粘贴和使用它!

 public static String getFormattedDate(Date date){
            Calendar cal=Calendar.getInstance();
            cal.setTime(date);
            //2nd of march 2015
            int day=cal.get(Calendar.DATE);

            if(!((day>10) && (day<19)))
            switch (day % 10) {
            case 1:  
                return new SimpleDateFormat("d'st' 'of' MMMM yyyy").format(date);
            case 2:  
                return new SimpleDateFormat("d'nd' 'of' MMMM yyyy").format(date);
            case 3:  
                return new SimpleDateFormat("d'rd' 'of' MMMM yyyy").format(date);
            default: 
                return new SimpleDateFormat("d'th' 'of' MMMM yyyy").format(date);
        }
        return new SimpleDateFormat("d'th' 'of' MMMM yyyy").format(date);
    }

用于测试乳糖

示例:从main方法调用它!

Date date = new Date();
        Calendar cal=Calendar.getInstance();
        cal.setTime(date);
        for(int i=0;i<32;i++){
          System.out.println(getFormattedDate(cal.getTime()));
          cal.set(Calendar.DATE,(cal.getTime().getDate()+1));
        }

输出:

22nd of February 2018
23rd of February 2018
24th of February 2018
25th of February 2018
26th of February 2018
27th of February 2018
28th of February 2018
1st of March 2018
2nd of March 2018
3rd of March 2018
4th of March 2018
5th of March 2018
6th of March 2018
7th of March 2018
8th of March 2018
9th of March 2018
10th of March 2018
11th of March 2018
12th of March 2018
13th of March 2018
14th of March 2018
15th of March 2018
16th of March 2018
17th of March 2018
18th of March 2018
19th of March 2018
20th of March 2018
21st of March 2018
22nd of March 2018
23rd of March 2018
24th of March 2018
25th of March 2018

16
String ordinal(int num)
{
    String[] suffix = {"th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th"};
    int m = num % 100;
    return String.valueOf(num) + suffix[(m > 3 && m < 21) ? 0 : (m % 10)];
}

14

我想贡献现代的答案。SimpleDateFormat八年前问这个问题时,该班就可以使用,但是现在您应该避免使用它,因为它不仅已经过时,而且非常麻烦。使用java.time代替。

编辑

DateTimeFormatterBuilder.appendText(TemporalField, Map<Long, String>)为此很好。使用它,我们构建了一个格式化程序,可以为我们完成工作:

    Map<Long, String> ordinalNumbers = new HashMap<>(42);
    ordinalNumbers.put(1L, "1st");
    ordinalNumbers.put(2L, "2nd");
    ordinalNumbers.put(3L, "3rd");
    ordinalNumbers.put(21L, "21st");
    ordinalNumbers.put(22L, "22nd");
    ordinalNumbers.put(23L, "23rd");
    ordinalNumbers.put(31L, "31st");
    for (long d = 1; d <= 31; d++) {
        ordinalNumbers.putIfAbsent(d, "" + d + "th");
    }

    DateTimeFormatter dayOfMonthFormatter = new DateTimeFormatterBuilder()
            .appendText(ChronoField.DAY_OF_MONTH, ordinalNumbers)
            .appendPattern(" MMMM")
            .toFormatter();

    LocalDate date = LocalDate.of(2018, Month.AUGUST, 30);
    for (int i = 0; i < 6; i++) {
        System.out.println(date.format(dayOfMonthFormatter));
        date = date.plusDays(1);
    }

该代码段的输出为:

30th August
31st August
1st September
2nd September
3rd September
4th September

旧答案

该代码较短,但是恕我直言,它不是那么优雅。

    // ordinal indicators by numbers (1-based, cell 0 is wasted)
    String[] ordinalIndicators = new String[31 + 1];
    Arrays.fill(ordinalIndicators, 1, ordinalIndicators.length, "th");
    ordinalIndicators[1] = ordinalIndicators[21] = ordinalIndicators[31] = "st";
    ordinalIndicators[2] = ordinalIndicators[22] = "nd";
    ordinalIndicators[3] = ordinalIndicators[23] = "rd";

    DateTimeFormatter dayOfMonthFormatter = DateTimeFormatter.ofPattern("d");

    LocalDate today = LocalDate.now(ZoneId.of("America/Menominee")).plusWeeks(1);
    System.out.println(today.format(dayOfMonthFormatter) 
                        + ordinalIndicators[today.getDayOfMonth()]);

我刚刚运行了这个片段

23日

的众多功能之一java.time是将月份中的天作为int,这是直接且可靠的,这显然是从表中选择正确的后缀所必需的。

我建议您也编写一个单元测试。

PS类似的格式,也可用于分析含像序号日期字符串1st2nd等等。这是在做了这样一个问题:爪哇-解析日期可选秒

链接: Oracle教程:Date Time说明如何使用java.time


很想让这个答案更高,因为这个更现代的答案比尝试实施自己的实现更好。
Krease

9

如果您尝试了解i18n,则解决方案将变得更加复杂。

问题在于,在其他语言中,后缀不仅取决于数字本身,还取决于其计数的名词。例如,俄语为“2-ойдень”,但是为“2-аянеделя”(它们的意思是“第二天”,但是“第二周”)。如果仅格式化几天,则不适用,但在更通用的情况下,您应该意识到复杂性。

我认为不错的解决方案(我没有时间实际实现)将是扩展SimpleDateFormetter以在传递给父类之前应用支持区域设置的MessageFormat。这样,您就可以支持让比如说三月格式的%M获得“ 3rd”,%MM获得“ 03-rd”和%MMM获得“第三”。从外部看,此类类似于常规的SimpleDateFormatter,但支持更多格式。同样,如果常规的SimpleDateFormetter错误地应用了此模式,则结果的格式将不正确,但仍可读。


关于俄语中的性别的要点,但从技术上讲,如果没有其他环境,%MMM不可能实现。
疯狂物理学家

@Mad Physicist,这是不正确的,因为%MMM将应用于月份,因此我们知道该名词与共轭。
CAB

6

这里的许多示例都不适用于11、12、13。这是更通用的,适用于所有情况。

switch (date) {
                case 1:
                case 21:
                case 31:
                    return "" + date + "st";

                case 2:
                case 22:
                    return "" + date + "nd";

                case 3:
                case 23:
                    return "" + date + "rd";

                default:
                    return "" + date + "th";
}


3

有一种更简单,更确定的方法。您需要使用的功能是getDateFromDateString(dateString); 它基本上删除了日期字符串中的st / nd / rd / th,并对其进行了简单地解析。您可以将SimpleDateFormat更改为任何值,这将起作用。

public static final SimpleDateFormat sdf = new SimpleDateFormat("d");
public static final Pattern p = Pattern.compile("([0-9]+)(st|nd|rd|th)");

private static Date getDateFromDateString(String dateString) throws ParseException {
     return sdf.parse(deleteOrdinal(dateString));
}

private static String deleteOrdinal(String dateString) {
    Matcher m = p.matcher(dateString);
    while (m.find()) {
        dateString = dateString.replaceAll(Matcher.quoteReplacement(m.group(0)), m.group(1));
    }
    return dateString;

}


1
这个答案是关于解析一个字符串,而问题是关于生成一个字符串。但是仍然合适,因为一个人可能需要两个方向。另外,此答案解决了另一个问题
罗勒·布尔克

3

Greg提供的解决方案的唯一问题是,它不能解释以“青少年”数字结尾的大于100的数字。例如,111应该是第111,而不是111。这是我的解决方案:

/**
 * Return ordinal suffix (e.g. 'st', 'nd', 'rd', or 'th') for a given number
 * 
 * @param value
 *           a number
 * @return Ordinal suffix for the given number
 */
public static String getOrdinalSuffix( int value )
{
    int hunRem = value % 100;
    int tenRem = value % 10;

    if ( hunRem - tenRem == 10 )
    {
        return "th";
    }
    switch ( tenRem )
    {
    case 1:
        return "st";
    case 2:
        return "nd";
    case 3:
        return "rd";
    default:
        return "th";
    }
}

6
在哪种情况下,“一个月”序列会超过31天?
SatanEnglish 2014年

@SatanEnglish,关于这种静态工厂方法的好处是,它不仅可以用于一个月的后缀,而且可以使用更多的时间。:)
Jared Rummler

1
此方法返回st为11,nd为12和rd为13
TheIT

@SatanEnglish。考虑到今天是我选择的日历中的第2月137日,我认为您的问题会自动回答。严重的是,如果您知道要看的地方,非格里高利历就比比皆是。
疯狂物理学家'18

不,@ TheIT,不是。我测试了 它应返回th11、12和13。我相信if ( hunRem - tenRem == 10 )情况可以肯定。
Ole VV

2

RuleBasedNumberFormat 在ICU库中

我感谢@ Pierre-Olivier Dybman(http://www.icu-project.org/apiref/icu4j/com/ibm/icu/text/RuleBasedNumberFormat.html)到ICU项目库的链接,但仍然需要弄清楚了解如何使用它,因此RuleBasedNumberFormat下面是用法示例。

它只会格式化单个数字而不是整个日期,因此,如果要查找格式如下的日期,则需要构建一个组合字符串:例如2月3日,星期一。

以下代码RuleBasedNumberFormat针对给定的区域设置将其设置为序号格式,创建一个java.time ZonedDateTime,然后将其序号数字格式化为字符串。

RuleBasedNumberFormat numOrdinalFormat = new RuleBasedNumberFormat(Locale.UK,
    RuleBasedNumberFormat.ORDINAL);
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Pacific/Auckland"));

String dayNumAndOrdinal = numOrdinalFormat.format(zdt.toLocalDate().getDayOfMonth());

输出示例:

第三名

要么

第四名

等等


1

这是一种方法,如果找到正确的后缀文字,则使用正确的后缀文字更新DateTimeFormatter模式d'00',例如,对于第1个月的第几天,它将被替换d'st'。模式更新后,就可以将其输入DateTimeFormatter进行其余操作。

private static String[] suffixes = {"th", "st", "nd", "rd"};

private static String updatePatternWithDayOfMonthSuffix(TemporalAccessor temporal, String pattern) {
    String newPattern = pattern;
    // Check for pattern `d'00'`.
    if (pattern.matches(".*[d]'00'.*")) {
        int dayOfMonth = temporal.get(ChronoField.DAY_OF_MONTH);
        int relevantDigits = dayOfMonth < 30 ? dayOfMonth % 20 : dayOfMonth % 30;
        String suffix = suffixes[relevantDigits <= 3 ? relevantDigits : 0];
        newPattern = pattern.replaceAll("[d]'00'", "d'" + suffix + "'");
    }

    return newPattern;
}

它确实需要在每次格式化调用之前更新原始模式,例如

public static String format(TemporalAccessor temporal, String pattern) {
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern(updatePatternWithDayOfMonthSuffix(temporal, pattern));
    return formatter.format(temporal);
}

因此,如果格式化模式是在Java代码之外(例如模板)定义的,则这很有用,就像您可以在Java中定义模式一样,然后用@ OleV.V回答。也许更合适


1
富有创意且令人费解。当然,我花了很长时间才了解它是如何工作的。那不是好的代码的标志。
Ole VV

1
@ OleV.V。感谢您的反馈-我已经对其进行了一些重组,因此它不再那么冗长。刚刚看到您的方法,我喜欢!我认为两种方法在不同的权衡下都是有效的。您在格式化时不需要任何进一步的支持,但确实需要使用构建器来定义模式,而该构建器会排除非Java代码中的模式定义。我的方法确实需要格式化方面的额外支持,但不依赖于构建器来创建模式,因此使可以在定义模式的位置更加灵活。我的要求决定了后者
tazmaniax

3
优点和缺点非常好。您可能希望将其包括在答案中?只是个主意。
Ole VV

1

我为自己写了一个辅助方法来获取模式。

public static String getPattern(int month) {
    String first = "MMMM dd";
    String last = ", yyyy";
    String pos = (month == 1 || month == 21 || month == 31) ? "'st'" : (month == 2 || month == 22) ? "'nd'" : (month == 3 || month == 23) ? "'rd'" : "'th'";
    return first + pos + last;
}

然后我们可以称其为

LocalDate localDate = LocalDate.now();//For reference
int month = localDate.getDayOfMonth();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(getPattern(month));
String date = localDate.format(formatter);
System.out.println(date);

输出是

December 12th, 2018

不过,这至少需要26个API :)
Mikkel Larsen

@MikkelLarsen这个问题不是关于android的,我正在为此使用Java 8。Android不支持Java 8的大型
API。– SamHoque

@SamSakerz我的坏人:)
Mikkel Larsen

@MikkelLarsen 在ThreeTen-Backport项目中,大多数java.time功能都被反向移植到Java 6和Java 7 。在ThreeTenABP中进一步适用于早期的Android(<26)。请参阅如何使用ThreeTenABP…
罗勒·布尔克

1

尝试以下功能:

public static String getFormattedDate(Date date) 
{
  Calendar cal = Calendar.getInstance();
  cal.setTime(date);
  //2nd of march 2015
  int day = cal.get(Calendar.DATE);
  if (!((day > 10) && (day < 19)))
   switch (day % 10) {
    case 1:
     return new SimpleDateFormat("d'st' 'of' MMMM yyyy").format(date);
    case 2:
     return new SimpleDateFormat("d'nd' 'of' MMMM yyyy").format(date);
    case 3:
     return new SimpleDateFormat("d'rd' 'of' MMMM yyyy").format(date);
    default:
     return new SimpleDateFormat("d'th' 'of' MMMM yyyy").format(date);
   }
  return new SimpleDateFormat("d'th' 'of' MMMM yyyy").format(date);
}

在回答一个旧问题时,如果您包含一些上下文来解释您的答案如何帮助您的答案,那么对于其他StackOverflow用户来说,它的作用将大得多,尤其是对于已经接受了答案的问题。请参阅:如何写一个好的答案
David Buck

感谢您的贡献。我建议大家不要使用DateCalendarSimpleDateFormat。这些类的设计很差,而且已经过时了,最后一个特别麻烦。取而代之的是使用java.time中的LocalDateDateTimeFormatter,它们都是现代Java日期和时间API。另外,我不确定与其他18个答案相比,您的答案有哪些新贡献?
Ole VV

0

在科特林,您可以像这样使用

fun changeDateFormats(currentFormat: String, dateString: String): String {
        var result = ""
        try {
            val formatterOld = SimpleDateFormat(currentFormat, Locale.getDefault())
            formatterOld.timeZone = TimeZone.getTimeZone("UTC")

            var date: Date? = null

            date = formatterOld.parse(dateString)

            val dayFormate = SimpleDateFormat("d", Locale.getDefault())
            var day = dayFormate.format(date)

            val formatterNew = SimpleDateFormat("hh:mm a, d'" + getDayOfMonthSuffix(day.toInt()) + "' MMM yy", Locale.getDefault())

            if (date != null) {
                result = formatterNew.format(date)
            }

        } catch (e: ParseException) {
            e.printStackTrace()
            return dateString
        }

        return result
    }


    private fun getDayOfMonthSuffix(n: Int): String {
        if (n in 11..13) {
            return "th"
        }
        when (n % 10) {
            1 -> return "st"
            2 -> return "nd"
            3 -> return "rd"
            else -> return "th"
        }
    }

这样设置

  txt_chat_time_me.text = changeDateFormats("SERVER_DATE", "DATE")

-3

可以使用以下方法获取传递给它的日期的格式化字符串。它将使用Java中的SimpleDateFormat将日期格式化为1st,2nd,3rd,4th..。例如:-2015年9月1日

public String getFormattedDate(Date date){
            Calendar cal=Calendar.getInstance();
            cal.setTime(date);
            //2nd of march 2015
            int day=cal.get(Calendar.DATE);

            switch (day % 10) {
            case 1:  
                return new SimpleDateFormat("d'st' 'of' MMMM yyyy").format(date);
            case 2:  
                return new SimpleDateFormat("d'nd' 'of' MMMM yyyy").format(date);
            case 3:  
                return new SimpleDateFormat("d'rd' 'of' MMMM yyyy").format(date);
            default: 
                return new SimpleDateFormat("d'th' 'of' MMMM yyyy").format(date);
        }

-4

以下是对问题的更有效答案,而不是对样式进行硬编码。

要将日期更改为序数,需要使用以下后缀

DD +     TH = DDTH  result >>>> 4TH

OR to spell the number add SP to the format

DD + SPTH = DDSPTH   result >>> FOURTH

这个问题中找到我的完整答案。


问题是格式在Java中没有Oracle数据库docs.oracle.com/cd/B12037_01/server.101/b10759/... 的Java使用的SimpleDateFormat日期:docs.oracle.com/javase/tutorial/i18n/format/...
贝拉尔mazlom 2015年

-9
public String getDaySuffix(int inDay)
{
  String s = String.valueOf(inDay);

  if (s.endsWith("1"))
  {
    return "st";
  }
  else if (s.endsWith("2"))
  {
    return "nd";
  }
  else if (s.endsWith("3"))
  {
    return "rd";
  }
  else
  {
    return "th";
  }
}

它不应该使用endsWith(); 产生错误的输出
Rahul Sharma
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.