2

I had help setting up a function to take two strings and merge them into a date object. Java - Take two strings and combine into a single date object

This has been working fine until it tries to parse 1st June, then i get the below error

Exception in thread "AWT-EventQueue-0" java.time.format.DateTimeParseException: Text '1st June' could not be parsed, unparsed text found at index 7
    at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1952)
    at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1851)
    at java.time.LocalDate.parse(LocalDate.java:400)
    at Timetable.ClassManager.parseDate(ClassManager.java:201)
    at Timetable.GoogleAPI.loadClasses(GoogleAPI.java:133)
    at Timetable.ClassManager.loadClasses(ClassManager.java:58)

The code for the function is

public LocalDateTime parseDate(String strDate, String strTime) {
    strTime = strTime + ":00";
    System.out.println("Date: " + strDate);
    System.out.println("Time: " + strTime);
    
    
    LocalDate date = LocalDate.now();
    DateTimeFormatter dtfForDate = new DateTimeFormatterBuilder()
                                    .parseCaseInsensitive()
                                    .parseDefaulting(ChronoField.YEAR, date.getYear())
                                    .appendPattern("d['th']['st']['rd']['nd'] MMM")
                                    .toFormatter(Locale.ENGLISH);

    DateTimeFormatter dtfForTime = DateTimeFormatter.ofPattern("H:m:s", Locale.ENGLISH);
    
    LocalDateTime ldt = LocalDate.parse(strDate, dtfForDate)
                                    .atTime(LocalTime.parse(strTime, dtfForTime));
    System.out.println("Local Date Time: " + ldt);
    
    return ldt;
}

The two prints give me

Date: 1st June

Time: 9:15:00

I would need to be able to handle both full month names and month abbreviations, i.e., March was set as Mar, April as Apr.

Michael
  • 41,989
  • 11
  • 82
  • 128
Ceri Turner
  • 830
  • 2
  • 12
  • 36
  • well, 1st June isn't a valid format for a Time. that'll be the issue – Stultuske May 20 '21 at 09:01
  • You may well just need `MMMM` for full month name. When you parse dates in May it makes no difference because the full month name and the month abbreviation are both `May` in English, so you didn’t discover your bug until you hit into the difference between `Jun` and `June`. – Ole V.V. May 20 '21 at 12:46
  • If you need to accept `Mar`, `Apr`, `May` and `June`, the gold-plated solution is the `appendText(TemporalField, Map)` method already used for ordinal numbers in the last half of the answer. With this you can control accurately which months are written in full and which are abbreviated. – Ole V.V. May 22 '21 at 05:36

1 Answers1

4

Use the following pattern which will cater to both, the three-letter abbreviated month names as well as the full month names:

.appendPattern("d['th']['st']['rd']['nd'] [MMMM][MMM]")

Demo:

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.util.Locale;

public class Main {
    public static void main(String[] args) {
        System.out.println(parseDate("1st June", "9:15:00"));
        System.out.println(parseDate("1st Jun", "9:15:00"));
    }

    public static LocalDateTime parseDate(String strDate, String strTime) {
        LocalDate date = LocalDate.now();
        DateTimeFormatter dtfForDate = new DateTimeFormatterBuilder()
                                        .parseCaseInsensitive()
                                        .parseDefaulting(ChronoField.YEAR, date.getYear())
                                        .appendPattern("d['th']['st']['rd']['nd'] [MMMM][MMM]")
                                        .toFormatter(Locale.ENGLISH);

        DateTimeFormatter dtfForTime = DateTimeFormatter.ofPattern("H:m:s", Locale.ENGLISH);

        LocalDateTime ldt = LocalDate.parse(strDate, dtfForDate).atTime(LocalTime.parse(strTime, dtfForTime));

        return ldt;
    }
}

Output:

2021-06-01T09:15
2021-06-01T09:15

Update

This update addresses the following concern raised by Meno Hochschild:

Personally, I don't like the misuse of optional sections here. Negative example: "1th Jun" or "2st Jun" would also be successfully parsed but should not.

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        System.out.println(parseDate("1st June", "9:15:00"));
        System.out.println(parseDate("1st Jun", "9:15:00"));
    }

    public static LocalDateTime parseDate(String strDate, String strTime) {
        LocalDate date = LocalDate.now();
        DateTimeFormatter dtfForDate = new DateTimeFormatterBuilder()
                                        .parseCaseInsensitive()
                                        .parseDefaulting(ChronoField.YEAR, date.getYear())
                                        .appendText(ChronoField.DAY_OF_MONTH, ordinalMap())
                                        .appendPattern(" [MMMM][MMM]")
                                        .toFormatter(Locale.ENGLISH);

        DateTimeFormatter dtfForTime = DateTimeFormatter.ofPattern("H:m:s", Locale.ENGLISH);

        LocalDateTime ldt = LocalDate.parse(strDate, dtfForDate).atTime(LocalTime.parse(strTime, dtfForTime));

        return ldt;
    }

    static Map<Long, String> ordinalMap() {
        String[] suffix = { "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th" };
        Map<Long, String> map = new HashMap<>();
        
        for (int i = 1; i <= 31; i++) 
            map.put((long)i, String.valueOf(i) + suffix[(i > 3 && i < 21) ? 0 : (i % 10)]);
        
        return map;
    }
}

Output:

2021-06-01T09:15
2021-06-01T09:15

Courtesy: The logic to build the Map is based on this excellent answer.

Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110
  • 2
    Personally, I don't like the misuse of optional sections here. Negative example: "1th Jun" or "2st Jun" would also be successfully parsed but should not. – Meno Hochschild May 20 '21 at 11:56
  • It’s a fair point, @MenoHochschild. I really see it more as a comment on the question than a comment on the answer taking over the same practice from the question. Anyone looking for validation of your negative examples may for example use [my answer here](https://stackoverflow.com/a/50369812/5772882) (that question is about formatting, but the formatter I present is fine for parsing too). – Ole V.V. May 20 '21 at 13:00
  • @MenoHochschild - My solution was focused on the main problem: *how to cater to the three-letter abbreviated month names as well as the full month names*. Nonetheless, thanks for the valuable feedback as always. I've added an update to address the concern raised by you. – Arvind Kumar Avinash May 20 '21 at 13:20
  • @OleV.V. - I was aware of the idea of using a `Map` as posted in [this answer](https://stackoverflow.com/a/28614653/10819573). Your solution to build the `Map` is also good. However, I liked more the one which I have referred to in the answer. – Arvind Kumar Avinash May 20 '21 at 13:23
  • There are many good ways to do it. I agree that the one you picked is very good too. There’s no reason to hesitate to use it. – Ole V.V. May 20 '21 at 14:01