无法从 TemporalAccessor 获取基于周的字符串的 LocalDate

问题描述 投票:0回答:5

我正在尝试将格式为“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 包含基于周的年份、一年中的星期和星期几,这应该足以构造一个本地日期。

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
java java-time
5个回答
3
投票

这适用于 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 Bug 数据库,但没有找到 Bug 描述。

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

链接


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 的建议,现在在

ChronoField.DAY_OF_WEEK
-setep
 中使用 
parseDefaulting


1
投票

您可以使用年月

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

输出

2019-01

0
投票

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


0
投票

YearWeek
班级

要处理一年中的几周,请使用类来表示该概念。

您忽略了定义“周”。也许您指的是标准 ISO 8601 周。第 1 周包含日历年的第一个星期四,基于周的年份恰好由 52 或 53 个完整的 7 天周组成,因此,第一周/最后几周可能与上一个/连续的日历年有几天不同。

您会在

ThreeTen-Extra
库中找到一个代表标准 ISO 8601 周的类,YearWeek,这是一个免费提供的开源项目。

通过

DateTimeFormatter
DateTimeFormatterBuilder
对象中定义格式模式以匹配您的输入。

final DateTimeFormatter PARSER = 
    new DateTimeFormatterBuilder ( )
        .parseCaseInsensitive ( )
        .appendValue ( IsoFields.WEEK_BASED_YEAR , 4 , 10 , SignStyle.EXCEEDS_PAD )
        .appendValue ( IsoFields.WEEK_OF_WEEK_BASED_YEAR , 2 )
        .toFormatter ( );

解析您的输入,生成一个

YearWeek
对象。

String input = "201901";
YearWeek yearWeek = YearWeek.parse ( input , PARSER );

通过生成标准 ISO 8601 格式的文本进行验证。

String input = "201901";
YearWeek yearWeek = YearWeek.parse ( input , PARSER );

2019-W01

确定该周第一天的日期,即星期一。

YearWeek
对象可以返回该日期的
LocalDate
对象。通过
DayOfWeek
枚举指定您想要一周中的哪一天。

LocalDate localDate = yearWeek.atDay ( DayOfWeek.MONDAY );

2018-12-31

© www.soinside.com 2019 - 2024. All rights reserved.