3

I have a String that look like this:

String str = "Jun 26th 2021, 04:30:15 pm NY";

I want to convert it to ZonedDateTime, for this I use DateTimeFormatterBuilder:

DateTimeFormatter formatter = new DateTimeFormatterBuilder()
        .parseCaseInsensitive()
        .appendPattern("MMM dd'th' uuuu, h:mm:ss a z")
        .toFormatter(Locale.US);
ZonedDateTime result = ZonedDateTime.parse(str, formatter);

but the parser is not happy with this format I got this error:

java.time.format.DateTimeParseException: Text 'Jun 26th 2021, 04:30:15 pm NY' could not be parsed at index 27

It seems that NY not covered by the z, any idea about this error please, also is there any trick to avoid 'th' in the parser ?

Pekka
  • 141
  • 2
  • 11
  • 1
    Well, NY is not a recognised timezone. Java doesn't know what NY means. What other custom timezone names can be there, instead of NY? – Sweeper Jun 26 '21 at 09:46
  • 1
    See [this answer](https://stackoverflow.com/a/50369812/5133585) for how to handle the `th` part. – Sweeper Jun 26 '21 at 09:52

2 Answers2

3

DateTimeFormatter#parse(CharSequence, ParsePosition) is at your disposal.

Note that NY is not the name of a timezone. The naming convention for timezone is Region/City e.g. Europe/Paris. You can get the list of timezone names using ZoneId#getAvailableZoneIds.

Also, for day-of-month with ordinal e.g. 26th, you can build a Map as shown in the following code.

Demo:

import java.text.ParsePosition;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
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) {
        String strDateTime ="Jun 26th 2021, 04:30:15 pm NY";
    
        DateTimeFormatter dtf = new DateTimeFormatterBuilder()
                .parseCaseInsensitive()
                .appendPattern("[MMMM][MMM] ") // caters for both full name and 3-letter abbv.
                .appendText(ChronoField.DAY_OF_MONTH, ordinalMap())
                .appendPattern(" u, h:m:s a")
                .toFormatter(Locale.ENGLISH);

        LocalDateTime ldt = LocalDateTime.from(dtf.parse(strDateTime, new ParsePosition(0)));
        ZonedDateTime zdt = ldt.atZone(ZoneId.of("America/New_York"));
        System.out.println(zdt);
    }
    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-26T16:30:15-04:00[America/New_York]

ONLINE DEMO

Learn more about the modern Date-Time API from Trail: Date Time.

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

Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110
  • 2
    It is also worth noting that in the future when OP does have more timezones to handle, they can read where the parsing ended, get the substring from that index, and look it up in a `Map`. – Sweeper Jun 26 '21 at 11:49
2

As highlighted by Sweeper, NY is not a recognised timezone by Java. To minimize changes to your code you can use the DateTimeFormatter#withZone method of your DateTimeFormatter formatter returning a copy of your formatter with the new override zone like below:

String str = "Jun 26th 2021, 04:30:15 pm"; //<-- erased NY
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
        .parseCaseInsensitive()
        .appendPattern("MMM dd'th' uuuu, h:mm:ss a")
        .toFormatter(Locale.US);
ZonedDateTime result = ZonedDateTime.parse(str, formatter.withZone(ZoneId.of("America/New_York")));
System.out.println(result); //<-- 2021-06-26T16:30:15-04:00[America/New_York]
dariosicily
  • 4,239
  • 2
  • 11
  • 17
  • Also you can use `.toFormatter().withZone(ZoneId.of("America/New_York")).withLocale(Locale.US);` – Pekka Jun 26 '21 at 10:39
  • @Pekka You are right, I wrote it in this way just to put modifies to your code at the end. – dariosicily Jun 26 '21 at 10:44
  • This solution will fail for `21st`, `22nd` etc. as you have used a fixed ordinal, `th` in your format. Check my answer to learn the correct way of dealing with ordinal with day-of-month. – Arvind Kumar Avinash Jun 26 '21 at 10:50
  • 1
    @ArvindKumarAvinash, I upvoted your solution about the mode you solved the `th` problem, I see you used `Locale.English` instead of `Locale.US` . English is not my first language , there is some particular reason for this choice ? – dariosicily Jun 26 '21 at 10:54
  • 1
    @dariosicily - Thanks. `Locale.ENGLISH` specifies just the language in which the date-time string is. As you already know, `Locale` has both country and language. When we deal with just English, `Locale.ENGLISH` or `Locale.ROOT` is sufficient. Please check [Never use SimpleDateFormat or DateTimeFormatter without a Locale](https://stackoverflow.com/a/65544056/10819573) for more examples. – Arvind Kumar Avinash Jun 26 '21 at 10:59
  • Your solution is very helpfull, but yes I forgot about `th st` part, the answer of @ArvindKumarAvinash cover this part, I will mark it as accepted answer sorry :) – Pekka Jun 26 '21 at 12:27
  • @Pekka, no problem :) – dariosicily Jun 26 '21 at 13:02