日历行为与 LocalDate 不兼容?

Calendar behavior is not compatible with LocalDate?

嗨,伙计们,我在尝试将行为从日历迁移到本地日期时遇到了麻烦。

payDate.set(Calendar.DAY_OF_MONTH,payDay)

假设 payDate 的当前日期是 2020-01-29

出于业务原因 payDay 可以具有 0 的值,因此,当使用前面的场景执行前面的代码行时,结果是 payDate 更新日期到 2019-12-31, 也就是说日期回到上个月的最后一天。

我不确定,这是技术原因,如果有人能向我解释一下,我将非常感激,我尝试查看 java 文档,但没有帮助。

所以我需要使用 LocalDate java 库复制该行为。在我看来,我的观点是; Calendar 中的 set 方法与 LocalDate 中 DAY_OF_MONTH 的值类似:

payDate.withDayOfMonth(payDay)

但是当出现以下情况并且 payDay 等于 0 时,我得到一个错误:

java.time.DateTimeException: Invalid value for DayOfMonth (valid values 1 - 28/31): 0

我也有一些想法,关于如何在规则生效时在 localDate 中获得相同的日历结果(如果 payDay 为 0,return 到上个月的最后一天),但是太冗长。

如果您知道 LocalDate 上的类似行为,请帮助我。谢谢。

您将无法仅调用一种方法来获得相同的结果。如果您 确定 将 DAY_OF_MONTH 设置为 0 应该会导致它回滚一个月(这是我 运行 过去的业务类型分析师或产品负责人进行健全性检查)然后你将不得不做这样的事情:

    int payDay = 0;
    LocalDate payDate = LocalDate.of(2020, Month.JANUARY, 29);

    if(payDay == 0) {
        payDate = payDate.minusMonths(1);
        payDay = payDate.lengthOfMonth();
    }

    payDate = payDate.withDayOfMonth(payDay);

另一种方法:

   int payDay = 0;
   LocalDate payDate = LocalDate.of(2020, Month.JANUARY, 29);
   if(payDay == 0) {
      payDate = payDate.withDayOfMonth(1).minusDays(1);
   } else {
      payDate = payDate.withDayOfMonth(payDay);
   }

TL;DR: 使用 payDate = payDate.plusDays(payDay - payDate.getDayOfMonth());


您描述的 Calendar 的行为记录在 javadoc 中:

Leniency

Calendar has two modes for interpreting the calendar fields, lenient and non-lenient. When a Calendar is in lenient mode, it accepts a wider range of calendar field values than it produces. When a Calendar recomputes calendar field values for return by get(), all of the calendar fields are normalized. For example, a lenient GregorianCalendar interprets MONTH == JANUARY, DAY_OF_MONTH == 32 as February 1.

When a Calendar is in non-lenient mode, it throws an exception if there is any inconsistency in its calendar fields. For example, a GregorianCalendar always produces DAY_OF_MONTH values between 1 and the length of the month. A non-lenient GregorianCalendar throws an exception upon calculating its time or calendar field values if any out-of-range field value has been set.

为显示此效果,请尝试将 Calendar 的日期设置为 2020 年 1 月 70 日:

Calendar cal = Calendar.getInstance();
cal.clear();
cal.set(2020, Calendar.JANUARY, 70);
System.out.println(new SimpleDateFormat("yyyy-MM-dd").format(cal.getTime()));

输出

2020-03-10

如果你这样做,你会得到相同的结果:

cal.set(2020, Calendar.JANUARY, 1);
cal.add(Calendar.DAY_OF_MONTH, 69);

LocalDate 始终是 non-lenient,因此您不能将日期值设置为超出范围的值。但是,通过将操作更改为“添加”而不是“设置”,您可以获得与 Calendar 相同的结果。

所以,如果你有一个特定的日期,例如问题中提到的 2020-01-29 日期,并且您想将日期值“设置”为 70 或 0,具有与 [=18] 相同的 lenient 溢出逻辑=] 有,这样做:

LocalDate date = LocalDate.parse("2020-01-29");
date = date.plusDays(70 - date.getDayOfMonth());
System.out.println(date);
LocalDate date = LocalDate.parse("2020-01-29");
date = date.plusDays(0 - date.getDayOfMonth());
System.out.println(date);

输出

2020-03-10
2019-12-31

如您所见,date.plusDays(dayToSet - date.getDayOfMonth()) 会给您想要的结果。

以下是我的处理方式:

    LocalDate payDate = LocalDate.now(); // or whatever
    int payDay = 0;
    if (payDay == 0) {
        // simulate `GregorianCalendar` behaviour: day 0 is the day before day 1
        payDate = payDate.withDayOfMonth(1).minusDays(1);
    } else {
        payDate = payDate.withDayOfMonth(payDay);
    }
    System.out.println(payDate);

当我运行刚才的片段时,输出的是你已经提到的日期:

2019-12-31

如果我们想要它更短,我们可以使用 payDate.withDayOfMonth(1).minusDays(1).plusDays(payDay) 或 Andreas 回答中的技巧,我们不需要 if 语句。不过我不会。 (1) 阅读困难。 (2) 它没有给出上面代码段中免费提供的 payDay 的验证。

Calendar 的混乱行为来自于 not 运行ge 检查 set() 的参数。因此,该月的第 0 天是该月第 1 天的前一天。第 -1 天将是前一天,依此类推。它在文档的这个片段中(或者至少应该是):

When a Calendar is in lenient mode, it accepts a wider range of calendar field values than it produces. When a Calendar recomputes calendar field values for return by get(), all of the calendar fields are normalized. For example, a lenient GregorianCalendar interprets MONTH == JANUARY, DAY_OF_MONTH == 32 as February 1.

您可以使用 setLenient 方法文档中的此片段阅读它:

The default is lenient.

链接