正如许多人已经说过的,ET 不是时区。是常用的缩写both EST and EDT(东部标准时间和东部夏令时间),但也有more than one timezone that uses it。
短名称(如EST 和EDT)也不是时区,因为这样的缩写是ambiguous and not standard。有more than one timezone that can use the same abbreviations。
理想的情况是使用IANA timezones names(始终采用Region/City 的格式,例如America/Sao_Paulo 或Europe/Berlin)。
但是像 EST 和 ET 这样的短名称的使用很普遍,所以我们必须接受它(并采取一些变通办法)。
第一件事是将您要使用的时区定义为ET(这将是一个非常随意的选择,但没有其他方法,因为ET 不明确)。在下面的示例中,我选择了America/New_York。您可以使用 java.util.TimeZone 类(调用 TimeZone.getAvailableIDs())查看所有可用时区的列表(并选择最适合您需求的时区)。
可以使用java.text.DateFormatSymbols 类覆盖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 新的日期/时间 API
如果您使用的是 Java 8,则可以将此代码替换为 new java.time API。更简单,less bugged and less error-prone than the old SimpleDateFormat and Calendar APIs。
所有相关类都在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),所以当它开始和结束时,你会得到意想不到的结果。
如果我得到 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.
幸运的是,ZonedDateTime 有 withLaterOffsetAtOverlap() 方法,该方法在 DST 结束后的偏移量处返回相应的日期。所以你可以模拟SimpleDateFormat调用这个方法的行为。
但是,如果我得到 DST 开始的日期:
String dateString = "Mar 09, 2008, 2:30 AM ET";
在凌晨 2 点,时钟向前移动到凌晨 3 点,因此不存在凌晨 2 点到 2:59 之间的当地时间。在这种情况下,SimpleDateFormat 和 ZonedDateTime 都将时间调整为凌晨 3:30 并使用 DST 偏移量 (-04:00) - 日期将等同于 2008-03-09T03:30-04:00。