pompolus
pompolus

Reputation: 45

Bug retrieving current time on Android with a given timezone

In my application I retrieve from a webservice an unix timestamp (between 0 and 15minutes in the future) and I display a countdown to that time in the form of XXm-XXs. So I simply do System.currentTimeMillis() - timestamp and I convert the result in a human readable date. Everything works fine but it seems that with certain timezones, my timer is 30 minutes off, because System.currentTimeMillis() return a value 1800000 millis lower than expected because Calendar returns wrong value of minutes when I request minutes with it. The timezone is the GMT+8 of Kuala Lumpur (Malasya). Using another GMT+8 timezone works normally. Example:

 long till = requestTimeFromWebService()
 long now=System.currentTimeMillis();
 long remaining_time = till - now;
 Calendar c=Calendar.getInstance();
 c.setTimeInMillis(remaining_time);
 int minutes=c.get(Calendar.MINUTE);
 System.out.println(""+minutes+"m");
 int seconds=c.get(Calendar.SECOND);
 System.out.println(""+seconds+"s");

With this code System.out.println(""+minutes+"m"); prints (e.g) 5m if GMT+2 Rome Timezone is set and 35m if GMT+8 Kuala Lumpur Timezone is set.

Is this a known bug? I found this: http://www.objectdb.com/database/forum/363 that seems to confirm an issue.

I also found this: https://en.wikipedia.org/wiki/Time_in_Malaysia

Blockquote At 2330 hrs local time of 31 December 1981, people in Peninsular Malaysia adjusted their clocks and watches ahead by 30 minutes to become 00:00 hours local time of 1 January 1982, to match the time in use in East Malaysia, which is UTC+08:00. This could explain where the bug comes off.

Any advice?

Upvotes: 1

Views: 1206

Answers (2)

Basil Bourque
Basil Bourque

Reputation: 339342

A date-time != span-of-time

You are abusing the date-time class java.util.Calendar (or java.util.Date) to inappropriately track a span of time. That class tracks time as a count of milliseconds since the epoch of start of 1970 in UTC (1970-01-01T00:00:00Z) plus an assigned time zone.

So when you instantiate with a count of milliseconds of 5 minutes, you are actually creating a date-time of 5 minutes after start of 1970, 1970-01-01T00:05:00Z for a java.util.Date and adding a time zone for java.util.Calendar.

When you applied a time zone for Malaysia you end up getting the old-style Malaysia time rules for 1970, not today’s post-1981 Malaysia rules.

So, no bugs, just a misuse of features.

Lesson learned: Do not use a date-time value to represent a span-of-time.

java.time

Another problem: You are using the notoriously troublesome old legacy date-time classes, now supplanted by the java.time classes.

If by “unix timestamp” you meant a count of milliseconds from an epoch of 1970 in UTC such as 1_473_738_754_764L, then use the Instant class. The Instant class represents a moment on the timeline in UTC with a resolution of nanoseconds.

First, we simulate some input data as described in Question as being in the future up to 15 minutes.

Instant instantNow = Instant.now ();
long seconds = TimeUnit.MINUTES.toSeconds ( 10 );
String input = Long.toString ( instantNow.plusSeconds ( seconds ).toEpochMilli () ); // 10 minutes in the future.

To process that String input, we convert to a long primitive value, and feed it to a Instant factory method.

Instant instantLater = Instant.ofEpochMilli ( Long.valueOf ( input ) );

Span-of-time

To capture the elapsed time, use the Duration class.

Duration duration = Duration.between ( instantNow , instantLater );

System.out.println ( "instantNow: " + instantNow + " | instantLater: " + instantLater + " | duration: " + duration );

When run. Note the standard ISO 8601 format for a duration PnYnMnDTnHnMnS where P marks the beginning and the T separates the years-months-days portion from the hours-minutes-seconds portion. So PT10M is “ten minutes”. Always use this format for textual representation of elapsed time rather than ambiguous clock-style (HH:MM:SS). The Duration and Period classes in java.time can parse and generate such strings directly with no need to specify a formatting pattern.

instantNow: 2016-09-13T19:16:33.913Z | instantLater: 2016-09-13T19:26:33.913Z | duration: PT10M

Note that none of the above code cared about time zones. All the values were in UTC. Much of your business logic, data storage, and data exchange should be in UTC. Only use zoned values where necessary or for presentation to the user.

Zoned

Your Questions asked about zoned values for Rome and for Malaysia. Apply a ZoneId to get a ZonedDateTime. Specify a proper time zone name. Never use the 3-4 letter abbreviation such as EST or IST as they are not true time zones, not standardized, and not even unique(!).

ZoneId zMontreal = ZoneId.of ( "America/Montreal" );
ZoneId zRome = ZoneId.of ( "Europe/Rome" );
ZoneId zKualaLumpur = ZoneId.of ( "Asia/Kuala_Lumpur" );

ZonedDateTime zdtMontreal = instantNow.atZone ( zMontreal );
ZonedDateTime zdtRome = instantNow.atZone ( zRome );
ZonedDateTime zdtKualaLumpur = instantNow.atZone ( zKualaLumpur );

System.out.println ( "instantNow: " + instantNow + " | zdtMontreal: " + zdtMontreal + " | zdtRome: " + zdtRome + " | zdtKualaLumpur: " + zdtKualaLumpur );

instantNow: 2016-09-13T20:23:34.280Z | zdtMontreal: 2016-09-13T16:23:34.280-04:00[America/Montreal] | zdtRome: 2016-09-13T22:23:34.280+02:00[Europe/Rome] | zdtKualaLumpur: 2016-09-14T04:23:34.280+08:00[Asia/Kuala_Lumpur]

Upvotes: 2

pompolus
pompolus

Reputation: 45

While I still don't know why the original code doesn't work, I can resolve my specific problem simply using

Calendar c=Calendar.getInstance(TimeZone.getTimeZone("GMT"));

instead than

Calendar c=Calendar.getInstance();

So I can always compare timestamp with the UTC TimeZone that is what I'm interested in.

Btw, Calendar should work in my case even setting the locale timezone (that is what happens when no argument is passed to getInstance()), and it does for most of the timezones, but apparently not for everyone.

Upvotes: 0

Related Questions