【问题标题】:Unable to obtain LocalDate from TemporalAccessor for week based string无法从 TemporalAccessor 获取基于周的字符串的 LocalDate
【发布时间】:2019-12-20 02:06:59
【问题描述】:

我正在尝试将格式为“YYYYww”(例如 201901)的简单字符串解析为 LocalDate,但我的尝试都没有成功。

我尝试通过简单地使用模式“YYYYww”以及手动将值附加到 FormatterBuilder 来解析它。 由于我的输入字符串不包含一天,我还将格式化程序配置为默认为星期日。

下面是运行 Java 8 (IBM JRE 8.0.5.25) 时对我来说失败的代码。

public static void main(String[] args) {
    formatter1(); // Unable to obtain LocalDate from TemporalAccessor
    formatter2(); // Text '201901' could not be parsed at index 0
    formatter3(); // Text '201901' could not be parsed at index 6
}

public static void formatter1() {
    DateTimeFormatter formatter = new DateTimeFormatterBuilder()
            .appendValue(WeekFields.ISO.weekBasedYear(), 4, 4, SignStyle.NEVER)
            .appendValue(WeekFields.ISO.weekOfYear(), 2, 2, SignStyle.NEVER)
            .parseDefaulting(WeekFields.ISO.dayOfWeek(), DayOfWeek.SUNDAY.getValue())
            .toFormatter();

    LocalDate.parse("201901", formatter);
}

public static void formatter2() {
    DateTimeFormatter formatter = new DateTimeFormatterBuilder()
            .appendPattern("YYYYww")
            .parseDefaulting(WeekFields.ISO.dayOfWeek(), DayOfWeek.SUNDAY.getValue())
            .toFormatter();

    LocalDate.parse("201901", formatter);
}

public static void formatter3() {
    DateTimeFormatter formatter = new DateTimeFormatterBuilder()
            .parseLenient()
            .appendPattern("YYYYww")
            .parseDefaulting(WeekFields.ISO.dayOfWeek(), DayOfWeek.SUNDAY.getValue())
            .toFormatter();

    LocalDate.parse("201901", formatter);
}

从示例代码中可以看出,我收到了不同的错误消息,尤其是第一个示例让我感到困惑,因为 TemporalAccessor 包含基于周的年份、一年中的星期和工作日,这应该足以构造一个 LocalDate。

Exception in thread "main" java.time.format.DateTimeParseException: Text '201901' could not be parsed: Unable to obtain LocalDate from TemporalAccessor: {WeekOfYear[WeekFields[MONDAY,4]]=1, WeekBasedYear[WeekFields[MONDAY,4]]=2019, DayOfWeek=7},ISO of type java.time.format.Parsed
    at java.time.format.DateTimeFormatter.createError(DateTimeFormatter.java:1931)
    at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1866)
    at java.time.LocalDate.parse(LocalDate.java:411)
    at Main.formatter1(Main.java:22)
    at Main.main(Main.java:10)
Caused by: java.time.DateTimeException: Unable to obtain LocalDate from TemporalAccessor: {WeekOfYear[WeekFields[MONDAY,4]]=1, WeekBasedYear[WeekFields[MONDAY,4]]=2019, DayOfWeek=7},ISO of type java.time.format.Parsed
    at java.time.LocalDate.from(LocalDate.java:379)
    at java.time.LocalDate$$Lambda$7.000000001061ED20.queryFrom(Unknown Source)
    at java.time.format.Parsed.query(Parsed.java:237)
    at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1862)
    ... 3 more

【问题讨论】:

  • 顺便说一句,调试此类事情的一个小而有用的技巧。正在调用TemporalAccessor a = formatter.parse("...");,这样你就可以在这一步之间进行,而且更容易理解为什么事情会发生这样的事情:)
  • 我无法复制。您的方法formatter2formatter3 在我的Java 11 上都能完美运行。要让formatter1 正常工作,只需将WeekFields.ISO.weekOfYear() 替换为WeekFields.ISO.weekOfWeekBasedYear()
  • @OleV.V.抱歉,我在 Java 8 (IBM) 上运行
  • 我已在 (Oracle) Java 1.8.0_101 上复制。 formatter2() 抛出 java.time.format.DateTimeParseException: Text '201901' could not be parsed at index 0formatter3() 改为抛出 java.time.format.DateTimeParseException: Text '201901' could not be parsed at index 6。我觉得这很有趣,无法立即解释。 (在问题中添加您的 Java 版本将使我能够收回我的反对票。)
  • 非常有趣,所以它似乎已在较新版本的 Java 中得到修复。感谢您的确认,我已在问题中添加了我的版本。

标签: java java-time


【解决方案1】:

您可以使用YearMonth

public static void main(String[] args) {
    System.out.println(YearMonth.parse("201901", DateTimeFormatter.ofPattern("yyyyMM")));
}

输出

2019-01

【讨论】:

  • 一个好主意,但是,如果我正确理解了这个问题,他会尝试使用周数,而不是月数:/
  • 很遗憾,我输入的不是年份和月份,而是年份和星期。
【解决方案2】:

据我所知,你不能这样做。据我所知,您的代码存在两个问题。首先是LocalDate 没有足够的信息,因为您的代码无法知道它应该解析到哪个工作日。

第二个是稍微奇怪的字段的使用。如果您只使用 Chrono 字段,您应该没问题。您可能还可以调整您的模式以包含空格或破折号,因为解析似乎不喜欢它在一个数字中 - 不知道为什么

例如,您可以执行以下操作:

DateTimeFormatter formatter = new DateTimeFormatterBuilder()
        .appendValue(ChronoField.YEAR, 4, 4, SignStyle.NEVER)
        .appendValue(ChronoField.ALIGNED_WEEK_OF_YEAR, 2, 2, SignStyle.NEVER)
        .parseDefaulting(ChronoField.DAY_OF_WEEK, DayOfWeek.SUNDAY.getValue())
        .toFormatter();

LocalDate d = LocalDate.parse("201933", formatter);

但是,使用周数似乎不是一个好的选择。如果你能像@butiri-dan 建议的那样坚持几个月,你很可能会过得更好。

编辑:根据@carlos-heuberger 的建议,它现在在parseDefaulting-setep 中使用ChronoField.DAY_OF_WEEK

【讨论】:

  • 这似乎可行,但我想知道 ChronoFields 是否是正确使用的字段。不幸的是,需要此应用程序的部门根据这些周代码工作,所以我必须与他们合作。
  • 从文档来看,我会说是:docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/…,但它并不过分具体,确实让人想知道是否有更好的选择 :)
  • 我会使用 ChronoField.DAY_OF_WEEK(而不是 WeekFields 天),对于基于周的年/周,IsoFields.WEEK_BASED_YEARIsoFields.WEEK_OF_WEEK_BASED_YEAR
  • 好点,将相应地更新答案,谢谢@carlos-heuberger
  • 很有趣,之前不知道IsoFields。然而,使用 IsoFields.WEEK_BASED_YEARIsoFields.WEEK_OF_WEEK_BASED_YEAR 代替各自的 ChronoFields 会导致第一周的不同处理。如果在我的示例中将默认日期从星期日更改为星期一,则使用 IsoFields 会解析“2018-12-31”,而使用 ChronoField.YEARChronoField.ALIGNED_WEEK_OF_YEAR 会解析为“2019-01-07”。让我想知道在这种情况下使用 ChronoField 是否真的正确。
【解决方案3】:

2019 年的第一周并没有给你一个特定的日期。您需要添加一周中的特定日期。我跑了这个:

System.out.println(LocalDate.parse("2019-01-01", DateTimeFormatter.ofPattern("YYYY-ww-ee")));

得到"2018-12-30"(这是因为2019年1月1日是星期二,即一周的第三天,所以2019年第一周的第一天实际上是"2018-12-30")。请注意,由于某种原因,没有破折号"-" 它不起作用。要了解格式化程序选项,请访问此处:DateTimeFormatter

【讨论】:

    【解决方案4】:

    这适用于 Java 8 及更高版本:

        DateTimeFormatter formatter = new DateTimeFormatterBuilder()
                .appendValue(WeekFields.ISO.weekBasedYear(), 4)
                .appendValue(WeekFields.ISO.weekOfWeekBasedYear(), 2)
                .parseDefaulting(WeekFields.ISO.dayOfWeek(), DayOfWeek.SUNDAY.getValue())
                .toFormatter();
    
        System.out.println(LocalDate.parse("201901", formatter));
    

    输出是:

    2019-01-06

    我已经在 Oracle Java 1.8.0_101 上进行了测试。我没有安装任何 IBM Java。

    使其工作的关键是使用weekOfWeekBasedYear() 而不是weekOfYear()。我的其他变化是装饰性的。 WeekFields.ISO.weekOfYear() 通过计算一年中的星期一来定义周数,这不是您想要的,因为根据 ISO 的第 1 周可能从上一年年底(12 月 29 日至 31 日)附近的星期一开始。因此,它也不适用于 基于周的年份,并且 Java 拒绝将两者一起用于标识周。

    在 Java 9 上,formatter2()formatter3() 都可以工作(在 Oracle Java 9.0.4 上测试)。 Java 8 中似乎存在一个错误,其中相邻字段解析不适用于YYYYww。不过,我已经搜索了 Oracle Java 错误数据库,但没有找到错误描述。

    ISO 8601

    ISO 8601 是定义您使用的周数的标准,它还定义了一周的格式。它就像2012-W48。因此,除非您有充分且非常具体的理由不这样做,否则这是您应该使用的格式,尤其是在您与任何外部系统交换数据时。它也恰好消除了 Java 8 上的相邻字段解析问题。

        DateTimeFormatter formatter = new DateTimeFormatterBuilder()
                .appendPattern("YYYY-'W'ww")
                .parseDefaulting(WeekFields.ISO.dayOfWeek(), DayOfWeek.SUNDAY.getValue())
                .toFormatter();
    
        System.out.println(LocalDate.parse("2019-W01", formatter));
    

    2019-01-06

    链接

    【讨论】:

      猜你喜欢
      • 2018-01-01
      • 1970-01-01
      • 2016-05-19
      • 2019-04-16
      • 1970-01-01
      • 2020-07-29
      • 2017-02-20
      • 2019-12-04
      • 2022-12-13
      相关资源
      最近更新 更多