david.mihola david.mihola - 2 months ago 22
Android Question

LocalDateTime.now() crashes on Sony Bravia

I am using the ThreeTen Android Backport in an app for AndroidTV.

While everything works perfectly on the Nexus Player and on all tested Amazon Fire TV devices, the call to

consistently crashes the app on a Sony 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)

What's going on and what can I do about it?


The reason of your exception is quite clear and documented in your exception message:

Broken zone id ("-01:00GMT-02:00,J086/02:00,J176/02:00").

It is also clear that Threeten-ABP (and Java-8, too) does not allow to construct an invalid zone id at all, see following example which tries a syntactically valid format:

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

This is different than in old JDK-class java.util.TimeZone where you can set any arbitrary ID. So the question arises what to do with such a zone-id. It is so horribly broken that you cannot even guess which real timezone identifier was meant.

The only reasonable thing is to use the underlying platform timezone which is still available by the expression TimeZone.getDefault() although its zone-id is unuseable. Note that the broken zone id prevents you from using the tz data of Threeten-ABP or any other tz repository but the platform data.

The best workaround / hack based on the platform timezone data is as follows (still using ThreetenABP only):

LocalDateTime ldt;

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

As I mentioned in my comment, I have taken this strange behaviour of some Android devices into account to stabilize my time library Time4A so I feel it is good to mention following cleaner and safer alternative starting with version v3.16-2016a:

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

It is cleaner because it does not depend on any ugly exception handling, not even internally. If the zone id of the underlying system timezone cannot be resolved then Time4A automatically switches to a wrapper around the system timezone instead of using the own tz repository. No user action required.

Note that Time4A has a uniform facade for timezones based on the own tz data as well as based on the Android platform tz data. You can even use both tz data in parallel (Timezone.of("Europe/Berlin") uses the Time4A-data (up-to-date) while Timezone.of("java.util.TimeZone~Europe/Berlin") uses the platform data which might be old). This feature is very useful to resolve local times of user-input as displayed on Android device.

It is also safer because exotic device timestamps are correctly validated in contrast to ThreetenABP, see also some other SO-posts like this and that.

A bridge from Time4A to ThreetenABP might look like:

LocalDateTime threeten = 
       tsp.getYear(), tsp.getMonth(), tsp.getDayOfMonth(), 
       tsp.getHour(), tsp.getMinute(), tsp.getSecond(), 

However, I don't recommend it because

a) the dex limit can be reached soon (using two libraries simultaneously),

b) Time4A has so many features and offers much better i18n-experience and a superior format and parse engine that it can completely replace ThreetenABP.

The only problem of Time4A is just this: It is not well known.