LocalDateTime.now() 在 Sony Bravia 上崩溃
LocalDateTime.now() crashes on Sony Bravia
我正在 AndroidTV 的应用程序中使用 ThreeTen Android Backport。
虽然在 Nexus Player 和所有经过测试的 Amazon Fire TV 设备上一切正常,但在 Sony Bravia 4K 2015 (KD-55x8509C) 上调用 LocalDateTime.now()
时应用程序始终崩溃。
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)
这是怎么回事,我该怎么办?
您的异常原因非常清楚并记录在您的异常消息中:
损坏的区域 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。所以问题出现了如何处理这样的区域标识。它是如此可怕地破碎,以至于你甚至无法猜测它是指哪个真实时区标识符。
唯一合理的做法是使用表达式 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 帖子,如 this and that。
从 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 并提供更好的国际化体验以及更高级的格式和解析引擎,它可以完全取代 ThreetenABP。
Time4A唯一的问题就是:不为人知。
是的,比该死的索尼 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;
}
但是,我不确定最后一个,手边没有设备。大概,什么时候会更新收据。
我正在 AndroidTV 的应用程序中使用 ThreeTen Android Backport。
虽然在 Nexus Player 和所有经过测试的 Amazon Fire TV 设备上一切正常,但在 Sony Bravia 4K 2015 (KD-55x8509C) 上调用 LocalDateTime.now()
时应用程序始终崩溃。
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)
这是怎么回事,我该怎么办?
您的异常原因非常清楚并记录在您的异常消息中:
损坏的区域 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。所以问题出现了如何处理这样的区域标识。它是如此可怕地破碎,以至于你甚至无法猜测它是指哪个真实时区标识符。
唯一合理的做法是使用表达式 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 帖子,如 this and that。
从 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 并提供更好的国际化体验以及更高级的格式和解析引擎,它可以完全取代 ThreetenABP。
Time4A唯一的问题就是:不为人知。
是的,比该死的索尼 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;
}
但是,我不确定最后一个,手边没有设备。大概,什么时候会更新收据。