java.time中Calendar.roll的等价物是什么?

java.time中Calendar.roll的等价物是什么?,java,java.util.calendar,java-time,Java,Java.util.calendar,Java Time,我正在研究旧的CalendarAPI,看看它有多糟糕,我发现Calendar有一个方法。与add方法不同,roll不会更改较大日历字段的值 例如,日历实例c表示日期2019-08-31。调用c.roll(Calendar.MONTH,13)会在MONTH字段中添加13,但不会更改年份,因此结果为2019-09-30。请注意,月份的日期会发生变化,因为它是一个较小的字段 我试图在现代java.timeAPI中找到这样一种方法。我认为这样的方法必须在LocalDate或LocalDateTime中

我正在研究旧的
Calendar
API,看看它有多糟糕,我发现
Calendar
有一个方法。与
add
方法不同,
roll
不会更改较大日历字段的值

例如,日历实例
c
表示日期2019-08-31。调用
c.roll(Calendar.MONTH,13)
会在MONTH字段中添加13,但不会更改年份,因此结果为2019-09-30。请注意,月份的日期会发生变化,因为它是一个较小的字段

我试图在现代
java.time
API中找到这样一种方法。我认为这样的方法必须在
LocalDate
LocalDateTime
中,但我没有找到类似的方法

因此,我尝试编写自己的
roll
方法:

public static LocalDateTime roll(LocalDateTime ldt, TemporalField unit, long amount) {
    LocalDateTime newLdt = ldt.plus(amount, unit.getBaseUnit());
    return ldt.with(unit, newLdt.get(unit));
}
但是,这只适用于某些情况,而不适用于其他情况。例如,它不适用于中描述的情况:

考虑一个最初设置为1999年6月6日星期日的GregorianCalendar。 呼叫名单(Calendar.WEEK\u OF\u MONTH,-1)将日历设置为星期二 1999年6月1日,而调用add(Calendar.WEEK\u OF_MONTH,-1)设置 一九九九年五月三十日(星期日)。这是因为滚动规则 另一个约束条件是:当 一个月的第二周开始滚动。加上add规则1,结果 日期必须介于6月1日星期二和6月5日星期六之间。根据 添加规则2,即每周的第天,在更改 月的周设置为星期二,这是与星期日最接近的值 (其中星期日是一周的第一天)

我的代码:

System.out.println(roll(
        LocalDate.of(1999, 6, 6).atStartOfDay(),
        ChronoField.ALIGNED_WEEK_OF_MONTH, -1
));
输出1999-07-04T00:00,而使用日历:

Calendar c = new GregorianCalendar(1999, 5, 6);
c.roll(Calendar.WEEK_OF_MONTH, -1);
System.out.println(c.getTime().toInstant());
输出
1999-05-31T23:00:00Z
,这是我所在时区的
1999-06-01


java.time
API中,什么是
roll
的等价物?如果没有,我如何编写一个方法来模拟它呢?

首先,我记不起是否见过
Calendar.roll的任何有用的应用程序。第二,我认为在一些特殊情况下,功能没有得到很好的指定。角落里的案子会很有趣。如果没有
roll
方法,每月滚动13个月并不困难。类似的观察结果可能是java.time不提供此功能的原因

相反,我认为我们将不得不求助于更多的手动滚动方式。第一个例子是:

    LocalDate date = LocalDate.of(2019, Month.JULY, 22);
    int newMonthValue = 1 + (date.getMonthValue() - 1 + 13) % 12;
    date = date.with(ChronoField.MONTH_OF_YEAR, newMonthValue);
    System.out.println(date);
输出:

2019-08-22

我使用的事实是,在ISO年表中,一年中总有12个月。由于
%
总是给出一个基于0的结果,因此在进行模运算之前,我从基于1的月份值中减去1,然后再将其加回去,我假设为正滚动。如果滚动的月数可能为负数,则会变得稍微复杂一些(留给读者)

对于其他字段,我认为类似的方法适用于大多数情况:在给定较大字段的情况下,找到字段的最小和最大可能值,并进行一些模运算

在某些情况下,这可能成为一个挑战。例如,当夏季时间(DST)结束时,时钟从凌晨3点倒转到凌晨2点,因此一天有25个小时长,您将如何从早上6点倒转37个小时?我相信这是可以做到的。而且我也确信这个功能不是内置的

以滚动月周为例,旧API和现代API之间的另一个区别开始发挥作用:a
GregoriaCalendar
不仅定义日历日和时间,还定义了一个由一周的第一天和第一周的最少天数组成的周方案。在java.time中,周方案由
WeekFields
对象定义。因此,虽然滚动月份的周在
gregoriacalendar
中可能是明确的,但不知道周方案,它与
LocalDate
LocalDateTime
无关。可以尝试假设ISO周(从周一开始,第一周是至少有4天的新月份的开始),但它可能并不总是用户想要的

月的周和年的周是特殊的,因为周跨越月和年的界限。以下是我实施每月滚动周的尝试:

private static LocalDate rollWeekOfMonth(LocalDate date, int amount, WeekFields wf) {
    LocalDate firstOfMonth = date.withDayOfMonth(1);
    int firstWeekOfMonth = firstOfMonth.get(wf.weekOfMonth());
    LocalDate lastOfMonth = date.with(TemporalAdjusters.lastDayOfMonth());
    int lastWeekOfMonth = lastOfMonth.get(wf.weekOfMonth());
    int weekCount = lastWeekOfMonth - firstWeekOfMonth + 1;
    int newWeekOfMonth = firstWeekOfMonth
            + (date.get(wf.weekOfMonth()) - firstWeekOfMonth
                            + amount % weekCount + weekCount)
                    % weekCount;
    LocalDate result = date.with(wf.weekOfMonth(), newWeekOfMonth);
    if (result.isBefore(firstOfMonth)) {
        result = firstOfMonth;
    } else if (result.isAfter(lastOfMonth)) {
        result = lastOfMonth;
    }
    return result;
}
试一试:

    System.out.println(rollWeekOfMonth(LocalDate.of(1999, Month.JUNE, 6), -1, WeekFields.SUNDAY_START));
    System.out.println(rollWeekOfMonth(LocalDate.of(1999, Month.JUNE, 6), -1, WeekFields.ISO));
输出:

说明:您引用的文档假定星期日是一周的第一天(它以“星期日是一周的第一天”结尾;它可能是在美国编写的),因此在6月6日星期日之前还有一周。而滚动-1周应在本周之前滚动。我的第一行代码就是这样做的

在ISO周计划中,6月6日星期日属于从5月31日星期一到6月6日星期日的一周,因此6月没有本周之前的一周。因此,我的第二行代码滚动到6月的最后一周,6月28日到7月4日。由于我们不能在6月外活动,所以选择了6月30日

我没有测试它的行为是否与
gregorianalendar
相同。为了进行比较,
gregoriacalendar.roll
实现使用52行代码来处理
周/月
案例,而我的20行代码则是如此。要么我没有考虑到什么,要么java.time再次显示了它的优越性

相反,我对现实世界的建议是:明确您的需求,并直接在java.time上实现它们,而忽略旧API的行为。作为一项学术练习,你的问题既有趣又有趣。

TL;博士 没有等价物

考虑一下您是否真的需要
java.util.Calendar
roll
行为:

Calendar c = new GregorianCalendar(1999, 5, 6);
c.roll(Calendar.WEEK_OF_MONTH, -1);
System.out.println(c.getTime().toInstant());
/**
*在给定时间上加或减(上/下)单个时间单位
*字段,而不更改较大的字段。例如,要滚动当前
*更新一天,你就可以实现它