Arthur V
Arthur V

Reputation: 11

Bug in Android SimpleDateFormat

I am using SimpleDateFormat to format times in Android. The goal is to get a human readable time representation out of a long which contains the time in milliseconds:

 var simpleDateFormat = SimpleDateFormat("s.SS")
 simpleDateFormat.timeZone = TimeZone.getTimeZone("UTC")
 var result = simpleDateFormat.format(timeInMillis)

I have encountered the following very weird bug: the second last digit of timeInMillis is a 7 or an 8 and the digit before is a 5, the formating is wrong, since the result 1/100 of a second too small.

Example:

timeInMillis = 1570

Expected value of result: 1.57

Actual value of result 1.56

Can anyone recreate this bug? And where can I report it?

Upvotes: 1

Views: 218

Answers (2)

laalto
laalto

Reputation: 152787

An explanation for this behavior is that Android SimpleDateFormat uses inaccurate floating point math here:

// BEGIN Android-added: Better UTS#35 conformity for fractional seconds.
case PATTERN_MILLISECOND: // 'S'
    // Fractional seconds must be treated specially. We must always convert the parsed
    // value into a fractional second [0, 1) and then widen it out to the appropriate
    // formatted size. For example, an initial value of 789 will be converted
    // 0.789 and then become ".7" (S) or ".78" (SS) or "0.789" (SSS) or "0.7890" (SSSS)
    // in the resulting formatted output.
    if (current == null) {
        value = (int) (((double) value / 1000) * Math.pow(10, count));
        zeroPaddingNumber(value, count, count, buffer);
    }
    break;
// END Android-added: Better UTS#35 conformity for fractional seconds.

For example, the expression (int) (((double) value / 1000) * Math.pow(10, count)) yields 56 for value 570 and count 2.

As pointed out by other answerers, there are better alternatives for datetimes in Android development. For example Java 8 java.time desugaring.

Upvotes: 1

Arvind Kumar Avinash
Arvind Kumar Avinash

Reputation: 78975

I am not sure how you are trying to do it.

import java.text.SimpleDateFormat;
import java.util.TimeZone;

public class Main {
    public static void main(String args[]) {
        long timeInMillis = 1570;
        var simpleDateFormat = new SimpleDateFormat("s.SS");
        simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
        var result = simpleDateFormat.format(timeInMillis);
        System.out.println(result);
    }
}

Output:

1.570

Check it also on ideone.

Note that the date-time API of java.util and their formatting API, SimpleDateFormat are outdated and error-prone. It is recommended to stop using them completely and switch to the modern date-time API. Learn more about the modern date-time API at Trail: Date Time.

Note: If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and How to use ThreeTenABP in Android Project.

Using the modern date-time API:

import java.time.Instant;
import java.time.LocalTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;

public class Main {
    public static void main(String args[]) {
        long timeInMillis = 1570;
        var dtf = DateTimeFormatter.ofPattern("s.SS");
        LocalTime time = Instant.ofEpochMilli(timeInMillis).atOffset(ZoneOffset.UTC).toLocalTime();
        System.out.println(time.format(dtf));
    }
}

Output:

1.57

Upvotes: 0

Related Questions