0

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
6
  • 5
    Why don't you have a look at how many days there are in september. That should answer your question ;) When parsing dates the days "roll over" when overflowing so that 33.1 equals 2.2 (31.1+1day+1day) Commented May 17, 2018 at 14:18
  • 3
    java.util.Date is pretty outdated. Commented May 17, 2018 at 14:21
  • You could also use strict date parsing in which case your code would throw an exception and end with an error telling you that there is no 31.9. But it will not turn into the 30.9 in any way. Commented May 17, 2018 at 14:22
  • thanks @OHGODSPIDERS for quick answer. Upstream was trying to play tricks with my client I guess ;) Thanks a ton Commented May 17, 2018 at 14:26
  • When you parse dateString to java.util.Date, you should use DateFormat (i.e., SimpleDateFormat to match the pattern you want). To answer the next part, you passed 2018-09-31 , but your toInstant() method produces 2018-09-30T14:00:00Z instead of 2018-09-31T00:00:00Z because it involves your timezone. Based on your timezone this value changes. Commented May 17, 2018 at 14:31

1 Answer 1

2

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.