简体   繁体   English

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

[英]What is the equivalent of Calendar.roll in java.time?

I was studying the old Calendar API to see how bad it was, and I found out that Calendar has a roll method.我正在研究旧的Calendar API 来看看它有多糟糕,我发现Calendar有一个roll方法。 Unlike the add method, roll does not change the values of bigger calendar fields.add方法不同, roll不会更改较大日历字段的值。

For example, the calendar instance c represents the date 2019-08-31.例如,日历实例c表示日期 2019-08-31。 Calling c.roll(Calendar.MONTH, 13) adds 13 to the month field, but does not change the year, so the result is 2019-09-30.调用c.roll(Calendar.MONTH, 13)将月份字段加 13,但不改变年份,所以结果是 2019-09-30。 Note that the day of month changes, because it is a smaller field.请注意,月份中的日期会发生变化,因为它是一个较小的字段。

Related 有关的

I tried to find such a method in the modern java.time API.我试图在现代java.time API 中找到这样的方法。 I thought such a method has to be in LocalDate or LocalDateTime , but I found nothing of the sort.我认为这样的方法必须在LocalDateLocalDateTime ,但我什么也没找到。

So I tried to write my own roll method:所以我尝试编写自己的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));
}

However, this only works for some cases, but not others.但是,这只适用于某些情况,而不适用于其他情况。 For example, it does not work for the case described in the documentation here :例如,它不适用于此处文档中描述的情况:

Consider a GregorianCalendar originally set to Sunday June 6, 1999. Calling roll(Calendar.WEEK_OF_MONTH, -1) sets the calendar to Tuesday June 1, 1999, whereas calling add(Calendar.WEEK_OF_MONTH, -1) sets the calendar to Sunday May 30, 1999. This is because the roll rule imposes an additional constraint: The MONTH must not change when the WEEK_OF_MONTH is rolled.考虑最初设置为 1999 年 6 月 6 日星期日的 GregorianCalendar。调用 roll(Calendar.WEEK_OF_MONTH, -1) 将日历设置为 1999 年 6 月 1 日星期二,而调用 add(Calendar.WEEK_OF_MONTH, -1) 将日历设置为 5 月 30 日星期日, 1999。这是因为滚动规则施加了一个额外的约束:当 WEEK_OF_MONTH 滚动时,MONTH 不得更改。 Taken together with add rule 1, the resultant date must be between Tuesday June 1 and Saturday June 5. According to add rule 2, the DAY_OF_WEEK, an invariant when changing the WEEK_OF_MONTH, is set to Tuesday, the closest possible value to Sunday (where Sunday is the first day of the week).与添加规则 1 一起考虑,结果日期必须在 6 月 1 日星期二和 6 月 5 日星期六之间。根据添加规则 2,更改 WEEK_OF_MONTH 时的不变式 DAY_OF_WEEK 设置为星期二,这是与星期日最接近的可能值(其中星期日是一周的第一天)。

My code:我的代码:

System.out.println(roll(
        LocalDate.of(1999, 6, 6).atStartOfDay(),
        ChronoField.ALIGNED_WEEK_OF_MONTH, -1
));

outputs 1999-07-04T00:00 , whereas using Calendar :输出1999-07-04T00:00 ,而使用Calendar

Calendar c = new GregorianCalendar(1999, 5, 6);
c.roll(Calendar.WEEK_OF_MONTH, -1);
System.out.println(c.getTime().toInstant());

outputs 1999-05-31T23:00:00Z , which is 1999-06-01 in my timezone.输出1999-05-31T23:00:00Z ,即我的时区中的1999-06-01

What is an equivalent of roll in the java.time API? java.time API 中的roll等价物是什么? If there isn't one, how can I write a method to mimic it?如果没有,我该如何编写一种方法来模仿它?

First, I cannot remember having seen any useful application of Calendar.roll .首先,我不记得见过任何有用的Calendar.roll应用程序。 Second, I don't think that the functionality is very well specified in corner cases.其次,我认为在极端情况下没有很好地指定功能。 And the corner cases would be the interesting ones.角落案例将是有趣的案例。 Rolling month by 13 months would not be hard without the roll method.如果没有roll方法,将月份滚动 13 个月并不难。 It may be that similar observations are the reasons why this functionality is not offered by java.time.类似的观察结果可能是 java.time 不提供此功能的原因。

Instead I believe that we would have to resort to more manual ways of rolling.相反,我认为我们将不得不求助于更多的手动滚动方式。 For your first example:对于您的第一个示例:

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

Output:输出:

2019-08-22 2019-08-22

I am using the fact that in the ISO chronology there are always 12 months in the year.我使用的事实是,在 ISO 年表中,一年中总是有 12 个月。 Since % always gives a 0-based result, I subtract 1 from the 1-based month value before the modulo operation and add it back in afterwards And I am assuming a positive roll.由于%总是给出基于 0 的结果,因此我在模运算之前从基于 1 的月份值中减去 1,然后将其添加回来并且我假设一个正滚动。 If the number of months to roll may be negative, it gets slightly more complicated (left to the reader).如果滚动的月数可能是负数,它会稍微复杂一些(留给读者)。

For other fields I think that a similar approach will work for most cases: Find the smallest and the largest possible value of the field given the larger fields and do some modulo operation.对于其他字段,我认为类似的方法适用于大多数情况:在给定较大字段的情况下找到该字段的最小和最大可能值,并进行一些模运算。

It may become a challenge in some cases.在某些情况下,这可能会成为挑战。 For example, when summer time (DST) ends and the clock is turned backward from 3 to 2 AM, so the day is 25 hours long, how would you roll 37 hours from 6 AM?例如,当夏令时 (DST) 结束并且时钟从凌晨 3 点倒退到凌晨 2 点时,一天有 25 小时,您如何从早上 6 点开始滚动 37 小时? I'm sure it can be done.我确信它可以做到。 And I am also sure that the functionality is not built in.而且我也确信该功能不是内置的。

For your example with rolling the week of month, another difference between the old and the modern API comes into play: a GregorianCalendar not only defines a calendar day and time, it also defines a week scheme consisting of a first day of the week and a minimum number of days in the first week.对于滚动月份中的一周的示例,旧 API 和现代 API 之间的另一个区别开始发挥作用: GregorianCalendar不仅定义了日历日期和时间,还定义了由一周的第一天和第一周的最少天数。 In java.time the week scheme is defined by a WeekFields object instead.在 java.time 中,周方案由WeekFields对象定义。 So while rolling the week of month may be unambiguous in GregorianCalendar , without knowing the week scheme it isn't with LocalDate or LocalDateTime .因此,虽然在GregorianCalendar滚动月份中的一周可能是明确的,但在不知道周计划的情况下,它与LocalDateLocalDateTime无关。 An attempt may be to assume ISO weeks (start on Monday, and the first week is the on that has at least 4 days of the new month in it), but it may not always be what a user had intended.可以尝试假设 ISO 周(从星期一开始,第一周是新月份至少有 4 天的日期),但它可能并不总是用户想要的。

Week of month and week of year are special since weeks cross month and year boundaries.一个月中的一周和一年中的一周是特殊的,因为周跨越了月和年的界限。 Here's my attempt to implement a roll of week of month:这是我尝试实现每月一周的滚动:

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

Try it out:试试看:

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

Output:输出:

 1999-06-01 1999-06-30

Explanation: The documentation you quote assumes that Sunday is the first day of the week (it ends “where Sunday is the first day of the week”; it was probably written in the USA) so there is a week before Sunday June 6. And rolling by -1 week should roll into this week before.解释:您引用的文档假设星期日是一周的第一天(它以“星期日是一周的第一天”结束;它可能是在美国写的)所以在 6 月 6 日星期日之前还有一周。并且滚动 -1 周应该会滚动到本周之前。 My first line of code does that.我的第一行代码就是这样做的。

In the ISO week scheme, Sunday June 6 belong to the week from Monday May 31 through Sunday June 6, so in June there is no week before this week.在 ISO 周方案中,6 月 6 日星期日属于从 5 月 31 日星期一到 6 月 6 日星期日的那一周,因此在 6 月,这周之前没有一周。 Therefore my second line of code rolls into the last week of June, June 28 through July 4. Since we cannot go outside June, June 30 is chosen.因此,我的第二行代码将滚动到 6 月的最后一周,即 6 月 28 日到 7 月 4 日。由于我们不能离开 6 月,因此选择了 6 月 30 日。

I have not tested whether it behaves the same as GregorianCalendar .我还没有测试它的行为是否与GregorianCalendar相同。 For comparison,the GregorianCalendar.roll implementation uses 52 code lines to handle the WEEK_OF_MONTH case, compared to my 20 lines.为了进行比较,与我的 20 行相比, GregorianCalendar.roll实现使用 52 行代码来处理WEEK_OF_MONTH情况。 Either I have left something out of consideration, or java.time once again shows it superiority.要么我忽略了一些东西,要么 java.time 再次显示了它的优越性。

Rather my suggestion for the real world is: make your requirements clear and implement them directly on top of java.time, ignoring how the old API behaved.相反,我对现实世界的建议是:明确您的需求并直接在 java.time 之上实现它们,忽略旧 API 的行为。 As an academic exercise, your question is a fun and interesting one.作为一项学术练习,你的问题很有趣。

TL;DR TL; 博士

There is no equivalent.没有等价物。

Think about whether you really need the behavior of roll of java.util.Calendar :想想你是否真的需要java.util.Calendarroll行为:

    /**
     * Adds or subtracts (up/down) a single unit of time on the given time
     * field without changing larger fields. For example, to roll the current
     * date up by one day, you can achieve it by calling:
     * roll(Calendar.DATE, true).
     * When rolling on the year or Calendar.YEAR field, it will roll the year
     * value in the range between 1 and the value returned by calling
     * getMaximum(Calendar.YEAR).
     * When rolling on the month or Calendar.MONTH field, other fields like
     * date might conflict and, need to be changed. For instance,
     * rolling the month on the date 01/31/96 will result in 02/29/96.
     * When rolling on the hour-in-day or Calendar.HOUR_OF_DAY field, it will
     * roll the hour value in the range between 0 and 23, which is zero-based.
     *
     * @param field the time field.
     * @param up indicates if the value of the specified time field is to be
     * rolled up or rolled down. Use true if rolling up, false otherwise.
     * @see Calendar#add(int,int)
     * @see Calendar#set(int,int)
     */
    public void roll(int field, boolean up);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM