9

In various Android projects, I use the following static function to parse dates such as 1900-12-31. Of course, this function should be deterministic - but it turns out it is not. Why?

Normally, it parses the date 2010-10-30, for example, to the correct Date instance holding that value. But I have noticed that when I have an IntentService running at the same time and parsing some dates, this function parses the same date as above to 1983-01-20, which is one of the dates parsed in the IntentService. How can this happen?

public static Date dateFromString(String dateStr) {
    SimpleDateFormat mDateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
    SimpleDateFormat mDateTimeFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.getDefault());
    Date dateOut = null;
    try {
        if (dateStr != null) {
            if (dateStr.length() == 7) {
                if (dateStr.startsWith("--")) {
                    dateStr = "0000"+dateStr.substring(1);
                }
            }
            else if (dateStr.length() == 6) {
                if (dateStr.startsWith("-")) {
                    dateStr = "0000"+dateStr;
                }
            }
            else if (dateStr.length() == 5) {
                dateStr = "0000-"+dateStr;
            }
            else if (dateStr.matches("[0-9]{2}\\.[0-9]{2}\\.[0-9]{4}")) {
                dateStr = dateStr.substring(6, 10)+"-"+dateStr.substring(3, 5)+"-"+dateStr.substring(0, 2);
            }
            else if (dateStr.matches("[0-9]{2}\\/[0-9]{2}\\/[0-9]{4}")) {
                dateStr = dateStr.substring(6, 10)+"-"+dateStr.substring(3, 5)+"-"+dateStr.substring(0, 2);
            }
            else if (dateStr.matches("[0-9]{8}")) {
                dateStr = dateStr.substring(0, 4)+"-"+dateStr.substring(4, 6)+"-"+dateStr.substring(6, 8);
            }
            if (dateStr.length() >= 20) {
                String dateTimeStr = dateStr.trim();
                if (dateTimeStr.endsWith("Z")) {
                    dateTimeStr = dateStr.substring(0, dateTimeStr.length()-1)+"+0000";
                }
                if (dateStr.charAt(10) == ' ') {
                    dateTimeStr = dateStr.substring(0, 10)+"T"+dateStr.substring(11);
                }
                try {
                    dateOut = mDateTimeFormat.parse(dateTimeStr);
                }
                catch (Exception e2) {
                    dateOut = mDateFormat.parse(dateStr);
                }
            }
            else {
                dateOut = mDateFormat.parse(dateStr);
            }
        }
    }
    catch (Exception e) {
        dateOut = null;
    }
    return dateOut;
}

Edit: I do the parsing in my Activity's onCreate() where I start an AsyncTask that does the job. In the Activity's onStop(), a background service is started which does the same job. When I close the app (onStop()) and quickly restart it (onCreate()), both seem to be running simultaneously and the error occurrs.

caw
  • 30,999
  • 61
  • 181
  • 291
  • Just as an experiment, what happens if you make `dateFromString` synchronized? – Diego Basch Jan 14 '13 at 00:08
  • Firstly, why is the method `static` and secondly which class is it in? – Squonk Jan 14 '13 at 00:10
  • @DiegoBasch Thanks, I will try! But actually, `synchronized` should by no means be necessary, as it only prevents interference on objects - which are not used here (static). – caw Jan 14 '13 at 00:44
  • @Squonk Actually, it used to be non-static before. But as it does not operate on any objects, I made it static. The problem also occurred for the non-static method. It is located in `MyApp.java` which extends `Application`. – caw Jan 14 '13 at 00:47
  • @MarcoW. : I think the answer from Dheeraj V.S. probably explains it. The fact `SimpleDateFormat` is not thread safe is most likely to be the cause of the problem. – Squonk Jan 14 '13 at 03:44
  • I suspect the code you have shown is not the source of the problem. somewhere you are taking the results of this function and storing it in a data structure. That is where your race condition is most likely occurring. Take a look at the code of where you store the results -- is that data structure synchronized? – iagreen Jan 14 '13 at 13:04
  • @iagreen I have checked that the problem is not caused by external data structures or methods, it was just in this method where I logged all the input and output. As you can see, the thread-safety of `SimpleDateFormat` was the problem. – caw Jan 14 '13 at 13:07

1 Answers1

7

The documentation of SimpleDateFormat says:

SimpleDateFormat is not thread-safe. Users should create a separate instance for each thread.

There you go. Just create the SimpleDateFormat object separately in each thread and pass it to the method.

Dheeraj Vepakomma
  • 26,870
  • 17
  • 81
  • 104
  • 1
    He does create a separate instance in each thread, in fact he creates two new instances in every call. – iagreen Jan 14 '13 at 13:02
  • 1
    Thank you! I didn't expect the `parse()` method to have side effects and change some of the class's fields - but in fact the `parse()` method (which is inherited from the `Date` class) writes to `Date`'s field `calendar`. Thus, it's logical that concurrent use of `parse()` causes problems. – caw Jan 14 '13 at 13:05