Date object SimpleDateFormat not parsing timestamp string correctly in Java (Android) environment

30,748

Solution 1

Try removing the fractional seconds from the format string. I just ran into the same issue, but with a slightly different format. My input format wasn't in ISO format (no "T", and no "Z"), but the symptom was the same -- time was off by some random number of minutes and seconds, but everything else was fine. This is what my log results looked like:

When using the fractional second format:

SimpleDateFormat dateFormater = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSSSS");

# Parsed date: 2011-05-27 17:11:15.271816 => Fri May 27 17:15:46 EDT 2011
# Parsed date: 2011-05-27 17:09:37.750343 => Fri May 27 17:22:07 EDT 2011
# Parsed date: 2011-05-27 17:05:55.182921 => Fri May 27 17:08:57 EDT 2011
# Parsed date: 2011-05-27 16:55:05.69092 => Fri May 27 16:56:14 EDT 2011
# Parsed date: 2011-05-27 16:38:35.50348 => Fri May 27 16:39:25 EDT 2011

I fixed it by removing the fractional seconds from the format.

SimpleDateFormat dateFormater = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

# Parsed date: 2011-05-27 17:11:15.271816 => Fri May 27 17:11:15 EDT 2011
# Parsed date: 2011-05-27 17:09:37.750343 => Fri May 27 17:09:37 EDT 2011
# Parsed date: 2011-05-27 17:05:55.182921 => Fri May 27 17:05:55 EDT 2011
# Parsed date: 2011-05-27 16:55:05.69092 => Fri May 27 16:55:05 EDT 2011
# Parsed date: 2011-05-27 16:38:35.50348 => Fri May 27 16:38:35 EDT 2011

What I think is happening is that my "fractional seconds" part of the input string is too long (the same is true in the OP example). It appears to be expecting only three decimal places. If you do the math (take the first example):

  • fractional seconds = 0.271816 seconds
  • What DateFormat sees is 271816 / 1000 of a second
  • 271816 / 1000 == 271 seconds
  • 271 / 60 = 4 minutes
  • 271 % 60 = 31 seconds
  • 17:11:15 to 17:15:46 is exactly 4 minutes, 31 seconds off

Solution 2

There are three major problems in your code:

  1. You have used .SSSSSS for the fraction of a second whereas the SimpleDateFormat does not support a precision beyond milliseconds (.SSS). It also means that you need to limit the digits in the fraction of a second to three.
  2. You have used Z to parse the timezone offset, -05:00 whereas the correct pattern for this is XXX.
  3. You have used hh for a time in 24-Hour format whereas the correct pattern for this is HH. The symbol, hh is used for a time in 12-Hour (i.e. with am/pm) format.

Apart from this, I recommend you always use Locale with a date parsing/formatting API because parts of a date-time string are represented in different ways in different Locales.

Demo:

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

public class Main {
    public static void main(String[] args) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX", Locale.ENGLISH);
        Date date = sdf.parse("2011-04-11T22:27:18.491-05:00");

        // Print the default string i.e. Date#toString
        System.out.println(date);

        // Print the date-time in a custom format
        sdf.setTimeZone(TimeZone.getTimeZone("GMT-05:00"));
        System.out.println(sdf.format(date));
    }
}

Output:

Tue Apr 12 04:27:18 BST 2011
2011-04-11T22:27:18.491-05:00

Some facts about legacy date-time API:

  1. The java.util.Date object is not a real date-time object like the modern date-time types; rather, it represents the number of milliseconds since the standard base time known as "the epoch", namely January 1, 1970, 00:00:00 GMT (or UTC). When you print an object of java.util.Date, its toString method returns the date-time in the JVM's timezone, calculated from this milliseconds value. If you need to print the date-time in a different timezone, you will need to set the timezone to SimpleDateFormat and obtain the formatted string from it.
  2. The java.util date-time API 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* .

Using modern date-time API:

import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;

public class Main {
    public static void main(String[] args) {
        OffsetDateTime odt = OffsetDateTime.parse("2011-04-11T22:27:18.491726-05:00");

        // Print the default string i.e. OffsetDateTime#toString
        System.out.println(odt);

        // Print the date-time in a custom format. Note: OffsetDateTime#toString drops
        // seconds if it is zero
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSSSSXXX");
        System.out.println(dtf.format(odt));
    }
}

Output:

2011-04-11T22:27:18.491726-05:00
2011-04-11T22:27:18.491726-05:00

Note: For DateTimeFormatter, the symbol, u means year whereas the symbol, y means year-of-era. It doesn't make any difference for a year in the [AD][2] era, but it matters for a year in the BC era. Check this answer to learn more about it.

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


* For any reason, if you have to stick to Java 6 or Java 7, you can use ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7. 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.

Solution 3

Try this, working for me Z should be useed in date, or rmove from Format String

SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss.SSSSSS'Z'");

played_at_local = dateFormat.parse("2011-04-11T22:27:18.491726Z-05:00");
Share:
30,748
Chirag Patel
Author by

Chirag Patel

I work on Rails, Javascript and lately PHP. Enjoy building stuff in general

Updated on March 28, 2021

Comments

  • Chirag Patel
    Chirag Patel about 3 years

    I'm using the SimpleDateFormat object with the Date object as shown below. The problem lis that the Date object shows the wrong date, which is a few minutes off from the original string. The Date object appears to store the time in total milliseconds in the debugger.

    Any ideas on the problem?

    import java.text.SimpleDateFormat;
    
    import java.util.Date;
    
    Date played_at_local; 
    
    dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss.SSSSSSZ");
    
    played_at_local = dateFormat.parse("2011-04-11T22:27:18.491726-05:00"); 
    
    //played_at_local shows "Mon Apr 11 22:35:29 America/Chicago 2011" in debugger
    
  • Scott Wilson
    Scott Wilson about 12 years
    Yes, SimpleDateFormat can't handle microseconds. The easiest thing to do is strip out the extra 3 digits, e.g. date = date.substring(0, date.length()-4)+"Z";
  • Joe
    Joe about 12 years
    @scottw: except that won't always work, as you can see in my example, the last 2 items have only 5 digits, instead of 6; so in some cases, there might be only 1 decimal place, which would be completely wrong! If you simply truncate the string to have only 3 decimal places, then it works fine as well. In our case, we didn't need the microseconds (I'd guess many people would also agree with that), so it turns out just removing the S from the format string is sufficient to get a usable time from it. If you do need the fractional seconds, then yeah, you have to do some string manipulation.
  • Scott Wilson
    Scott Wilson almost 12 years
    thats a good point Joe - my solution only worked as I was working from a service that always provided 6 digits for fractional seconds (including padding with zeroes) so I could get away with string manipulation.
  • Syeda Zunaira
    Syeda Zunaira over 9 years
    Please add further details.