Search code examples
androidsimpledateformatdatetimeformatter

DateTimeFormatter and SimpleDateFormat produce different results using same input string


We currently in the process of replacing SimpleDateFormat with DateTimeFormatter. During this I came across a weird behavior. There is a difference in milliseconds which I can't explain to myself. Here is the code:

val timeString = "2021-09-17T13:37:00.09Z"
val newFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX").withZone(ZoneId.of("UTC"))
val oldFormatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX", Locale.US)

val zonedDateTime = newFormatter.parse(timeString)
val newFormatterDate = Date.from(zonedDateTime.toInstant())
val oldFormatterDate = oldFormatter.parse(timeString)

Assert.assertEquals(newFormatterDate, oldFormatterDate) // false
// newFormatterDate.time is 1631885820090 and 
// oldFormatterDate.time is 1631885820009

I found a lot of posts here stating that we shouldn't use SimpleDateFormat anymore.

But can someone explain to me how this could happen? Do we have a bug in our code or misunderstood something?

Edit: The solution provided by @Ole V.V.'s link (How to parse date-time with two or three milliseconds digits in java?) may solve the bug I encounter but it does not answer the question/explain why these two formatters produce different results.


Solution

  • The modern, DateTimeFormatter considers the digits after seconds as the fraction of a second whereas the legacy, SimpleDateFormat considers the digits after the seconds as the number of milliseconds.

    Let's see how DateTimeFormatter processes it:

    0.09 seconds = 0.09 * 1000 ms = 90 ms
    

    On the other hand, SimpleDateFormat processes it as 09 milliseconds = 9 milliseconds.

    By the way, while using the modern Date-Time API, you do not need to use a DateTimeFormatter explicitly to parse your Date-Time string because it is already in ISO 8601 format. The modern Date-Time API is based on ISO 8601 and does not require using a DateTimeFormatter object explicitly as long as the Date-Time string conforms to the ISO 8601 standards.

    Demo:

    import java.time.Instant;
    
    public class Main {
        public static void main(String[] args) {
            System.out.println(Instant.parse("2021-09-17T13:37:00.09Z").toEpochMilli());
        }
    }
    

    Output:

    1631885820090
    

    Learn more about the modern Date-Time API* from Trail: Date Time.


    * 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. Note that Android 8.0 Oreo already provides support for java.time.