1

In our Android application, we have a util method for formatting a date to UTC:

 static String formatToUTC(Date date) {
        DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
        return dateFormat.format(date).replace(" ", "T") + "Z";
    }

what sometimes produce the value:

????-??-??T??:??:??.???Z

To fix the issue we should reproduce it first, but we can't reproduce the behavior. We were trying different Locales and pass an invalid new Date() from Long.Max_VALUE, Long.MIN_VALUE.

The question is: What can be a reason for this issue?

Thanks in advance!

Edited:

The date is Android app generated in two ways:

  1. new Date();
  2. new Date(longValue)

The date comes from a worldwide.

Edited 2:

The issue appears on a server side (written on a c#).

We use the Ksoap2 for Android to interact with a server and it fails sometimes trying to convert ????-??-??T??:??:??.???Z to a DateTime object. But main time everything is good.

Oleg Sokolov
  • 1,134
  • 1
  • 12
  • 19
  • 4
    Offtop: instead of magic char replacement and concatenation just use `"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"` pattern. – Oleg Estekhin Jun 05 '18 at 18:13
  • 1
    Where is the garbled string confirmed (e.g., UI level, inside formatToUTC via debugger, etc.)? If there are other methods that take the return value of formatToUTC(Date date) and manipulate/use it, those might be responsible for encoding errors. – Otomatonium Jun 05 '18 at 18:28
  • 2
    Maybe a clue as to where the `Date` is coming from might give a little more insight. User input? Returned from a database (guaranteed format?)? Does the date come from a single region or world wide? Android app generated? If app generated add that code to your question. – Barns Jun 05 '18 at 19:01
  • 1
    @Barns I have added answers to the edited question. – Oleg Sokolov Jun 05 '18 at 19:45
  • 1
    Indeed that is odd behavior. I have tried to generate dates in a loop with `new Date(longValue)` -- even improbable values -- and I cannot duplicate the strange result. Oleg provided a pattern that will allow you to get rid of the `replace()` method. About the only thing you can do is try to catch the unexpected value and log it. – Barns Jun 05 '18 at 20:08
  • 1
    FYI, the troublesome old date-time classes such as `java.util.Date`, `java.util.Calendar`, and `java.text.SimpleDateFormat` are now legacy, supplanted by the [*java.time*](https://docs.oracle.com/javase/10/docs/api/java/time/package-summary.html) classes. Much of the *java.time* functionality is back-ported to Java 6 & Java 7 in the [***ThreeTen-Backport***](http://www.threeten.org/threetenbp/) project. Further adapted for earlier Android in the [***ThreeTenABP***](https://github.com/JakeWharton/ThreeTenABP) project. See [*How to use ThreeTenABP…*](http://stackoverflow.com/q/38922754/642706). – Basil Bourque Jun 05 '18 at 21:25
  • Perhaps check the source code for the `SimpleDateFormat` code to look for a string literal of the Question Mark character. But easier just to replace these awful classes with *java.time*, and move on. – Basil Bourque Jun 05 '18 at 22:52
  • @BasilBourque Thanks for your suggestions, but unfortunately we cant use 3th part libraries in our project. – Oleg Sokolov Jun 06 '18 at 04:43

2 Answers2

2

tl;dr

Instant.now().
       .truncatedTo( ChronoUnit.MILLISECONDS ) ;
       .toString()

2018-01-23T01:23:45.123Z

Thread-safety issue?

That is bizarre behavior I have not heard of. My first guess is a threading problem. The legacy date-time classes are not thread-safe. Their replacements, the java.time classes, are entirely thread-safe.

java.time

You could replace those troublesome old legacy classes. They were supplanted years ago in Java by the modern java.time classes. For Android, see bullets below.

Instant

The java.util.Date class was replaced by java.time.Instant. The Instant class represents a moment on the timeline in UTC with a resolution of nanoseconds (up to nine (9) digits of a decimal fraction).

Instant instant = Instant.now() ;  // Capture the current moment in UTC.

If you want only milliseconds, you can truncate any microseconds or nanoseconds that may be present in your Instant.

Instant instantTruncated = instant.truncatedTo( ChronoUnit.MILLISECONDS ) ;

ISO 8601

Your desired output format is defined by the ISO 8601 standard.

The java.time classes use these standard formats by default when parsing/generating strings. So no need to specify a formatting pattern.

String output = instant.toString();

2018-01-23T01:23:45.123Z


About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.

Where to obtain the java.time classes?

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
0

To help you figure out the issue I would check if the returned String contains any question marks in it, and if it does then throw an Exception. Set a breaker point in your exception and it should let you see exactly which value is breaking your date.

Something along the lines of:

static String formatToUTC(Date date) {
    DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
    String temp = dateFormat.format(date).replace(" ", "T") + "Z"

    if (temp.contains("?"))
        throw new IllegalArgumentException("Invalid date");  //set a breakpoint here

    return temp;
}

Hope this helps

  • Good suggestion if `java.time` is out of bounds. Even better, include the value of `date.getTime()` in the exception message. I would also insert the `if` statement both before and after `replace` and concatenation, with different messages, so as to rule out that one of those string operations is causing the trouble. – Ole V.V. Jun 06 '18 at 08:27