【问题标题】:How to convert two digit year to full year using Java 8 time API如何使用 Java 8 时间 API 将两位数年份转换为全年
【发布时间】:2018-11-16 02:39:50
【问题描述】:

我希望从我的项目中删除 Joda-Time 库。

我正在尝试将两位数的年份转换为全年。 Joda-Time 的以下代码可以实现该目的。下面是joda-time的如下代码

DateTimeFormatter TWO_YEAR_FORMATTER = DateTimeFormat.forPattern("yy");
int year = LocalDate.parse("99"", TWO_YEAR_FORMATTER).getYear();
System.out.println(year);

输出:1999

这是我期望的输出,这在我的情况下是有意义的。但是,当我使用 java.time API 尝试相同的过程时,它会产生 DatetimeParseException。下面是java.time API的如下代码:

DateTimeFormatter TWO_YEAR_FORMATTER = DateTimeFormatter.ofPattern("yy");
int year = LocalDate.parse("99", TWO_YEAR_FORMATTER).getYear();
System.out.println(year);

堆栈跟踪:

Exception in thread "main" java.time.format.DateTimeParseException: Text '99' could not be parsed: Unable to obtain LocalDate from TemporalAccessor: {Year=2099},ISO of type java.time.format.Parsed
    at java.base/java.time.format.DateTimeFormatter.createError(DateTimeFormatter.java:2017)
    at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1952)
    at java.base/java.time.LocalDate.parse(LocalDate.java:428)
    at scratch.main(scratch.java:10)
Caused by: java.time.DateTimeException: Unable to obtain LocalDate from TemporalAccessor: {Year=2099},ISO of type java.time.format.Parsed
    at java.base/java.time.LocalDate.from(LocalDate.java:396)
    at java.base/java.time.format.Parsed.query(Parsed.java:235)
    at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1948)
    ... 2 more

我没有看到堆栈跟踪的原因。如果有人能帮助我理解以下场景并解释如何使用 Java 8 时间 API 将两位数年份转换为全年,那就太好了。

【问题讨论】:

标签: java jodatime java-time 2-digit-year


【解决方案1】:

问题是您不能单独将年份解析为 LocalDate。 LocalDate 需要更多信息。

您可以使用格式化程序的parse 方法,它会给您一个TemporalAccessor,然后从中获取年份字段:

int year = TWO_YEAR_FORMATTER.parse("99").get(ChronoField.YEAR);
System.out.println(year);

解决两者之间的差异:这是两个不同的 API。是的,它们非常相似,java.time 包是由 JodaTime 的设计决定提供的,但它从未打算成为它的直接替代品。

如果您想更改基准年,请参阅 this answer(默认情况下,“99”将解析为 2099 而不是 1999)。

【讨论】:

  • 感谢您的回答。您的程序输出为 2099。但我希望 1999 转换为 joda 时间,这确实有意义。你知道如何修改它来解析 2000 年之前的年份吗?
【解决方案2】:

您无法创建LocalDate,因为您提供的String 未提供填写所有必填字段所需的信息。

你可以创建一个Year

Year parsed = Year.parse("99", TWO_YEAR_FORMATTER);
int year = parsed.getValue();

【讨论】:

    【解决方案3】:

    为什么它不起作用

    java.time 不像 Joda-Time 那样提供月份和日期的默认值。该消息说它无法仅从一年中获取LocalDate,或者相反,它缺少月份和日期(不过,您可以提供自己的默认值,如Mikhail Kholodkov’s answer 所示)。一般来说,这种行为是为了帮助我们:它提醒我们提供所有需要的值,并从代码中清楚地说明是否使用了任何默认值,以及使用哪个。

    该怎么做

    只需使用java.timeYear 类。先声明

    public static final DateTimeFormatter TWO_YEAR_FORMATTER = new DateTimeFormatterBuilder()
            .appendValueReduced(ChronoField.YEAR, 2, 2, 1950)
            .toFormatter();
    

    然后使用

        int year = Year.parse("99", TWO_YEAR_FORMATTER).getValue();
        System.out.println(year);
    

    输出

    1999

    在我输入 1950 的位置插入所需的基值,以指定从该年起 99 年(含)内的一年。如果您希望过去的年份包括今年,您可以使用:

    private static final Year base = Year.now(ZoneId.of("Asia/Kolkata")).minusYears(99);
    public static final DateTimeFormatter TWO_YEAR_FORMATTER = new DateTimeFormatterBuilder()
            .appendValueReduced(ChronoField.YEAR, 2, 2, base.getValue())
            .toFormatter();
    

    顺便说一句,不要相信我的话,但我认为 Joda-Time 使用当年减去 30 年作为基准或支点。如果是这样,在最后一个 sn-p 中使用 30 而不是 99 将是最兼容的(所以也会给你预期的 1999)。

    【讨论】:

      【解决方案4】:

      您需要为DAY_OF_MONTHMONTH_OF_YEAR 提供默认值

      DateTimeFormatter TWO_YEAR_FORMATTER = new DateTimeFormatterBuilder()
                      .appendPattern("yy")
                      .parseDefaulting(ChronoField.MONTH_OF_YEAR, 1)
                      .parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
                      .toFormatter();
      

      另外,

      Java-8 默认使用 2000-2099 范围,不像 SimpleDateFormat 相对于今天的 -80 年到 +20 年的范围。

      Full answer

      由于您只解析年份,为了将 '99' 设为 '1999',您的代码应如下所示:

      DateTimeFormatter TWO_YEAR_FORMATTER = new DateTimeFormatterBuilder()
                      .appendPattern("")
                      .parseDefaulting(ChronoField.MONTH_OF_YEAR, 1)
                      .parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
                      .appendValueReduced(ChronoField.YEAR_OF_ERA, 2, 2, LocalDate.now().minusYears(80))
                      .toFormatter();
      

      【讨论】:

      • 使用构建器的一个优点是您可以使用例如.appendValueReduced(ChronoField.YEAR, 2, 2, 1950) 而不是appendPattern 来指定要解释的年份,在这种情况下为1950-2049(包括)。
      【解决方案5】:

      您可以使用formatter.parse(input, Year::from) 直接解析年份:

      import java.time.*;
      import java.time.format.*;
      class Main {
        public static void main(String[] args) {
          DateTimeFormatter TWO_YEAR_FORMATTER = DateTimeFormatter.ofPattern("yy");
          Year year = TWO_YEAR_FORMATTER.parse("99", Year::from);
          System.out.println(year);
        }
      }
      

      请注意,这将输出2099。要输出 1999,您应该使用特定的格式化程序,例如 the one suggested by Ole V.V. in their answer

      【讨论】:

      • 您可能已经阅读了标题并没有注意 1999 的预期结果(不是 2099,因为您的代码会产生)?
      • @OleV.V.你说对了一半。我读了标题,但我回答的目的不是“显示正确的日期”,而是指出解析器可以使用非常易读的形式formatter.parse(input, Year::from) 返回Year。我希望我的编辑能让我的意图更加清晰。
      • 谢谢。我觉得清楚多了。也感谢您提供的友好链接。
      猜你喜欢
      • 2011-01-02
      • 2010-12-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-24
      • 2022-08-09
      • 1970-01-01
      • 2021-09-26
      相关资源
      最近更新 更多