4

我正在使用 ThreeTen-Backport(特别是ThreeTenABP)在我的项目中显示时间戳。我希望显示的时间戳以本地化格式显示(基于Locale系统的);使用以下任何一种DateTimeFormatter.ofLocalizedDateTime()方法都很容易:

DateTimeFormatter formatter = DateTimeFormatter
        .ofLocalizedDateTime(FormatStyle.LONG)
        .withLocale(Locale.getDefault())
        .withZone(ZoneId.systemDefault());

String timestamp = formatter.format(Instant.now());

问题是我对格式化程序的输出没有太多控制,只有四种FormatStyle类型(SHORT, MEDIUM, LONG, FULL)。我很好奇是否有一种方法可以对输出进行更精细的控制,而不会丢失本地化格式。


使用前面的代码,语言环境的结果timestamp"en_US"是:

"January 23, 2017 1:28:37 PM EST"

虽然"ja_JP"语言环境的结果是:

"2017年1月23日 13:28:37 GMT-5:00"

如您所见,每个语言环境都使用特定的模式,并使用 12 或 24 小时格式的默认值。我想保持本地化模式,但要更改是否显示时区,或者是否使用 12 或 24 小时格式。

例如; 如果我可以将两个语言环境都设置为使用 12 小时格式,并删除时区;结果如下所示:

"January 23, 2017 1:28:37 PM"

"2017年1月23日 1:28:37午後"
4

2 回答 2

1

您可以获取Localewith的格式字符串DateTimeFormatterBuilder.getLocalizedDateTimePattern。一旦你有了那个字符串,你就可以用这个DateTimeFormatter.ofPattern方法来操作它。

    String fr = DateTimeFormatterBuilder.getLocalizedDateTimePattern(FormatStyle.LONG, FormatStyle.FULL, IsoChronology.INSTANCE, Locale.FRANCE);
    //d MMMM yyyy HH' h 'mm z
    String ge = DateTimeFormatterBuilder.getLocalizedDateTimePattern(FormatStyle.LONG, FormatStyle.FULL, IsoChronology.INSTANCE, Locale.GERMAN);
    //d. MMMM yyyy HH:mm' Uhr 'z
    String ca = DateTimeFormatterBuilder.getLocalizedDateTimePattern(FormatStyle.LONG, FormatStyle.FULL, IsoChronology.INSTANCE, Locale.CANADA);
    //MMMM d, yyyy h:mm:ss 'o''clock' a z
    String en = DateTimeFormatterBuilder.getLocalizedDateTimePattern(FormatStyle.LONG, FormatStyle.FULL, IsoChronology.INSTANCE, Locale.ENGLISH);
    //MMMM d, yyyy h:mm:ss a z

DateTimeFormatter您可以指定带有符号字符的日期的单个单位和模式的方法。每单位使用的符号字符数也会影响显示的内容:

  • M将为您提供月份的数字。
  • MM即使月份小于 10,也会得到两位数的月份。
  • MMM应该给你月份的名字。

请参阅 DateTimeFormatter文档中的“格式化和解析模式”部分。

下面的模式为您提供了一个四位数的年份、两位数的月份和两位数的日期。

LocalDate localDate = LocalDate.now(); //For reference
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy MM dd");
String formattedString = localDate.format(formatter);
于 2017-01-21T00:17:10.057 回答
1

The problem with FormatStyle's (AFAIK) is that they use predefined patterns. Although, it's possible to get them and manipulate/change the pattern to fit your needs.

I'm not using Android specific environment, so I'm not sure how well this code will work for you. I'm using Java JDK 1.7.0_79 and ThreeTen Backport 1.3.4. I'm also using America/New_York timezone for my tests - I guess it corresponds to EST.

I noticed some differences from your environment:

  • using FormatStyle.LONG for japanese locale gives me 2017/01/23 13:28:37 EST
  • I had to use FormatStyle.FULL to get 2017年1月23日 13時28分37秒 EST

But I think this doesn't invalidate my tests.

First I used java.text.DateFormat class to get the localized pattern as a String. Then I did some replacements to this String, according to the configuration wanted:

  • look for HH, hh, H or h and change to use 12 or 24 hour format (I replaced keeping the same number of letters)
  • remove or add z (or Z): to remove or add a timezone
  • avoid literals: some patterns (like in pt_BR locale) have a literal h after the hours (like HH'h' which becomes 13h), so I had to take care of this when replacing

The code to create the formatter is:

// creates a formatter with the specified style, locale and zone
// there are options to use 12 or 24 hour format and include or not a timezone
public DateTimeFormatter getFormatter(FormatStyle style, Locale locale, ZoneId zone,
                                      boolean use24HourFormat, boolean useTimezone) {
    // get the format correspondent to the style and locale
    DateFormat dateFormat = DateFormat.getDateTimeInstance(style.ordinal(), style.ordinal(), locale);

    // *** JDK 1.7.0_79 returns SimpleDateFormat ***
    // If Android returns another type, check if it's possible to get the pattern from this type
    if (dateFormat instanceof SimpleDateFormat) {
        // get the pattern String for the locale
        String pattern = ((SimpleDateFormat) dateFormat).toPattern();

        if (use24HourFormat) {
            if (pattern.contains("hh")) { // check the "hh" hour format
                // hh not surrounded by ' (to avoid literals)
                pattern = pattern.replaceAll("((?<!\')hh)|(hh(?!\'))", "HH");
            } else { // check the "h" hour format
                // h not surrounded by ' (to avoid literals)
                pattern = pattern.replaceAll("((?<!\')h)|(h(?!\'))", "H");
            }
        } else {
            if (pattern.contains("HH")) { // check the "HH" hour format
                // HH not surrounded by ' (to avoid literals)
                pattern = pattern.replaceAll("((?<!\')HH)|(HH(?!\'))", "hh");
            } else { // check the "H" hour format
                // H not surrounded by ' (to avoid literals)
                pattern = pattern.replaceAll("((?<!\')H)|(H(?!\'))", "h");
            }
        }

        if (useTimezone) {
            // checking if already contains a timezone (the naive way)
            if (!pattern.contains("z") && !pattern.contains("Z")) {
                // I'm adding z in the end, but choose whatever pattern you want for the timezone (it can be Z, zzz, and so on)
                pattern += " z";
            }
        } else {
            // 1 or more (z or Z) not surrounded by ' (to avoid literals)
            pattern = pattern.replaceAll("((?<!\')[zZ]+)|([zZ]+(?!\'))", "");
        }

        // create the formatter for the locale and zone, with the customized pattern
        return DateTimeFormatter.ofPattern(pattern, locale).withZone(zone);
    }

    // can't get pattern string, return the default formatter for the specified style/locale/zone
    return DateTimeFormatter.ofLocalizedDateTime(style).withLocale(locale).withZone(zone);
}

Some usage examples (my default Locale is pt_BR - Brazilian Portuguese):

ZoneId zone = ZoneId.of("America/New_York");
Instant instant = ZonedDateTime.of(2017, 1, 23, 13, 28, 37, 0, zone).toInstant();
FormatStyle style = FormatStyle.FULL;

// US locale, 24-hour format, with timezone
DateTimeFormatter formatter = getFormatter(style, Locale.US, zone, true, true);
System.out.println(formatter.format(instant)); // Monday, January 23, 2017 13:28:37 PM EST
// US locale, 24-hour format, without timezone
formatter = getFormatter(style, Locale.US, zone, true, false);
System.out.println(formatter.format(instant)); // Monday, January 23, 2017 13:28:37 PM 
// US locale, 12-hour format, with timezone
formatter = getFormatter(style, Locale.US, zone, false, true);
System.out.println(formatter.format(instant)); // Monday, January 23, 2017 1:28:37 PM EST
// US locale, 12-hour format, without timezone
formatter = getFormatter(style, Locale.US, zone, false, false);
System.out.println(formatter.format(instant)); // Monday, January 23, 2017 1:28:37 PM 

// japanese locale, 24-hour format, with timezone
formatter = getFormatter(style, Locale.JAPAN, zone, true, true);
System.out.println(formatter.format(instant)); // 2017年1月23日 13時28分37秒 EST
// japanese locale, 24-hour format, without timezone
formatter = getFormatter(style, Locale.JAPAN, zone, true, false);
System.out.println(formatter.format(instant)); // 2017年1月23日 13時28分37秒 
// japanese locale, 12-hour format, with timezone
formatter = getFormatter(style, Locale.JAPAN, zone, false, true);
System.out.println(formatter.format(instant)); // 2017年1月23日 1時28分37秒 EST
// japanese locale, 12-hour format, without timezone
formatter = getFormatter(style, Locale.JAPAN, zone, false, false);
System.out.println(formatter.format(instant)); // 2017年1月23日 1時28分37秒 

// pt_BR locale, 24-hour format, with timezone
formatter = getFormatter(style, Locale.getDefault(), zone, true, true);
System.out.println(formatter.format(instant)); // Segunda-feira, 23 de Janeiro de 2017 13h28min37s EST
// pt_BR locale, 24-hour format, without timezone
formatter = getFormatter(style, Locale.getDefault(), zone, true, false);
System.out.println(formatter.format(instant)); // Segunda-feira, 23 de Janeiro de 2017 13h28min37s 
// pt_BR locale, 12-hour format, with timezone
formatter = getFormatter(style, Locale.getDefault(), zone, false, true);
System.out.println(formatter.format(instant)); // Segunda-feira, 23 de Janeiro de 2017 01h28min37s EST
// pt_BR locale, 12-hour format, without timezone
formatter = getFormatter(style, Locale.getDefault(), zone, false, false);
System.out.println(formatter.format(instant)); // Segunda-feira, 23 de Janeiro de 2017 01h28min37s 

Notes:

  • The method DateFormat.getDateTimeInstance returns a SimpleDateFormat, but I'm not sure if it works the same way in Android. You can check if it returns a different type and if it's possible to get the pattern String from this class (if it's not, then I don't know another way of doing it).
  • I created options to change the hour format (12 or 24) and include or remove a timezone. But you can create as many options as you need - once you have the pattern String, you can do anything with it
  • My regular expressions are (IMO) a little bit ugly. But I'm not a regex expert and I'm not sure how they can be improved. I've tested them with some locales and they seem to be fine, but there might be some particular case where they fail (although I haven't tested enough to find it)
  • If you want to add the offset (like -05:00) you can use the xxx pattern (instead of z). If you want the offset with GMT (like GMT-05:00) you can use the ZZZZ pattern.
  • I'm not sure if all patterns have the z as the timezone (they can use Z or x), so you might change the code to look for the other patterns. In my tests I didn't find anything different from z, but anyway I'd recommend a double check on this just to make sure.
  • I'm checking if the pattern already has a timezone by doing pattern.contains("z") - a very naive/silly way because it doesn't handle a z literal (inside quotes). Maybe this could be changed to use a regex as well (although I didn't find a locale with a pattern that has a z literal).
于 2017-05-30T13:49:48.213 回答