How do I simply parse a date without a year specified?

Modern answer (valid from 2014 and on): I first declare an auxiliary method:

private static LocalDateTime parseWithDefaultYear(String stringWithoutYear, int defaultYear) {
    DateTimeFormatter parseFormatter = new DateTimeFormatterBuilder()
            .appendPattern("dd MMM HH:mm:ss")
            .parseDefaulting(ChronoField.YEAR, defaultYear)
            .toFormatter(Locale.ENGLISH);
    LocalDateTime dateTime = LocalDateTime.parse(stringWithoutYear, parseFormatter);
    return dateTime;
}

Then we can do:

    ZoneId zone = ZoneId.of("America/Argentina/Jujuy");
    String stringWithoutYear = "13 Dec 12:00:00";
    LocalDateTime now = LocalDateTime.now(zone);
    int defaultYear = now.getYear();
    LocalDateTime dateTime = parseWithDefaultYear(stringWithoutYear, defaultYear);
    if (dateTime.isAfter(now)) { // in the future
        defaultYear--;
        dateTime = parseWithDefaultYear(stringWithoutYear, defaultYear);
    }
    System.out.println(dateTime);

When running today (June 2018) this prints:

2017-12-13T12:00

You may ask why I want to parse a second time with a new formatter in case the first attempt gives a date in the future. Wouldn’t it be simpler just to subtract 1 year? While java.time certainly has good support for this, I chose a second parsing to handle the corner case of February 29 in leap years better. Imagine the code is run in the first couple of months of 2021 and the string is 29 Feb 12:00:00. Since 2021 is not a leap year, Java will choose February 28, the last day of the month. Subtracting 1 year would give February 28, 2020, which would be incorrect since 2020 is a leap year. Instead my new parsing with 2020 as default year gives the correct February 29, 2020.

Messages:

  • While the other answers were good answers in 2011, the classes Date, SimpleDateFormat, Calendar and GregorianCalendar are now long outdated, so I recommend avoiding them. The modern classes generally are more programmer friendly and come with fewer unpleasant surprises.
  • Always give correct time zone. Using an incorrect time zone is likely to cause the test for a future date-time to be inaccurate, risking that the result is off by 1 year.
  • Always give explicit locale. In this case I took “Dec” for English and provided Locale.ENGLISH.
  • Since you consider it safe to assume that the date is within the last year, I invite you to consider whether is it also safe to assume it’s within the last 4, 6 or 8 months, for example? If so, testing whether this is the case will give you a better validation:

    if (dateTime.isBefore(now.minusMonths(5))) {
        throw new IllegalArgumentException("Not within the last 5 months: " + stringWithoutYear);
    }
    

Leave a Comment