Search code examples
javadatetimesimpledateformatdate-format

How to solve parse exception when timezone has colon?


I am receiving DateTime as a String from a webservice. An example of this DateTime string is: "DateTime":"2021-06-06T04:54:41-04:00".

This 2021-06-06T04:54:41-04:00 more or less matches the ISO-8601 format, so I have used this pattern to parse it: yyyy-MM-dd'T'HH:mm:ssZ. However, the colon in the timezone part of the response DateTime is causing issues. 2021-06-06T04:54:41-04:00 is giving parse exception, but 2021-06-06T04:54:41-0400 is parsing fine.

Below code should explain it better:

public void stringToDate() {

        String pattern = "yyyy-MM-dd'T'HH:mm:ssZ";  //ISO - 8601 Format
        TimeZone timeZoneEST = TimeZone.getTimeZone("US/Eastern");
        SimpleDateFormat sdf = new SimpleDateFormat(pattern, new Locale("en", "US"));
        sdf.setLenient(false);
        sdf.setTimeZone(timeZoneEST);
        
        String timeFromWebService = "2021-06-06T04:54:41-04:00";
        try {
            Date parsedDate = sdf.parse(timeFromWebService); // not working because of colon in timezone part
            System.out.println(parsedDate);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        
        try {
            Thread.sleep(1000);  //sleep to avoid interleaving output from stacktrace (above) and syso (below)
        } catch (InterruptedException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
        
        String timeFromWebServiceModified = "2021-06-06T04:54:41-0400";  //removed colon from timezone part
        try {
            Date parsedDate = sdf.parse(timeFromWebServiceModified); // working because colon is removed in timezone part
            System.out.println(parsedDate);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        
    }

I want to handle this parsing without modifying the response DateTime. Any suggestions on how I can parse the original DateTime. Any suggestion on what pattern to use will be very help full.


Solution

  • 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*.

    Solution using java.time, the modern API:

    import java.time.OffsetDateTime;
    import java.time.format.DateTimeFormatter;
    import java.util.Locale;
    import java.util.stream.Stream;
    
    public class Main {
        public static void main(String[] args) {
            DateTimeFormatter dtf = DateTimeFormatter.ofPattern("u-M-d'T'H:m:s[XXX][XX][X]", Locale.ENGLISH);
            
            //Test
            Stream.of(
                        "2021-06-06T04:54:41-04:00",
                        "2021-06-06T04:54:41-0400",
                        "2021-06-06T04:54:41-04",
                        "2021-06-06T04:54:41Z"                  
            ).forEach(s -> System.out.println(OffsetDateTime.parse(s, dtf)));
        }
    }
    

    Output:

    2021-06-06T04:54:41-04:00
    2021-06-06T04:54:41-04:00
    2021-06-06T04:54:41-04:00
    2021-06-06T04:54:41Z
    

    ONLINE DEMO

    Check How to use OffsetDateTime in JDBC?.

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

    Solution using legacy API:

    SimpleDateFormat does not have a feature to specify optional patterns, the way we do, using the square bracket, with DateTimeFormatter. In this case, you can create multiple instances of SimpleDateFormat and try with each one.

    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.Locale;
    
    public class Main {
        public static void main(String[] args) {
            SimpleDateFormat sfdArr[] = {
                                new SimpleDateFormat("y-M-d'T'H:m:sXXX", Locale.ENGLISH),
                                new SimpleDateFormat("y-M-d'T'H:m:sXX", Locale.ENGLISH),
                                new SimpleDateFormat("y-M-d'T'H:m:sX", Locale.ENGLISH)
            };
            
            String []strDateTimeArr = {
                        "2021-06-06T04:54:41-04:00",
                        "2021-06-06T04:54:41-0400",
                        "2021-06-06T04:54:41-04",
                        "2021-06-06T04:54:41Z"                  
            };
            
            for(String s : strDateTimeArr) {
                Date date = null;
                for(SimpleDateFormat sdf : sfdArr) {
                    try {
                        date = sdf.parse(s);
                    }catch(ParseException e) {
                        //...
                    }
                }
                System.out.println(date);
            }
        }
    }
    

    ONLINE DEMO


    * 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.