具有更多控制权的本地化日期/时间

Localized Date/Time with More Control

提问人:Bryan 提问时间:1/21/2017 最后编辑:Bryan 更新时间:5/30/2017 访问量:2095

问:

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

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

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

问题是我对只有四种类型(、、、)的格式化程序的输出没有太多控制。我很好奇是否有一种方法可以在不丢失本地化格式的情况下对输出进行更精细的控制。FormatStyleSHORTMEDIUMLONGFULL


使用前面的代码,区域设置的结果为: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午後"
Java Android 本地化 threetenbp

评论

0赞 Meno Hochschild 1/21/2017
ThreetenBP(以及 Java-8 中的 java.time-package)没有这样的能力。我自己的 Android 时间库 (Time4A) 部分支持(至少支持从 12 小时切换到 24 小时或返回,具体取决于默认区域设置的 Android 用户设置)。
0赞 ProgrammersBlock 1/21/2017
我更新了我的答案,觉得它现在更接近布莱恩正在寻找的东西。

答:

1赞 ProgrammersBlock 1/21/2017 #1

可以使用 DateTimeFormatterBuilder.getLocalizedDateTimePattern 获取 Locale 的格式字符串。获得该字符串后,可以使用 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 中,您可以使用符号字符和 ofPattern 方法指定日期的各个单位。每个单位使用的符号字符数也会影响显示的内容:

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

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

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

LocalDate localDate = LocalDate.now(); //For reference
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy MM dd");
String formattedString = localDate.format(formatter);

评论

1赞 Meno Hochschild 1/21/2017
OP 仍然需要可本地化的模式,这意味着模式内部甚至有可本地化的文字。因此,您的帖子根本不是答案。
1赞 Basil Bourque 1/23/2017
这个答案似乎仍然不完整。也许您可以按照问题中提出的确切要求进行操作:(a) 指定格式是 12 小时还是 24 小时,(b) 指定是否显示时区或工作日。另外,我相信您的最后一个代码示例应该指定 .Locale
0赞 sasynkamil 8/31/2021
@ProgrammersBlock 谢谢 - 您的回答帮助我找到了如何将 2 位年份更改为 4 位年份并且仍然支持区域设置: String localizedPattern = DateTimeFormatterBuilder.getLocalizedDateTimePattern(dateStyle, timeStyle, IsoChronology.INSTANCE, locale);字符串 localizedPatternWithFullYear = localizedPattern.replace(“yy”, “yyyy”);DateTimeFormatter 格式化程序 = DateTimeFormatter.ofPattern(localizedPatternWithFullYear).withLocale(locale).withZone(zoneId)
1赞 user7605325 5/30/2017 #2

的 (AFAIK) 的问题在于它们使用预定义的模式。虽然,可以获取它们并操纵/更改模式以满足您的需求。FormatStyle

我没有使用特定于 Android 的环境,所以我不确定这段代码对你的效果如何。我正在使用 Java JDK 1.7.0_79ThreeTen 向后移植 1.3.4。我也使用美国/New_York时区进行测试 - 我想它对应于 EST

我注意到与您的环境存在一些差异:

  • 用于日语区域设置给了我FormatStyle.LONG2017/01/23 13:28:37 EST
  • 我不得不用它来得到FormatStyle.FULL2017年1月23日 13時28分37秒 EST

但我认为这不会使我的测试无效。

首先,我使用 class 将本地化模式作为 .然后我根据想要的配置对此进行了一些替换:java.text.DateFormatStringString

  • 查找 、 或 并更改为使用 12 或 24 小时格式(我替换了保持相同数量的字母)HHhhHh
  • 删除或添加(或):删除或添加时区zZ
  • 避免文字:某些模式(例如pt_BR区域设置)在小时后有一个文字(例如成为),因此我在替换时必须注意这一点hHH'h'13h

创建格式化程序的代码为:

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

一些用法示例(我的默认值是 pt_BR - 巴西葡萄牙语):Locale

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 

笔记:

  • 该方法返回一个 ,但我不确定它在 Android 中的工作方式是否相同。您可以检查它是否返回不同的类型,以及是否可以从此类中获取模式(如果不是,那么我不知道另一种方法)。DateFormat.getDateTimeInstanceSimpleDateFormatString
  • 我创建了更改小时格式(12 或 24)以及包含或删除时区的选项。但是您可以根据需要创建任意数量的选项 - 一旦您有了 模式 ,您可以用它做任何事情String
  • 我的正则表达式(IMO)有点难看。但我不是正则表达式专家,我不确定如何改进它们。我已经用一些语言环境测试了它们,它们似乎很好,但可能存在一些特殊情况,它们会失败(尽管我还没有测试到足够的测试来找到它)
  • 如果要添加偏移量(如),可以使用模式(而不是 )。如果您想要与 GMT 的偏移量(如 ),您可以使用该模式。-05:00xxxzGMT-05:00ZZZZ
  • 我不确定是否所有模式都具有 as 时区(它们可以使用 或 ),因此您可以更改代码以查找其他模式。在我的测试中,我没有发现任何不同之处,但无论如何,我建议对此进行仔细检查以确保。zZxz
  • 我正在检查模式是否已经有一个时区 - 一种非常幼稚/愚蠢的方式,因为它不处理文字(引号内)。也许这也可以更改为使用正则表达式(尽管我没有找到具有文字模式的区域设置)。pattern.contains("z")zz

评论

0赞 Bryan 5/31/2017
这是一个很好的解决方法。我在当前代码中看到的唯一问题是缺少 12 小时格式的 AM/PM 符号;这很容易实现,但是如果我遇到另一种需要更精细控制的情况,我需要为每种情况添加条件。对于应该内置到框架中的东西,这似乎是很多额外的工作。
1赞 5/31/2017
要添加 AM/PM,您必须添加模式,但您需要找到正确的位置 - 在小时/分钟/秒模式的整个块之后 - 这并不简单,因为模式变化很大(或者您可以假设它总是在最后,但在时区之前)。由于 的模式似乎是预定义的并内置在 JVM 中,我没有看到其他方法可以做到这一点。aFormatStytle