Reputation: 45
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 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:System.currentTimeMillis()
return a value 1800000 millis lower than expected
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
Reputation: 339342
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.
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 ) );
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.
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
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