0

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

payDate.set(Calendar.DAY_OF_MONTH,payDay)

让我们假设payDate当前日期为 2020-01-29

由于业务原因payDay可以0取值payDate为过去一个月。

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

所以我需要用 LocalDate java 库复制这种行为。从我的角度来看; 与LocalDate 中的值类似的set方法是:CalendarDAY_OF_MONTH

payDate.withDayOfMonth(payDay)

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

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

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

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

4

3 回答 3

4

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


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

宽大

Calendar有两种解释日历字段的模式,lenientnon-lenient。当 aCalendar处于宽松模式时,它接受的日历字段值范围比它产生的范围更广。当 aCalendar为 return by 重新计算日历字段值时get(),所有日历字段都被规范化。例如,宽大的人将,GregorianCalendar解释为 2 月 1 日。MONTH == JANUARYDAY_OF_MONTH == 32

当 aCalendar处于非宽松模式时,如果其日历字段中存在任何不一致,它将引发异常。例如,aGregorianCalendar总是产生DAY_OF_MONTH介于 1 和月份长度之间的值。如果设置了任何超出范围的字段值,则non-lenientGregorianCalendar在计算其时间或日历字段值时会引发异常。

要显示此效果,请尝试将 a 的日期设置为Calendar2020 年 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,因此您不能将 day-of-month 值设置为超出范围的值。Calendar但是,您可以通过将操作更改为“添加”而不是“设置”来获得与实际操作相同的结果。

因此,如果您有一个特定的日期,例如2020-01-29问题中提到的日期,并且您想将日期值“设置”为 70 或 0,并且具有与之前相同的宽松溢出逻辑Calendar,请执行以下操作:

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())将为您提供所需的结果。

于 2020-01-29T18:39:59.480 回答
2

以下是我将如何去做:

    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来自不检查参数的范围set()。因此,该月的第 0 天是该月第 1 天的前一天。第 -1 天将是前一天,依此类推。它在文档的这个片段中(或者至少应该是):

当日历处于宽松模式时,它接受的日历字段值范围比它产生的范围更广。当 Calendar 为 return by 重新计算日历字段值时get(),所有日历字段都被规范化。例如,宽大的GregorianCalendar 解释MONTH == JANUARY, DAY_OF_MONTH == 32为 2 月 1 日。

您可以使用该setLenient方法文档中的以下代码段阅读它:

默认是宽松的。

链接

于 2020-01-29T18:44:02.677 回答
1

您将无法仅调用一种方法来获得相同的结果。如果您确定将 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);
   }
于 2020-01-29T18:18:32.077 回答