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。
短名称(如 EST
和 EDT
)也不是时区,因为这样的缩写是 ambiguous and not standard. There are 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
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
.
幸运的是,ZonedDateTime
有withLaterOffsetAtOverlap()
方法,即returns夏令时结束后偏移处的相应日期。因此,您可以模拟 SimpleDateFormat
调用此方法的行为。
但是,如果我得到 DST 开始的日期:
String dateString = "Mar 09, 2008, 2:30 AM ET";
凌晨 2 点,时钟拨快到凌晨 3 点,因此凌晨 2 点到 2:59 之间的当地时间不存在。在这种情况下,SimpleDateFormat
和 ZonedDateTime
都会将时间调整为 3:30 AM 并使用 DST 偏移量 (-04:00
) - 日期将等同于 2008-03-09T03:30-04:00
.
我无法解析这个日期。有人注意到任何错误吗?他们似乎都失败了。
我尝试了多种 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。
短名称(如 EST
和 EDT
)也不是时区,因为这样的缩写是 ambiguous and not standard. There are 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
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
.
幸运的是,ZonedDateTime
有withLaterOffsetAtOverlap()
方法,即returns夏令时结束后偏移处的相应日期。因此,您可以模拟 SimpleDateFormat
调用此方法的行为。
但是,如果我得到 DST 开始的日期:
String dateString = "Mar 09, 2008, 2:30 AM ET";
凌晨 2 点,时钟拨快到凌晨 3 点,因此凌晨 2 点到 2:59 之间的当地时间不存在。在这种情况下,SimpleDateFormat
和 ZonedDateTime
都会将时间调整为 3:30 AM 并使用 DST 偏移量 (-04:00
) - 日期将等同于 2008-03-09T03:30-04:00
.