PeriodFormatter 在不同设备上的不同行为 (JodaTime)

Different behavior of PeriodFormatter on different devices (JodaTime)

我正在使用 JodaTime 为 android 应用实现倒数计时器。 根据设备的不同,输出也不同。

       DateTime openingDateTime = new DateTime(2018, DateTimeConstants.JUNE, 14, 21, 0, 0, DateTimeZone.forID("Europe/Moscow"));
        DateTime nowDateTime = DateTime.now(DateTimeZone.forID("Europe/Moscow"));
        long difference = openingDateTime.getMillis() - nowDateTime.getMillis();
(...)
onTick(difference);

(...)

    PeriodFormatter periodFormatter = new PeriodFormatterBuilder()
            .printZeroAlways()
            .appendDays().appendSuffix(" day", " days")
            .appendSeparator(" ")
            .appendHours()
            .appendSeparator(":")
            .appendMinutes()
            .appendSeparator(":")
            .appendSeconds()
            .toFormatter();
(...)    

        @Override
        public void onTick(long millisUntilFinished) {
            Duration duration = new Duration(millisUntilFinished);
            Period period = duration.toPeriod(PeriodType.dayTime());
            tvCounter.setText(periodFormatter.print(period));
        }

在一台设备上输出正确:491 天4:39:18 另一方面是错误的:0 天 11788:49:11。 我做错了什么?

感谢您的评论,我现在可以重现您的问题。只需将以下静态初始化程序添加到您的测试 class(首先)以模拟您观察预期输出的设备:

static {
    TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
}

根据规范(另见 accepted answer on this SO-post),转换 duration.toPeriod(periodType) 应该只使用 so-called 精确的持续时间字段,即小时、分钟、秒和毫秒,但不天。

我对Joda-Time(v2.9.6)源码的分析:

内部 class org.joda.time.chrono.BasicChronology 包含以下常量:

private static final DurationField cDaysField = new PreciseDurationField(DurationFieldType.days(), 86400000L);

所以我们看到这里这个持续时间字段被标记为 "precise",但是: subclass ZonedChronology 包装它并覆盖方法 isPrecise() 的行为:

public boolean isPrecise() { 
  return iTimeField ? iField.isPrecise() : iField.isPrecise() && this.iZone.isFixed(); 
} 

这显示了天数 ()-duration-field 的精度 属性 的额外区域依赖性,即对于像 UTC 这样的固定区域是精确的,否则是不精确的。

我不知道分析和观察到的行为是特性还是错误。比方说,期望 duration.toPeriod(...) 创建 Period 个对象是 zone-independent 是危险的。如果系统区域是固定的,那么精确的days-component就是not documented there

不幸的是,对默认时区的隐式依赖通过其 chronology-design 被深深地编码到 Joda-Time 中。 作为解决方法,您可以使用:

Period p = new Period(nowDateTime, openingDateTime, PeriodType.dayTime());