【问题标题】:DateTimeFormatter and SimpleDateFormat produce different strings [duplicate]DateTimeFormatter 和 SimpleDateFormat 产生不同的字符串[重复]
【发布时间】:2019-09-30 22:18:41
【问题描述】:

这不是某些人认为的重复。它是关于格式化日期的两个标准 Java 类,它们在自纪元以来的相同毫秒值上生成不同的字符串。

对于自 1883 年某个时间点之前出现的纪元以来的毫秒值,SimpleDateFormat 和 DateTimeFormatter 将产生不同的结果。由于我不明白的原因,DateTimeFormatter 会产生与我预期相差近四分钟的字符串。

这很重要,因为我正在更改一些代码以使用 DateTimeFormatter 而不是 SimpleDateFormat。我们的输入始终是自纪元以来的毫秒数,我需要在更改代码后保持相同的值。

前面的代码会根据毫秒创建一个日期,然后使用 SimpleDateFormat 对其进行格式化。

新代码从毫秒创建一个 Instant,然后从 Instant 创建一个 ZonedDateTime,然后是一个 DateTimeFormatter 来格式化它。

这是我使用 JUnit4 和 Hamcrest 编写的测试。该测试查找自 5 月 13 日 15:41:25 的纪元以来的毫秒数,从 2019 年开始每年向后计算。

对于每一年,它使用 SimpleDateFormat 和 DateTimeFormatter 格式化毫秒,然后比较结果。

@Test
  public void testSimpleDateFormatVersusDateTimeFormatter() throws Exception {
    String formatString = "EEE MMM dd HH:mm:ss zzz yyyy";
    String timeZoneCode = "America/New_York";

    ZoneId zoneId = ZoneId.of(timeZoneCode);

    SimpleDateFormat simpleDateFormat = new SimpleDateFormat(formatString);
    simpleDateFormat.setTimeZone(TimeZone.getTimeZone(timeZoneCode));

    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(formatString);

    for (int year = 0; year < 200; year++) {

      long millis = getMillisSinceEpoch(2019 - year, 5, 13, 15, 41, 25, timeZoneCode);

      System.out.printf("%s%n", new Date(millis));

      // Format using a DateTimeFormatter;
      Instant instant = Instant.ofEpochMilli(millis);
      ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(instant, zoneId);
      String dateTimeFormatterString = dateTimeFormatter.format(zonedDateTime);

      // Format using a SimpleDateFormat
      Date date = new Date(millis);
      String simpleDateFormatString = simpleDateFormat.format(date);

      System.out.println("dateTimeFormatterString = " + dateTimeFormatterString);
      System.out.println("simpleDateFormatString = " + simpleDateFormatString);
      System.out.println();

      assertThat(simpleDateFormatString, equalTo(dateTimeFormatterString));
    }
  }

  private long getMillisSinceEpoch(int year, int month, int dayOfMonth, int hours, int minutes, int seconds, String timeZoneId) {
    TimeZone timeZone = TimeZone.getTimeZone(timeZoneId);
    Calendar calendar = Calendar.getInstance(timeZone);
    calendar.set(Calendar.YEAR, year);
    calendar.set(Calendar.MONTH, month-1);
    calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth);
    calendar.set(Calendar.HOUR_OF_DAY, hours);
    calendar.set(Calendar.MINUTE, minutes);
    calendar.set(Calendar.SECOND, seconds);
    return calendar.getTimeInMillis();
  }

运行此程序,您可以看到从 2019 年到 1884 年的所有年份。因此,对于任何给定年份,您都会看到如下输出:

Mon May 13 12:41:25 PST 1895
dateTimeFormatterString = Mon May 13 15:41:25 EST 1895
simpleDateFormatString = Mon May 13 15:41:25 EST 1895

但一旦到了 1883 年,它就莫名其妙地失败了:

Sun May 13 12:41:25 PST 1883
dateTimeFormatterString = Sun May 13 15:45:23 EST 1883
simpleDateFormatString = Sun May 13 15:41:25 EST 1883


java.lang.AssertionError: 
Expected: "Sun May 13 15:45:23 EST 1883"
     but: was "Sun May 13 15:41:25 EST 1883"```

小时和秒显然是错误的。

顺便说一句,如果我将时区更改为“UTC”,则测试通过。

【问题讨论】:

  • Why when year is less than 1884, it remove few milliseconds? 的可能重复项(一旦我找到答案,就更容易在 SO 上找到重复的目标。)
  • 这不是重复的。这是因为旧的 SimpleDateFormat 和较新的 DateTimeFormatter 自纪元以来对于相同的毫秒值产生不同的结果。
  • 我同意你的观点,我已经投票决定重新开放。

标签: java java-time


【解决方案1】:

根据https://www.timeanddate.com/time/change/usa/new-york?year=1883(这是谷歌搜索“1883 时间调整”的第一个命中):

1883 年 11 月 18 日 - 时区变更(LMT → EST)

当地标准时间快到的时候 1883 年 11 月 18 日,星期日,下午 12:03:58 时钟倒转 0:03:58 到 改为当地时间 1883 年 11 月 18 日星期日中午 12:00:00。

3:58 与您看到的“差不多四分钟”相匹配。 我还没有对此进行测试,但我敢打赌,如果您在数年之外重复数月和数天,它就会发生在那个日期。

另见

【讨论】:

  • 其实我认为这个bug是在SimpleDateFormat中。上面格式化两个日期的代码从 SimpleDateFormat 和 DateTimeFormatter 生成相同的字符串,其值大于或等于 -2717650800000L。对于小于该值的值,SimpleDateFormat 似乎没有考虑 1883 年的时间调整。但很可能我遗漏了一些东西。
猜你喜欢
  • 2021-11-24
  • 1970-01-01
  • 2020-04-24
  • 2022-01-20
  • 1970-01-01
  • 2018-11-26
  • 2020-02-27
  • 2019-12-16
  • 2021-03-25
相关资源
最近更新 更多