13

With Java 8, the code below parses "18" into year "0018" instead of "2018".

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("M/d/y");
return LocalDate.parse(date, formatter);

input date is "01/05/18".

1) why the result is "0018"? Does DateTimeFormatter not follow the 80-20 rule?

2) How to control SimpleDateFormat parse to 19xx or 20xx? talked about SimpleDateFormat.set2DigitYearStart(Date) can be used to fix the year. Is there something similar to that for DateTimeFormatter?

I was hoping "M/d/y" will parse both 2 and 4 digit years.

"M/d/yy" throws Exception for 4 digit years and parses "01/05/97" to "2097-01-05". Ideally this should be parsed to "1997-01-05".

"M/d/yyyy" throws Exception for 2 digit years.

Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
Jesse Zhuang
  • 388
  • 1
  • 4
  • 14
  • 2
    Do you mean: `M/d/yy`, as you want to support a two-digit year? – Holger Jan 08 '18 at 18:31
  • 1
    Curious issue. I tried using `u` rather than `y` and got same behavior. Example: input "1/2/8" with pattern "M/d/u" = `0008-01-02`. Seems only the double-digit `uu` or `yy` engages the default century of `2000`. – Basil Bourque Jan 08 '18 at 22:05
  • 1
    From [the docs](https://docs.oracle.com/javase/9/docs/api/java/time/format/DateTimeFormatter.html): “**Year:** The count of letters determines the minimum field width below which padding is used. If the count of letters is two, then a reduced two digit form is used. … For parsing, this will parse using the base value of 2000, resulting in a year within the range 2000 to 2099 inclusive. If the count of letters is less than four (but not two), …” – Ole V.V. Jan 09 '18 at 09:31
  • Possible duplicate of [Parsing string to local date doesn't use desired century](https://stackoverflow.com/questions/29490893/parsing-string-to-local-date-doesnt-use-desired-century) – Meno Hochschild Jan 09 '18 at 13:32
  • Of course, if you have a 2-digit-year in input then you must use a 2-digit-symbol yy or uu in pattern. – Meno Hochschild Jan 09 '18 at 13:32

2 Answers2

28

There is not a single string of y or u that will allow you to parse both two and four digit years. However, you may use optional parts in the format pattern string to specify that a two or four digit year may be present:

public static LocalDate parseDateString(CharSequence date) {
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("M/d/[uuuu][uu]");
    return LocalDate.parse(date, formatter);
}

Try it:

    System.out.println(parseDateString("01/05/18"));
    System.out.println(parseDateString("01/06/2018"));

This printed:

2018-01-05
2018-01-06

In the format pattern string you need to put the four digit year first. With the opposite order, when trying to parse a four digit year, the formatter will parse two digits, decide it was successful this far, and then complain about unparsed text after the two digits.

If you want more precise control over how two digit years are interpreted:

    DateTimeFormatter formatter = new DateTimeFormatterBuilder().appendPattern("M/d/")
            .optionalStart()
            .appendPattern("uuuu")
            .optionalEnd()
            .optionalStart()
            .appendValueReduced(ChronoField.YEAR, 2, 2, 1920)
            .optionalEnd()
            .toFormatter();

Using this formatter in the above method let’s try:

    System.out.println(parseDateString("01/05/22"));

This prints:

1922-01-05

Giving 1920 as base (as in my example code) will cause two digit years to end up in the interval from 1920 through 2019. Adjust the value to your requirements.

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
  • 2
    This is the solution I am looking for, specifically the `appendValueReduced(ChronoField.YEAR, 2, 2, 1920)` method. `appendValueReduced(ChronoField.YEAR, 2, 2, LocalDate.now().minusYears(80))` can be used to achieve the behavior of 80-20 rule that I mentioned in the question (with `SimpleDateFormat.set2DigitYearStart(Date)`). – Jesse Zhuang Jan 09 '18 at 18:28
  • 1
    Worth noting that if you want to call `.format()` the result will be wrong. It will have "202121" for the year 2021; both year patterns are output. Which may not be what's desired! – RedYeti Feb 10 '21 at 17:30
0

Change your Formatter string to "M/d/yy"

Vishal Raja
  • 303
  • 1
  • 8