3

Exact error: java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: DayOfMonth

Below is a HashMap I've created where we can see only 3 months of data are present. The end goal is to create/update the MAP dynamically with last 12 moths of data, from current month.

Any month other than these 3 months will have value as 0L.

Map<String, Long> expectedValues = new HashMap<>();
expectedValues.put("2021-06-01", 10L);
expectedValues.put("2021-07-01", 20L);
expectedValues.put("2021-08-01", 30L);

I have written this logic to check and populate 0L for non-existing months. However getting the error while using the DateTimeFormatter

YearMonth thisMonth = YearMonth.now();
for (int i = 0; i < 12; i++) {
    // DateTimeFormatter monthYearFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    DateTimeFormatter monthYearFormatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
    String prevYearMonthStr = thisMonth.minusMonths(i).format(monthYearFormatter);
    if (!expectedValues.containsKey(prevYearMonthStr)) {
        expectedValues.put(prevYearMonthStr, 0L);
    }
}

How can we fix this error.

Youcef LAIDANI
  • 55,661
  • 15
  • 90
  • 140
sandeep
  • 3,061
  • 11
  • 35
  • 54
  • 2
    A `YearMonth` doesn't have a day, so you can't use a formatter which wants one. – tgdavies Sep 06 '21 at 08:51
  • 1
    Are you sure you want the map key of `expectedValues` to be a `String`? Of course, it depends on your exact use case, but in general, I would have used a `LocalDate` instead. – MC Emperor Sep 06 '21 at 08:54
  • @MC Emperor Yeah the use case requires it to be String. – sandeep Sep 06 '21 at 08:54
  • You are using a `String` that represents a full date (`LocalDate`) with year, month of year and day of month, but a `YearMonth` does not have a day of month. Can't you use `String`s like `"2021-06"` instead and parse them using a `DateTimeFormatter.ofPattern("uuuu-MM")`? – deHaar Sep 06 '21 at 09:02
  • No I need in the format of "2021-08-01". Is there any package other than YearMonth which can serve the purpose ? – sandeep Sep 06 '21 at 09:35
  • Related/similar: [Format a date using the new date time API](https://stackoverflow.com/questions/23069370/format-a-date-using-the-new-date-time-api) – Ole V.V. Sep 08 '21 at 06:29

1 Answers1

4

Well, YearMonth has only a year and a month field. Then you are trying to format it using an ISO_LOCAL_DATE_TIME formatter, which expects the Temporal (in your case YearMonth) to support a day-of-month field.

That obviously won't work.

You could use YearMonth's atDay method, which converts the YearMonth to a LocalDate with the specified day-of-month. Next, this LocalDate can be formatted using the predefined ISO_LOCAL_DATE formatter, which is equal to a formatter with the pattern string uuuu-MM-dd.

YearMonth thisMonth = YearMonth.now();
DateTimeFormatter format = DateTimeFormatter.ISO_LOCAL_DATE;
for (int i = 0; i < 12; i++) {
    String prev = thisMonth.minusMonths(i).atDay(1).format(format);
    if (!expectedValues.containsKey(prev)) {
        expectedValues.put(prev, 0L);
    }
}

Demo

Or using Java 8 features:

static Map<String, Long> transform(Map<String, Long> expectedValues) {
    DateTimeFormatter format = DateTimeFormatter.ISO_LOCAL_DATE;
    return Stream.iterate(YearMonth.now(), ym -> ym.minusMonths(1))
        .limit(12)
        .map(ym -> ym.atDay(1).format(format))
        .map(str -> Map.entry(str, expectedValues.getOrDefault(str, 0L)))
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}

This is what happens here:

  • Stream::iterate accepts an initial value, which is the current month, and then iterates over each month in the past.
  • limit makes sure we have 12 values, otherwise we would have an infinite stream.
  • The first map converts the YearMonth to a LocalDate with the first day of the month. Then it formats it using the pattern uuuu-MM-dd.
  • The second map creates a Map.Entry with the stream element as key, and as value either the value from the expectedValues map, or 0L if the key doesn't exist. Of course, these two map calls could be merged.
  • Then we collect these Map.Entry<String, Long> elements currently contained in the stream, to a Map.

Demo

MC Emperor
  • 22,334
  • 15
  • 80
  • 130