Search code examples
javastringparsingdatetimedate-conversion

parsing String into java.util.Date


I was trying to convert an input date String "2018-09-31" to date.I was expecting it to be "2018-09-30" or at least "2018-09-30T00:00:00Z". But surprisingly it turned out to be something different. Can someone explain why this so? and how can it be made to match input date only?

import org.apache.commons.lang3.time.FastDateFormat;

private static final FastDateFormat dateOnlyFormat = FastDateFormat.getInstance("yyyy-MM-dd");

@Test
public void getSQLDateWithOnlyDate() {
    java.util.Date date = null;
    try {
        String dateString = "2018-09-31";
        date = dateOnlyFormat.parse(dateString);
        System.out.println("input date : " + dateString);
        System.out.println("date.toInstant() : " + date.toInstant());
        System.out.println("date.toString() : " + date.toString());
    } catch (ParseException e) {
        e.printStackTrace();
    }

}

output

input date : 2018-09-31
date.toInstant() : 2018-09-30T14:00:00Z
date.toString() : Mon Oct 01 00:00:00 AEST 2018

Solution

  • What went wrong?

    OH GOD SPIDERS already explained some of it in a comment. There are 30 days in September. Your formatter extrapolates: 31 September is taken to mean the day after 30 September, that is, 1 October.

    But the time of day, 14:00:00? This is a time zone issue. Your formatter when not told otherwise uses your JVM’s time zone setting. This might for example be Australia/Sydney, at least your Date renders it as AEST, which I am taking to mean Australian Eastern Standard Time. So your string is parsed into 1 October at 00:00:00 at offset +10:00 from UTC. This is the same point in time as 30 September 14:00:00 UTC. Since Instant.toString() renders the point in time in UTC (denoted by the Z suffix), you get 2018-09-30T14:00:00Z.

    Fixing it

    When they try to play tricks on your program, I suggest that you really want to object. I understood you wanted a date only, not a time of day.

        String dateString = "2018-09-31";
    
        try {
            LocalDate.parse(dateString);
        } catch (DateTimeParseException dtpe) {
            System.out.println(dtpe);
        }
    

    This prints:

    java.time.format.DateTimeParseException: Text '2018-09-31' could not be parsed: Invalid date 'SEPTEMBER 31'

    If you did want the extrapolating behaviour you observed:

        DateTimeFormatter dateFormatter = DateTimeFormatter.ISO_LOCAL_DATE
                .withResolverStyle(ResolverStyle.LENIENT);
        System.out.println(LocalDate.parse(dateString, dateFormatter));
    

    2018-10-01

    You said you had expected 2018-09-30, but I know of no straightforward way to give you that. My first suggestion is you think again and ask if you really need this.

    In my code I am using java.time, the modern Java date and time API. I recommend it. A LocalDate is a date without time of day. It seems to me this both suits your requirements well and from the outsets prevents your time zone issue.

    Your method name, getSQLDateWithOnlyDate, may suggest that you intend to use your date with an SQL database? One advantage of LocalDate (and other java.time types) is you can pass it directly to your PreparedStatement’s setObject methods, providing that you are using at least Java 8 and at least JDBC 4.2. Your JPA implementation too should be happy to accept a LocalDate object.

    Link: Oracle tutorial: Date Time explaining how to use java.time.