How to always write milliseconds in formatted time ?

When we format date time objects with Java's API, very often we prefer to use already defined patterns. They probably cover 3/4 of total needs. But one of these patterns is a trap - DateTimeFormatter.ISO_OFFSET_DATE_TIME.

This pattern is often used when we need milliseconds precision in formatted date. However, it doesn't guarantee uniform conversion and because of that we can process the formats with 1, 2, 3 or even 0 milliseconds ! Take a look at following tests:

"default Java format" should "not write 0 milliseconds from converted date string" in {
  val dateToConvert = "2017-01-01T20:30:18.000+02:00"
  val utcDateTime = ZonedDateTime.parse(dateToConvert).withZoneSameInstant(UtcZone)

  val convertedDate = utcDateTime.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)

  convertedDate shouldEqual "2017-01-01T18:30:18Z"
}

"default Java format" should "not write last milliseconds from converted date string" in {
  val dateToConvert = "2017-01-01T20:30:18.120+02:00"
  val utcDateTime = ZonedDateTime.parse(dateToConvert).withZoneSameInstant(UtcZone)

  val convertedDate = utcDateTime.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)

  convertedDate shouldEqual "2017-01-01T18:30:18.12Z"
}

"default Java format" should "not write last 2 milliseconds from converted date string" in {
  val dateToConvert = "2017-01-01T20:30:18.100+02:00"
  val utcDateTime = ZonedDateTime.parse(dateToConvert).withZoneSameInstant(UtcZone)

  val convertedDate = utcDateTime.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)

  convertedDate shouldEqual "2017-01-01T18:30:18.1Z"
}

In order to solve the issue and keep 1 common format, we'd construct custom DateTimeFormatter:

private val IsoOffsetDateTimeWithConsistentMillis = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX")

"custom Java format" should "write 0 milliseconds from converted date string" in {
  val dateToConvert = "2017-01-01T20:30:18.000+02:00"
  val utcDateTime = ZonedDateTime.parse(dateToConvert).withZoneSameInstant(UtcZone)

  val convertedDate = utcDateTime.format(IsoOffsetDateTimeWithConsistentMillis)

  convertedDate shouldEqual "2017-01-01T18:30:18.000Z"
}

"custom Java format" should "write last milliseconds from converted date string" in {
  val dateToConvert = "2017-01-01T20:30:18.120+02:00"
  val utcDateTime = ZonedDateTime.parse(dateToConvert).withZoneSameInstant(UtcZone)

  val convertedDate = utcDateTime.format(IsoOffsetDateTimeWithConsistentMillis)

  convertedDate shouldEqual "2017-01-01T18:30:18.120Z"
}

"custom Java format" should "write last 2 milliseconds from converted date string" in {
  val dateToConvert = "2017-01-01T20:30:18.100+02:00"
  val utcDateTime = ZonedDateTime.parse(dateToConvert).withZoneSameInstant(UtcZone)

  val convertedDate = utcDateTime.format(IsoOffsetDateTimeWithConsistentMillis)

  convertedDate shouldEqual "2017-01-01T18:30:18.100Z"
}