在java.time中,Calendar.roll等效于什么? [英] What is the equivalent of Calendar.roll in java.time?

查看:61
本文介绍了在java.time中,Calendar.roll等效于什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在研究旧的Calendar API,以了解它的严重程度,然后发现Calendar具有

I was studying the old Calendar API to see how bad it was, and I found out that Calendar has a roll method. Unlike the add method, roll does not change the values of bigger calendar fields.

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

For example, the calendar instance c represents the date 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. Note that the day of month changes, because it is a smaller field.

相关

我试图在现代的java.time API中找到这种方法.我以为这样的方法必须在LocalDateLocalDateTime中,但是我什么都没找到.

I tried to find such a method in the modern java.time API. I thought such a method has to be in LocalDate or LocalDateTime, but I found nothing of the sort.

所以我尝试编写自己的roll方法:

So I tried to write my own roll method:

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:

请考虑最初定于1999年6月6日(星期日)的GregorianCalendar. 呼叫记录(Calendar.WEEK_OF_MONTH,-1)将日历设置为星期二 1999年6月1日,而调用add(Calendar.WEEK_OF_MONTH,-1)设置了 日历到1999年5月30日星期日.这是因为滚动规则强加了 另一个限制:当 WEEK_OF_MONTH已滚动.与添加规则1一起使用时,结果 日期必须在6月1日星期二至6月5日星期六之间. 添加规则2,即DAY_OF_WEEK,这是更改规则时的不变式 WEEK_OF_MONTH,设置为星期二,最接近周日的可能值 (其中星期日是一周的第一天).

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

我的代码:

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

输出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());

输出1999-05-31T23:00:00Z,在我的时区中是1999-06-01.

outputs 1999-05-31T23:00:00Z, which is 1999-06-01 in my timezone.

java.time API中的roll等效项是什么?如果没有,我该如何编写一种模仿它的方法?

What is an equivalent of roll in the java.time API? If there isn't one, how can I write a method to mimic it?

推荐答案

首先,我不记得看到过Calendar.roll的任何有用应用程序.其次,我认为在极端情况下不能很好地指定功能.而最极端的情况将是有趣的情况.没有roll方法,很难将月份滚动13个月.可能类似的观察结果是java.time不提供此功能的原因.

First, I cannot remember having seen any useful application of 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 rollmethod. It may be that similar observations are the reasons why this functionality is not offered by 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);

输出:

2019-08-22

2019-08-22

我正在使用这样一个事实:在ISO年表中,一年中总是有12个月.因为%总是给出从0开始的结果,所以我在模运算之前从基于1的月份值中减去1,然后再加回去.如果投放的月份数可能为负,则情况会稍微复杂一些(留给读者看).

I am using the fact that in the ISO chronology there are always 12 months in the year. 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. 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.

在某些情况下可能会成为挑战.例如,当夏令时(DST)结束并且时钟从3 AM倒退到2 AM,所以一天是25小时长,您将如何从6 AM开始滚动37小时?我确定可以做到.而且我也确信该功能不是内置的.

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? I’m sure it can be done. And I am also sure that the functionality is not built in.

在滚动一个星期的示例中,旧API和现代API之间的另一个区别开始发挥作用:GregorianCalendar不仅定义了日历的日期和时间,还定义了由第一天组成的星期方案一周的天数,以及第一周的最少天数.在java.time中,周方案由WeekFields对象定义.因此,在GregorianCalendar中滚动月份中的星期可能是明确的,而无需知道周方案,而LocalDateLocalDateTime则不是.可以尝试假定ISO周(从星期一开始,并且第一周是新月中至少有4天的月份),但这可能并不总是用户想要的.

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. In java.time the week scheme is defined by a WeekFields object instead. So while rolling the week of month may be unambiguous in GregorianCalendar, without knowing the week scheme it isn’t with LocalDate or LocalDateTime. 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.

每月的某周和每年的某周是特殊的,因为周跨越了月份和年份的界限.这是我尝试每月执行一周的时间:

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

尝试一下:

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

输出:

1999-06-01
1999-06-30

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

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. My first line of code does that.

在ISO周计划中,6月6日(星期日)属于5月31日(星期一)至6月6日(星期日)之间的一周,因此6月份之前没有一周.因此,我的第二行代码进入6月28日的6月最后一个星期到7月4日.因为我们不能选择6月30日之外的时间.

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. 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.

我尚未测试它的行为是否与GregorianCalendar相同.为了进行比较,与我的20行相比,GregorianCalendar.roll实现使用52条代码行来处理WEEK_OF_MONTH情况.我要么没有考虑,要么java.time再次显示了它的优越性.

I have not tested whether it behaves the same as GregorianCalendar. For comparison,the GregorianCalendar.roll implementation uses 52 code lines to handle the WEEK_OF_MONTH case, compared to my 20 lines. Either I have left something out of consideration, or java.time once again shows it superiority.

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

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. As an academic exercise, your question is a fun and interesting one.

这篇关于在java.time中,Calendar.roll等效于什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆