【问题标题】:Java SimpleDateFormat unable to parse "Aug 15, 2017, 4:58 PM ET" with "MMM dd, yyyy, h:mm a z"Java SimpleDateFormat 无法使用“MMM dd, yyyy, h:mm a z”解析“2017 年 8 月 15 日,美国东部时间下午 4:58”
【发布时间】:2018-01-24 21:46:35
【问题描述】:

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

我已经尝试了多种模式,具有多种 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 不是时区。 EDT 是东部夏令时间,EST 是东部标准时间。
  • 这是您的策略,因为您不知道字符串的外观?还是因为你想找到哪一个有效???
  • 看来你应该使用EDT而不是ET
  • 或中欧时间的 CET 或 CEST...
  • 实际上,这 3-4 个字符的缩写 e 是true time zones。它们不是标准化的,甚至不是唯一的!

标签: java date parsing timezone simpledateformat


【解决方案1】:

正如许多人已经说过的,ET 不是时区。是常用的缩写both EST and EDT东部标准时间东部夏令时间),但也有more than one timezone that uses it

短名称(如ESTEDT)也不是时区,因为这样的缩写是ambiguous and not standard。有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 类(调用 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.

幸运的是,ZonedDateTimewithLaterOffsetAtOverlap() 方法,该方法在 DST 结束后的偏移量处返回相应的日期。所以你可以模拟SimpleDateFormat调用这个方法的行为。


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

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

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

【讨论】:

  • 请记住,一年中将有一个小时无法区分标准时间和 DST,在秋季转换时,时间倒退并且从 0100 到 0200 的小时重复。跨度>
  • @JimGarrison 确实如此。每个 API 的行为方式都不同:SimpleDateFormat 更喜欢 DST 结束后的偏移量,ZonedDateTime 更喜欢之前的偏移量。我已经更新了答案,非常感谢!
  • Aug 不一定是英语,也可以是其他几种语言,例如 e. G。德语。和@JimGarrison,重复的不一定是1到2之间的小时。这是 2 点到 3 点之间的时间。
  • 优秀的答案!向您致敬,先生。
【解决方案2】:

您的格式很好,只是您的日期错误。 ET 不是有效的区域标识符。

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

【讨论】:

    猜你喜欢
    • 2021-09-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-28
    • 2013-07-13
    相关资源
    最近更新 更多