【问题标题】:Calendar behavior is not compatible with LocalDate?日历行为与 LocalDate 不兼容?
【发布时间】:2020-05-15 07:12:03
【问题描述】:

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

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,则返回上个月的最后一天),但过于冗长。

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

【问题讨论】:

  • 当您使用withDayOfMonth 时,您的意思是“25/12/2020 并用我给您的值替换_/12/2020”。没有 12 月的零日或任何月份。如果您有一些零索引的值,则向其添加 1。
  • 基本上,Calendar API 显然很奇怪——文档解释得很详细,但 java.time API 更明智......你在使用时逃脱了这么奇怪的事情Calendar 不起作用。如果您希望“0”表示“月初的前一天”,我建议您为此编写代码 - 检查 payDay 是否为 0,如果是,请使用 withDayOfMonth(1).plusDays(-1)
  • @JonSkeet LocalDate 有一个 minusDays 方法。您决定添加否定词有什么原因吗?
  • @Michael:不特别。 minusDays(1) 也适合我。 (这可能只是野田时间的偏见,我们只有PlusDays。)
  • @DavidConrad:我们可能会同意不同意。它需要如此多的文档这一事实表明它太复杂了,IMO。 (延迟评估之类的东西也无济于事。)我不会说 C API 是一个很好的例子——我完全同意 Date 设计师可能受到它的影响,我只是希望他们没有没有。

标签: java java-time localdate java-calendar


【解决方案1】:

下面是我的做法:

    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);

我刚才运行sn-p的时候,输出的是你刚才提到的日期:

2019-12-31

如果我们希望它更短,我们可以使用 payDate.withDayOfMonth(1).minusDays(1).plusDays(payDay) 或 Andreas 回答中的技巧,我们不需要 if 语句。不过,我不会。 (1) 更难阅读。 (2) 上面的sn-p中没有对payDay的免费验证。

Calendar 的令人困惑的行为来自没有对set() 的参数进行范围检查。因此,该月的第 0 天是该月第 1 天的前一天。第 -1 天将是前一天,依此类推。它在文档中的这个 sn-p 中(或者至少应该是):

当日历处于宽松模式时,它接受的范围更广 日历字段值比它产生的。当日历重新计算时 由get()返回的日历字段值,所有日历 字段被标准化。例如,宽大的GregorianCalendarMONTH == JANUARY, DAY_OF_MONTH == 32 解释为 2 月 1 日。

您可以通过setLenient 方法的文档中的这个 sn-p 阅读它:

默认是宽松的。

链接

【讨论】:

    【解决方案2】:

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


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

    宽大处理

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

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

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

    所以,如果你有一个特定的日期,例如问题中提到的2020-01-29 日期,并且您想将日期值“设置”为 70 或 0,并具​​有与 Calendar 相同的 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()) 将为您提供所需的结果。

    【讨论】:

    • 谢谢你救了我的脖子。 :)
    【解决方案3】:

    您不能只调用一种方法来获得相同的结果。如果您确定将 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);
       }
    

    【讨论】:

    • 我可能会以不同的方式对 0 进行特殊处理 - 而不是将“设置的日期”设置为 1 然后取前一天(请参阅我对问题的评论)。我认为这比减去一个月并计算出一个月的长度更容易阅读和推理。 (特别是,它消除了例如 3 月 30 日减去一个月意味着什么的问题。)
    • @JonSkeet 这很公平。我已将该建议添加为另一种选择。它的好处是不修改payDay,这可能是一个很好的调用。
    猜你喜欢
    • 2023-04-05
    • 2023-04-02
    • 1970-01-01
    • 1970-01-01
    • 2010-10-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-04
    相关资源
    最近更新 更多