如何自定义Joda时间日期格式的数字格式?

时间:2017-01-01 06:18:39

标签: java jodatime datetime-format

我想格式化日期,同时格式化dozenal中日期中的数字。

使用旧的Java日期格式化API,我可以这样做:

    format = new SimpleDateFormat(pattern, new DateFormatSymbolsAdapter(locale)) {{
        numberFormat = new DozenalNumberFormat();
    }};

不幸的是,SimpleDateFormat内部有一些愚蠢的代码,它为我的目的过快地分配对象,因为我经常格式化值。 Joda Time可能没有这个问题(到目前为止他们的其他类似乎没问题),所以我试图切换。

但是,使用Joda Time的日期格式化类,我不知道如何做到这一点。许多API被锁定的方式使得很难进入并做我想做的事情:

  • DateTimeFormatterBuilder方法没有给我一个指定它的方法
  • DateTimeFormatterBuilder没有任意追加,我可以看到
  • DateTimeFormatter似乎没有任何明显的拦截点
  • 所有有用的东西似乎都锁定在InternalPrinter及其实现中,这是包私有
  • 附加DateTimeFormatterDateTimePrinter等的方法似乎需要很多框架来进行自定义实现,而我还没有让我的实现工作。经过一番调查后,似乎这是a bug in Joda Time

当然,必须要有一些方法来做到这一点。有谁有想法吗?我想也许有人必须这样做才能让阿拉伯语日期格式在过去正常工作,因为我模糊地回忆起Joda Time有这个问题,但也许这就是过去。我可能是第一个尝试这样做的人,因为我想要一个不同的数字基数......

2 个答案:

答案 0 :(得分:2)

Joda-Time显然无法打印除ASCII数字0-9之外的其他数字。我已经调查了Joda文档的所有相关部分,甚至是类DateTimeUtils和{ {1}}。将语言环境设置为强制使用替代编号系统的语言环境也无济于事。

FormatUtils

最新的CLDR数据(version v30.02)告诉我们,阿拉伯语使用代号为“arab”(xml-tag String s = DateTimeFormat.forPattern("yyyy-MM-dd").withLocale(new Locale("ar")) .print(System.currentTimeMillis())); // output: 2017-01-02 (still ASCII) )的替代编号系统。但是,JDK可能并不总是最新的。 JDK通常依赖于旧的CLDR版本。但即便如此,据我所知,旧的CLDR版本也没有使用阿拉伯语的ASCII数字。

结论:你不应该使用Joda-Time来进行严肃的i18n工作(在其他许多细节中,例如固定的周开始等等,其中这个库非常糟糕)。如果您仍然坚持使用Joda-Time,那么您可以通过艰难的方式编写自己的自定义defaultNumberingSystem。但这并不好玩,因为你在Joda-issue中也注意到了(并且在可能的修复之后仍然没有乐趣,因为它太笨拙了。)

让我们看看更好的选择。

<强>的Java-8

DateTimePrinter

所以我们看到在Java-8上使用标准比Joda-Time更好但仍然没有怪癖。正确且唯一中途灵活的解决方案是使用类DecimalStyle

我的库 Time4J (也可以在版本为v3.x的Java-6上运行):

我编写了一个替代格式和解析引擎,它也可以处理Java-8类型,如 Locale loc = new Locale("ar"); System.out.println(DateTimeFormatter.ofPattern("yyyy-MM-dd") .withDecimalStyle(DecimalStyle.of(loc)) .format(LocalDate.now())); // output: 2017-01-02 (obviously my JDK uses wrong or outdated data) System.out.println(DateTimeFormatter.ofPattern("yyyy-MM-dd") .withDecimalStyle(DecimalStyle.STANDARD.withZeroDigit('\u0660')) .format(LocalDate.now())); // correct output with hardwired numbering system LocalDate等.Time4J有自己的独立于JDK的本地化资源库,实际上使用了CLDR -version v30.0.2。显示两种方式,通过区域设置的通用方式或使用关于编号系统的硬连线假设:

Instant

两种方式都基于零位0(unicode点0660)产生数字表示。 2017年显示为:2017

更新

您的上一条评论明确表示您主要关注如何实现 dozenal编号系统positional number system for base 12)。好吧,使用Java-8,没有足够的灵活性(没有{JAME-Time中的System.out.println( ChronoFormatter.ofPattern( "yyyy-MM-dd", PatternType.CLDR, new Locale("ar"), PlainDate.axis(TemporalType.LOCAL_DATE) ).format(LocalDate.now())); System.out.println( ChronoFormatter.ofPattern( "yyyy-MM-dd", PatternType.CLDR, Locale.ROOT, PlainDate.axis(TemporalType.LOCAL_DATE) ) .with(Attributes.NUMBER_SYSTEM, NumberSystem.ARABIC_INDIC) .format(LocalDate.now())); - 接口,也没有比DateTimePrinter更灵活的钩子,只允许设置零十进制数字而dozen是不是小数)。为了填补空白(并没有那么多工作),我决定implement最新版本v3.27(或Java-8平台上的v4.23)中的doalal系统。对于Android,我刚刚发布了Time4A-v3.27-2016j。使用示例和最终解决方案:

DecimalStyle

如果您使用的是Android,那么您可能还会考虑使用旧类型@Test public void printDate() { ChronoFormatter<PlainDate> f = ChronoFormatter.setUp(PlainDate.axis(), Locale.ROOT) .addFixedInteger(PlainDate.YEAR, 4) .addLiteral('-') .padNext(2) .addInteger(PlainDate.MONTH_AS_NUMBER, 1, 2) .addLiteral('-') .padNext(2) .addInteger(PlainDate.DAY_OF_MONTH, 1, 2) .build() .with(Attributes.NUMBER_SYSTEM, NumberSystem.DOZENAL) .with(Attributes.PAD_CHAR, '0'); assertThat( f.format(PlainDate.of(2017, 10, 11)), is("1201-0\u218A-0\u218B")); } 选择略有更改的代码,以便与旧代码进行互操作,例如使用表达式

java.util.Date

备注:此解决方案还尽最大努力在打印数字时避免额外的数组分配(例如,在许多情况下甚至避免使用ChronoFormatter.setUp( Moment.axis(TemporalType.JAVA_UTIL_DATE), Locale.getDefault())... ),尤其是当您使用Integer.toString()作为方法的第二个参数时'ChronoFormatter.formatToBuffer()`。到目前为止,总体性能工作与其他库相比非常高。

答案 1 :(得分:1)

你的问题根本不清楚,因为你没有真正指明你想要做什么,也没有确切地说是什么阻止了你。但我会提供一些信息。

仅供参考,Joda-Time项目现在位于maintenance mode,团队建议迁移到java.time课程。

使用java.time

java.time和Joda-Time都使用immutable objects。这意味着您可以内置thread-safety。因此,您可以缓存格式化程序对象并重新使用它们。无需重复实例化。

在java.time中,java.time.format.DateTimeFormatter类可以自动为您进行本地化。可以从Locale自动分配人类语言和格式。见this list of supported locales in Java 8。我建议在它们足够时使用这些自动本地化格式。

以UTC格式捕获当前时刻。调整为时区。请注意,时区的 nothing 与区域设置有关。在格式化为wall-clock time时,您可能希望在Québec中看到Moroccan user reading Arabic

Instant instant = Instant.now ();
ZonedDateTime zdt = instant.atZone ( ZoneId.of ( "America/Montreal" ) );

生成一个String以便呈现给用户。

Locale l = new Locale ( "ar" , "MA" ); // Arabic language, cultural norms of Morocco.
DateTimeFormatter f = DateTimeFormatter.ofLocalizedDateTime ( FormatStyle.FULL ).withLocale ( l );

保留对f的引用以缓存该对象。无需再次实例化。

转储到控制台。

String output = zdt.format ( f );
System.out.println ( "zdt.toString(): " + zdt );
System.out.println ( "output: " + output );
  

zdt.toString():2017-01-01T15:06:34.255-05:00 [美国/蒙特利尔]

     

输出:01يناير,2017 EST 03:06:34م

我不确定阿拉伯语是否会在此处正确复制粘贴。见live code in IdeOne.com

关于java.time

java.time框架内置于Java 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,例如java.util.DateCalendar和&amp; SimpleDateFormat

现在位于Joda-Timemaintenance mode项目建议迁移到java.time类。

要了解详情,请参阅Oracle Tutorial。并搜索Stack Overflow以获取许多示例和解释。规范是JSR 310

从哪里获取java.time类?

ThreeTen-Extra项目使用其他类扩展java.time。该项目是未来可能添加到java.time的试验场。您可以在此处找到一些有用的课程,例如IntervalYearWeekYearQuartermore