Java SimpleDateFormat 无法用 "MMM dd, yyyy, h:mm a z" 解析 "Aug 15, 2017, 4:58 PM ET"

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

我无法解析这个日期。有人注意到任何错误吗?他们似乎都失败了。

我尝试了多种 Locale 类型的模式。

这是我的策略:

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);
    }
}

你的格式没问题,只是日期不对。 ET 不是有效的区域标识符。

使用 TimeZone.getAvailableIDs() 您可以查看有效的区域 ID。

正如许多人所说,ET 不是时区。这是一个缩写,通常用来指代both EST and EDT (Eastern Standard Time and Eastern Daylight Time), but there are more than one timezone that uses it

短名称(如 ESTEDT)也不是时区,因为这样的缩写是 ambiguous and not standard. There are more than one timezone that can use the same abbreviations.

最理想的是使用 IANA timezones names(始终采用 Region/City 格式,如 America/Sao_PauloEurope/Berlin)。 但是像 ESTET 这样的短名称的使用很普遍,所以我们必须接受它(并做一些变通办法)。

第一件事是定义你想使用哪个时区作为ET(这将是一个非常随意的选择,但没有其他办法,因为ET 是不明确的)。在下面的示例中,我选择了 America/New_York。您可以使用 java.util.TimeZone class(调用 TimeZone.getAvailableIDs())查看所有可用时区的列表(并选择最适合您需要的时区)。

可以使用 java.text.DateFormatSymbols class 覆盖 SimpleDateFormat 使用的短名称。因此,一种解决方案是获取当前符号并仅覆盖我们想要的时区:

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));

这会将 ET 解析为 America/New_York,所有其他现有的内置区域不会受到影响。

Check the javadoc 有关 DateFormatSymbols 的更多详细信息。

另请注意,我使用了 Locale.ENGLISH,因为月份名称 (Aug) 是英文的。如果我不指定语言环境,将使用系统的默认设置,并且不能保证始终是英语。即使默认值是正确的,也可以在不通知的情况下更改,即使在运行时也是如此,因此最好使用明确的语言环境。


Java新Date/TimeAPI

如果您使用的是 Java 8,则可以将此代码替换为 new java.time API. It's easier, less bugged and less error-prone than the old SimpleDateFormat and Calendar APIs

所有相关的 class 都在 java.time 包中。您只需定义 java.util.Set 个首选区域并将其设置为 java.time.format.DateTimeFormatter。然后你将它解析为 java.time.ZonedDateTime - 如果你仍然需要使用 java.util.Date,你可以轻松地转换它:

// 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());

夏令时问题

有一些极端情况。 America/New_York 时区 has Daylight Saving Time (DST),因此当它开始和结束时,您可能会有意想不到的结果。

如果我得到夏令时结束的日期:

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

凌晨 2 点,时钟将 1 小时调回凌晨 1 点,因此凌晨 1 点和 1:59 上午之间的当地时间存在两次(在 DST 和非 DST 偏移中)。

SimpleDateFormat 将在 DST 结束后 (-05:00) 获得偏移量,因此日期将等同于 2008-11-02T01:30-05:00,而 ZonedDateTime 将在 (-04:00) 并且日期将等同于 2008-11-02T01:30-04:00.

幸运的是,ZonedDateTimewithLaterOffsetAtOverlap()方法,即returns夏令时结束后偏移处的相应日期。因此,您可以模拟 SimpleDateFormat 调用此方法的行为。


但是,如果我得到 DST 开始的日期:

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

凌晨 2 点,时钟拨快到凌晨 3 点,因此凌晨 2 点到 2:59 之间的当地时间不存在。在这种情况下,SimpleDateFormatZonedDateTime 都会将时间调整为 3:30 AM 并使用 DST 偏移量 (-04:00) - 日期将等同于 2008-03-09T03:30-04:00.