需要查询日历实例才能使日历设置生效

Need to query Calendar instance for Calendar setting to work

我有一个从 12 月的第一天开始的日历实例。我正在尝试将该日历实例设置为一年中的最后一个星期二。在设置一年的最后一天和星期几之间的某处,Calendar 实例恢复到原始时间:

Calendar cal = ....;
Log.d(TAG, String.format("Starting here %d-%d-%d", cal.get(Calendar.YEAR), cal.get(Calendar.MONTH)+1, cal.get(Calendar.DAY_OF_MONTH)));

cal.set(Calendar.DAY_OF_YEAR, cal.getActualMaximum(Calendar.DAY_OF_YEAR));
cal.set(Calendar.DAY_OF_WEEK, Calendar.TUESDAY);

Log.d(TAG, String.format("Ending here %d-%d-%d", cal.get(Calendar.YEAR), cal.get(Calendar.MONTH)+1, cal.get(Calendar.DAY_OF_MONTH)));
/* Other stuff */

给我 2017 年和 2018 年的输出:

2017:

Starting here 2017-12-1 
Ending here 2017-11-28 

2018:

Starting here 2018-12-1 
Ending here 2018-11-27 

一个不令人满意的修复

但是,如果我从 Calendar 实例中检索数据,它工作正常:

Calendar cal = ....;
Log.d(TAG, String.format("Starting here %d-%d-%d", cal.get(Calendar.YEAR), cal.get(Calendar.MONTH)+1, cal.get(Calendar.DAY_OF_MONTH)));

cal.set(Calendar.DAY_OF_YEAR, cal.getActualMaximum(Calendar.DAY_OF_YEAR));

// Ask the Calendar instance for information
long dummyValue = cal.getTimeInMillis();

cal.set(Calendar.DAY_OF_WEEK, Calendar.TUESDAY);

Log.d(TAG, String.format("Ending here %d-%d-%d", cal.get(Calendar.YEAR), cal.get(Calendar.MONTH)+1, cal.get(Calendar.DAY_OF_MONTH)));
/* Other stuff */

给我 2017 年和 2018 年的输出:

2017:

Starting here 2017-12-1 
Ending here 2018-1-2 

2018:

Starting here 2018-12-1
Ending here 2019-1-1 

是的,我知道第二组值的日期不是上周二。这就是 /* Other stuff */ 的目的。

java.time 和 ThreeTenABP

    LocalDate myLocalDate = LocalDate.of(2017, Month.DECEMBER, 1);

    System.out.format("Starting here: %s%n", myLocalDate);
    LocalDate lastTuesdayOfYear = myLocalDate
            .with(TemporalAdjusters.lastDayOfYear())
            .with(TemporalAdjusters.previousOrSame(DayOfWeek.TUESDAY));
    System.out.format("Ending here:   %s%n", lastTuesdayOfYear);

2017 年产出:

Starting here: 2017-12-01
Ending here:   2017-12-26

2018 年:

Starting here: 2018-12-01
Ending here:   2018-12-25

这个给你一年的最后一个星期二。不需要进一步的。

问题:我可以在 Android 上使用 java.time 吗?

是的,java.time 在新旧 Android 设备上都能很好地工作。它只需要至少 Java 6.

  • 在 Java 8 和更新的 Android 设备上(从 API 级别 26)内置现代 API。
  • 在 Java 6 和 7 中获取 ThreeTen Backport,现代 classes 的 backport(ThreeTen 用于 JSR 310;请参阅底部的链接)。
  • 在(较旧的)Android 使用 ThreeTen Backport 的 Android 版本。它叫做 ThreeTenABP。并确保使用子包从 org.threeten.bp 导入日期和时间 classes。

你的代码出了什么问题?

Calendar class 令人困惑。在您的情况下,它的行为符合文档。请允许我引用:

Calendar Fields Resolution

When computing a date and time from the calendar fields, … there may be inconsistent information (such as Tuesday, July 15, 1996 (Gregorian) -- July 15, 1996 is actually a Monday). Calendar will resolve calendar field values to determine the date and time in the following way.

If there is any conflict in calendar field values, Calendar gives priorities to calendar fields that have been set more recently. The following are the default combinations of the calendar fields. The most recent combination, as determined by the most recently set single field, will be used.

For the date fields:

 YEAR + MONTH + DAY_OF_MONTH
 YEAR + MONTH + WEEK_OF_MONTH + DAY_OF_WEEK
 YEAR + MONTH + DAY_OF_WEEK_IN_MONTH + DAY_OF_WEEK
 YEAR + DAY_OF_YEAR
 YEAR + DAY_OF_WEEK + WEEK_OF_YEAR

您的情况 DAY_OF_WEEK 是最近设置的。 DAY_OF_WEEK 属于上述五种组合中的三种。它选择哪一个我不知道。其中 None 还包括 DAY_OF_YEAR,您设置的另一个字段,因此 DAY_OF_YEAR 未被使用。这解释了您看到的行为。如果您坚持使用 Calendar,您应该可以使用 cal.set(Calendar.DAY_OF_WEEK_IN_MONTH, -1);,因为负值从月底开始倒数。

然而,Calendar 不仅设计糟糕且令人困惑,而且早已过时。因此,请考虑改用 java.time。

链接