Sina
Sina

Reputation: 31

Java SimpleDateFormat unable to parse "Aug 15, 2017, 4:58 PM ET" with "MMM dd, yyyy, h:mm a z"

I am unable to parse this date. Anyone notice any mistakes? They all seem to fail.

I have tried multiple patterns with multiple Locale types.

Here is my strategy:

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Locale;

public class Test {

static void check(Locale locale){

    String dateString = "Aug 15, 2017, 4:58 PM ET";

    DateFormat format1 = new SimpleDateFormat("MMM dd, yyyy, h:mm aa zz", locale);
    DateFormat format2 = new SimpleDateFormat("MMM dd, yyyy, h:mm a z", locale);
    DateFormat format3 = new SimpleDateFormat("MMM dd, yyyy, hh:mm a z", locale);
    DateFormat format4 = new SimpleDateFormat("MMM dd, yyyy, K:mm a z", locale);
    DateFormat format5 = new SimpleDateFormat("MMM dd, yyyy, KK:mm a z", locale);

    for (DateFormat format : Arrays.asList(format1, format2, format3, format4, format5)) {

        try {
            System.out.println(format.parse(dateString));
        } catch (ParseException ex){
            System.out.println("Failed");
        }
    }

}

public static void main(String[] args) {

    Arrays.asList(Locale.ENGLISH, Locale.UK, Locale.US, Locale.CANADA, Locale.ROOT, Locale.getDefault()).forEach(Test::check);
    }
}

Upvotes: 2

Views: 905

Answers (2)

user7605325
user7605325

Reputation:

As many have already said, ET is not a timezone. It's an abbreviation commonly used to refer to both EST and EDT (Eastern Standard Time and Eastern Daylight Time), but there are more than one timezone that uses it.

Short names (like EST and EDT) aren't timezones as well, because such abbreviations are ambiguous and not standard. There are more than one timezone that can use the same abbreviations.

The ideal is to use IANA timezones names (always in the format Region/City, like America/Sao_Paulo or Europe/Berlin). But the use of short names like EST and ET is widespread and common, so we must live with it (and do some workarounds as well).

The first thing is to define which timezone you want to use as ET (and this will be a very arbitrary choice, but there's no other way since ET is ambiguous). In the example below, I've chosen America/New_York. You can see the list of all available timezones (and choose one that fits best to your needs) using the java.util.TimeZone class (calling TimeZone.getAvailableIDs()).

It's possible to overwrite the short names used by SimpleDateFormat, using the java.text.DateFormatSymbols class. So, one solution is to get the current symbols and overwrite just the timezone we want:

SimpleDateFormat sdf = new SimpleDateFormat("MMM dd, yyyy, h:mm a z", Locale.ENGLISH);

// get current date symbols
String[][] zoneStrings = sdf.getDateFormatSymbols().getZoneStrings();
for (int i = 0; i < zoneStrings.length; i++) {
    // overwrite just America/New_York (my arbitrary choice to be "ET")
    if (zoneStrings[i][0].equals("America/New_York")) {
        zoneStrings[i][2] = "ET"; // short name for standard time
        zoneStrings[i][4] = "ET"; // short name for daylight time
        break;
    }
}
// create another date symbols and set in the formatter
DateFormatSymbols symbols = new DateFormatSymbols(Locale.ENGLISH);
symbols.setZoneStrings(zoneStrings);
sdf.setDateFormatSymbols(symbols);

String dateString = "Aug 15, 2017, 4:58 PM ET";
System.out.println(sdf.parse(dateString));

This will parse ET as America/New_York, and all other existing built-in zones won't be affected.

Check the javadoc for more details about DateFormatSymbols.

Also note that I used Locale.ENGLISH, because the month name (Aug) is in English. If I don't specify the locale, the system's default will be used, and it's not guaranteed to always be English. Even it the default is correct, it can be changed without notice, even at runtime, so it's better to use an explicit locale.


Java new Date/Time API

If you're using Java 8, you can replace this code with the new java.time API. It's easier, less bugged and less error-prone than the old SimpleDateFormat and Calendar APIs.

All relevant classes are in java.time package. You just need to define a java.util.Set of prefered zones and set it to a java.time.format.DateTimeFormatter. Then you parse it to a java.time.ZonedDateTime - if you still need to work with a java.util.Date, you can easily convert it:

// prefered zones
Set<ZoneId> preferredZones = new HashSet<>();
preferredZones.add(ZoneId.of("America/New_York"));

DateTimeFormatter fmt = new DateTimeFormatterBuilder()
    // date and time
    .appendPattern("MMM dd, yyyy, h:mm a ")
    // zone (use set of prefered zones)
    .appendZoneText(TextStyle.SHORT, preferredZones)
    // create formatter (use English locale for month name)
    .toFormatter(Locale.ENGLISH);
String dateString = "Aug 15, 2017, 4:58 PM ET";
// parse string
ZonedDateTime zdt = ZonedDateTime.parse(dateString, fmt);
// convert to java.util.Date
Date date = Date.from(zdt.toInstant());

Daylight Saving Time issues

There are some corner cases. America/New_York timezone has Daylight Saving Time (DST), so when it starts and ends, you can have unexpected results.

If I get the date when DST ends:

String dateString = "Nov 02, 2008, 1:30 AM ET";

At 2 AM, clocks shift 1 hour back to 1 AM, so the local times between 1 AM and 1:59 AM exist twice (in DST and in non-DST offsets).

SimpleDateFormat will get the offset after DST ends (-05:00) so the date will be equivalent to 2008-11-02T01:30-05:00, while ZonedDateTime will get the offset before (-04:00) and the date will be equivalent to 2008-11-02T01:30-04:00.

Fortunately, ZonedDateTime has the withLaterOffsetAtOverlap() method, that returns the corresponding date at the offset after DST ends. So you can emulate SimpleDateFormat's behaviour calling this method.


If I get the date when DST starts, though:

String dateString = "Mar 09, 2008, 2:30 AM ET";

At 2 AM, clocks shift forward to 3 AM, so local times between 2 AM and 2:59 AM don't exist. In this case, both SimpleDateFormat and ZonedDateTime will adjust the time to 3:30 AM and use the DST offset (-04:00) - the date will be equivalent to 2008-03-09T03:30-04:00.

Upvotes: 2

Vampire
Vampire

Reputation: 38724

Your format is fine, it is just your date that is wrong. ET is not a valid zone identifier.

With TimeZone.getAvailableIDs() you can look at valid zone IDs.

Upvotes: 0

Related Questions