11

I wish to remove the Joda-Time library from my project.

I am trying to convert a two digit year to full year. The following code from Joda-Time can fulfil the purpose. Below is the following code of joda-time

DateTimeFormatter TWO_YEAR_FORMATTER = DateTimeFormat.forPattern("yy");
int year = LocalDate.parse("99"", TWO_YEAR_FORMATTER).getYear();
System.out.println(year);

Output: 1999

This is the output that I expect and that makes sense in my situation. However, when I try the same procedure with java.time API, it produces a DatetimeParseException. Below is the following code of java.time API:

DateTimeFormatter TWO_YEAR_FORMATTER = DateTimeFormatter.ofPattern("yy");
int year = LocalDate.parse("99", TWO_YEAR_FORMATTER).getYear();
System.out.println(year);

Stacktrace:

Exception in thread "main" java.time.format.DateTimeParseException: Text '99' could not be parsed: Unable to obtain LocalDate from TemporalAccessor: {Year=2099},ISO of type java.time.format.Parsed
    at java.base/java.time.format.DateTimeFormatter.createError(DateTimeFormatter.java:2017)
    at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1952)
    at java.base/java.time.LocalDate.parse(LocalDate.java:428)
    at scratch.main(scratch.java:10)
Caused by: java.time.DateTimeException: Unable to obtain LocalDate from TemporalAccessor: {Year=2099},ISO of type java.time.format.Parsed
    at java.base/java.time.LocalDate.from(LocalDate.java:396)
    at java.base/java.time.format.Parsed.query(Parsed.java:235)
    at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1948)
    ... 2 more

I failed to see the reason of the stacktrace. It would be nice if someone could help me out to understand the following scenario and also explain how to convert a two digit year to full year using Java 8 time API.

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
Deb
  • 2,922
  • 1
  • 16
  • 32
  • Just out of interest, what's the advantage of using an API to basically do `2000 + year`? – DodgyCodeException Jun 06 '18 at 12:18
  • 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) – Ole V.V. Jun 06 '18 at 12:32
  • @DodgyCodeException Well I just wish to remove the joda-time library from the project – Deb Jun 06 '18 at 12:32

5 Answers5

11

The problem is that you can't parse a year on its own into a LocalDate. A LocalDate needs more information than that.

You can use the parse method of the formatter, which will give you a TemporalAccessor, and then get the year field from that:

int year = TWO_YEAR_FORMATTER.parse("99").get(ChronoField.YEAR);
System.out.println(year);

Addressing the discrepancy between the two: these are two distinct APIs. Yes, they are very similar, and the java.time package was informed by design decisions of JodaTime, but it was never intended to be a drop-in replacement for it.

See this answer if you would like to change the pivot year (by default '99' will resolve to 2099 and not 1999).

Michael
  • 41,989
  • 11
  • 82
  • 128
  • Thanks for your answer. Your programme output as 2099. But I am expecting 1999 as joda time converted and it does make sense. Do you know how can it modified to parse the year before 2000? – Deb Jun 06 '18 at 12:19
6

Why it didn’t work

java.time doesn’t supply default values for month and day of month the way it seems that Joda-Time does.

The message says that it cannot obtain a LocalDate from a year alone, or conversely, it is missing month and day (you may supply your own default values, though, as demonstrated in Mikhail Kholodkov’s answer).

Generally this behaviour is here to help us: it reminds us to supply all the values needed, and makes it clear from the code if any default values are used, and which.

What to do instead

Just use the Year class of java.time. First declare

public static final DateTimeFormatter TWO_YEAR_FORMATTER = new DateTimeFormatterBuilder()
        .appendValueReduced(ChronoField.YEAR, 2, 2, 1950)
        .toFormatter();

Then use

int year = Year.parse("99", TWO_YEAR_FORMATTER).getValue();
System.out.println(year);

Output

1999

Insert your desired base value where I put 1950 to specify a year within 99 years (inclusive) from that year. If you want the year to be in the past including this year, you may use:

private static final Year base = Year.now(ZoneId.of("Asia/Kolkata")).minusYears(99);
public static final DateTimeFormatter TWO_YEAR_FORMATTER = new DateTimeFormatterBuilder()
        .appendValueReduced(ChronoField.YEAR, 2, 2, base.getValue())
        .toFormatter();

BTW, don’t take my word for it, but I think Joda-Time uses current year minus 30 years as base or pivot. If this is so, using 30 instead of 99 in the last snippet will be the most compatible (so will also give you the 1999 you expected).

Alexander Ivanchenko
  • 25,667
  • 5
  • 22
  • 46
Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
5

You can't create a LocalDate because the String you give doesn't provide the information needed to fill all required fields.

You can create a Year though

Year parsed = Year.parse("99", TWO_YEAR_FORMATTER);
int year = parsed.getValue();
daniu
  • 14,137
  • 4
  • 32
  • 53
1

You need to provide default values for DAY_OF_MONTH and MONTH_OF_YEAR

DateTimeFormatter TWO_YEAR_FORMATTER = new DateTimeFormatterBuilder()
                .appendPattern("yy")
                .parseDefaulting(ChronoField.MONTH_OF_YEAR, 1)
                .parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
                .toFormatter();

In addition,

Java-8 uses the range 2000-2099 per default, not like SimpleDateFormat the range -80 years until +20 years relative to today.

Full answer

Since you're parsing years only, in order to have '99' as '1999' your code should be like this:

DateTimeFormatter TWO_YEAR_FORMATTER = new DateTimeFormatterBuilder()
                .appendPattern("")
                .parseDefaulting(ChronoField.MONTH_OF_YEAR, 1)
                .parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
                .appendValueReduced(ChronoField.YEAR_OF_ERA, 2, 2, LocalDate.now().minusYears(80))
                .toFormatter();
Mikhail Kholodkov
  • 23,642
  • 17
  • 61
  • 78
  • 3
    One advantage of using a builder is that you can use for example `.appendValueReduced(ChronoField.YEAR, 2, 2, 1950)` instead of `appendPattern` to specify that year is to be interpreted, in this case as in the range 1950–2049 (inclusive). – Ole V.V. Jun 06 '18 at 12:24
1

You could parse the year directly using formatter.parse(input, Year::from) :

import java.time.*;
import java.time.format.*;
class Main {
  public static void main(String[] args) {
    DateTimeFormatter TWO_YEAR_FORMATTER = DateTimeFormatter.ofPattern("yy");
    Year year = TWO_YEAR_FORMATTER.parse("99", Year::from);
    System.out.println(year);
  }
}

Note that this will output 2099. To output 1999, you should use a specific formatter like the one suggested by Ole V.V. in their answer.

Olivier Grégoire
  • 33,839
  • 23
  • 96
  • 137
  • You may have read the title and not paid attention to the expected result of 1999 (not 2099, as your code will yield)? – Ole V.V. Jun 07 '18 at 06:33
  • @OleV.V. You're half correct. I read the title, but the intent of my answer is not to "show the correct date", but point out that the parser can use the very readable form `formatter.parse(input, Year::from)` to return a `Year`. I hope my edit makes my intent more clear. – Olivier Grégoire Jun 07 '18 at 07:32
  • Thanks. I find it much clearer. And thanks for the friendly link too. – Ole V.V. Jun 07 '18 at 07:33