LocalDateTime.now()在Sony Bravia上崩溃了

时间:2016-02-16 09:25:57

标签: android sony android-tv threetenbp

我在AndroidTV应用中使用ThreeTen Android Backport

虽然一切都在Nexus播放器和所有经过测试的亚马逊Fire TV设备上完美运行,但LocalDateTime.now()的呼叫一直在索尼Bravia 4K 2015(KD-55x8509C)上崩溃应用程序。

Caused by: org.threeten.bp.DateTimeException: Invalid ID for ZoneOffset, invalid format: -01:00GMT-02:00,J086/02:00,J176/02:00
at org.threeten.bp.ZoneOffset.of(ZoneOffset.java:221)
at org.threeten.bp.ZoneId.of(ZoneId.java:344)
at org.threeten.bp.ZoneId.of(ZoneId.java:285)
at org.threeten.bp.ZoneId.systemDefault(ZoneId.java:244)
at org.threeten.bp.Clock.systemDefaultZone(Clock.java:137)
at org.threeten.bp.LocalDateTime.now(LocalDateTime.java:152)

发生了什么事,我该怎么办?

2 个答案:

答案 0 :(得分:1)

您的异常原因非常明确,并在您的异常消息中记录:

破区ID(" -01:00GMT-02:00,J086 / 02:00,J176 / 02:00")。

很明显, Threeten-ABP(以及Java-8也不允许构建无效的区域ID ),请参阅以下示例,该示例尝试语法上有效的格式:

String unsupported = "System/Unknown";
ZoneId zid = ZoneId.of(unsupported);
// org.threeten.bp.zone.ZoneRulesException: Unknown time-zone ID: System/Unknown

这与旧的JDK-class java.util.TimeZone不同,您可以在其中设置任意ID。所以问题出现了如何处理这样的区域ID。它是如此可怕的破碎,你甚至无法猜出哪个真正的时区标识符。

唯一合理的做法是使用表达式TimeZone.getDefault()仍然可用的底层平台时区,尽管它的zone-id不可用。请注意,破坏的区域ID会阻止您使用Threeten-ABP或任何其他tz存储库的tz数据,但会使用平台数据。

基于平台时区数据的最佳解决方法/黑客如下(仅使用ThreetenABP):

LocalDateTime ldt;

try {
    ldt = LocalDateTime.now();
} catch (DateTimeException ex) {
    long now = System.currentTimeMillis();
    int offsetInMillis = TimeZone.getDefault().getOffset(now);
    ldt = 
        LocalDateTime.ofEpochSecond(
            now / 1000, 
            (int) (now % 1000) * 1_000_000, 
            ZoneOffset.ofTotalSeconds(offsetInMillis / 1000));
}

正如我在评论中提到的,我已经考虑了一些Android设备的这种奇怪的行为以稳定我的时间库Time4A所以我觉得提及更清洁和更安全的替代从版本v3.16-2016a开始:

PlainTimestamp tsp = SystemClock.inLocalView().now();

更干净因为它不依赖于任何丑陋的异常处理,甚至不依赖于内部。如果无法解析基础系统时区的区域ID,则Time4A会自动切换到系统时区周围的包装器,而不是使用自己的tz存储库。无需用户操作。

请注意,Time4A根据自己的tz数据以及基于Android平台的tz数据,为时区提供统一的外观。您甚至可以并行使用两个tz数据(Timezone.of("Europe/Berlin")使用Time4A数据(最新),而Timezone.of("java.util.TimeZone~Europe/Berlin")使用可能较旧的平台数据。此功能对于解决Android设备上显示的用户输入的本地时间非常有用。

它也更安全,因为与ThreetenABP相比,异常设备时间戳已正确验证,另请参阅其他一些SO帖子,如thisthat

从Time4A到ThreetenABP的桥梁可能如下所示:

LocalDateTime threeten = 
    LocalDateTime.of(
       tsp.getYear(), tsp.getMonth(), tsp.getDayOfMonth(), 
       tsp.getHour(), tsp.getMinute(), tsp.getSecond(), 
       tsp.getInt(PlainTime.NANO_OF_SECOND));

但是,我不推荐它,因为

a)可以很快达到dex限制(同时使用两个库),

b)Time4A具有many features并且提供了更好的i18n体验和更出色的格式和解析引擎,它可以完全取代ThreetenABP。

Time4A的唯一问题就是:它并不为人所知。

答案 1 :(得分:0)

是的,比该死的Sony Bravia 4K 2015

不仅LocalDate.now()可以抛出,实际上任何依赖于" ZoneId.systemDefault()"的方法。因此,每次包装try-catch都会导致...令人不快的编码体验。

LocalDate.now()隐式调用ZoneId.systemDefault()

因此,作为解决方法,我构建了万无一失的ZoneId并将其提供给LocalDate.now()等。

public final class Hack {

    private static @NonNull String fix_TimeZone_getDefault_getID(String offsetId) {
        /* todo */
        return fixed_offset_id;
    }

    public static @NonNull ZoneId ZoneId() {
        String mayBeWeirdZoneId = TimeZone.getDefault().getID();
        ZoneId id;
        try {
            id = ZoneId.of(mayBeWeirdZoneId, ZoneId.SHORT_IDS);
        } catch (DateTimeException ignore) {
            id = ZoneId.of(fix_TimeZone_getDefault_getID(mayBeWeirdZoneId));
        }
        return id;
    }
}

使用:

LocalDateTime.now(Hack.ZoneId() /* instead of ZoneId.systemDefault() */ );  

ZoneId systemZone = Hack.ZoneId(); // my timezone, instead of ZoneId.systemDefault()

显然,你必须记住每次都使用它。 该死的,索尼 Kotlin的扩展方法在这里会派上用场。像ZoneId.systemDefaultAndNowSeriously()LocalDate.nowLikeAGoodGirl()这样的东西 但是,该死的。

PS:
FWIW
嗯,我相信Bravia没有完全错误的offsetId,所以我只是解析它:

private static @NonNull String fix_TimeZone_getDefault_getID(String offsetId) {
    if (offsetId == null) return ZoneOffset.UTC.getId();

    int gmt_pos = offsetId.indexOf("GMT");
    String off_fix = ZoneOffset.UTC.getId();
    if (gmt_pos > 0) {
        off_fix = offsetId.substring(0, gmt_pos);
    }
    return off_fix;
}

但是,我不确定最后一个,手上没有设备。可能会在知道更多时更新收据。