17

我正在调试一个问题,如果设置CalendarViewDatePicker非默认的最小日期,则将移至 1964 年 10 月。这至少在 API17 Nexus7 模拟器上重现,但在平台样式包括日期选择器中的微调器和日历视图的其他设备上,有关于此问题的报告。

这是一个演示该问题的代码片段:

class DatePickerDialog1964 extends DatePickerDialog {
    DatePickerDialog1964(Context c) {
        super(c, null, 2013, 4, 21);

        @SuppressWarnings("deprecation")
        Date min = new Date(2013-1900, 4, 21);

        DatePicker p = getDatePicker();
        p.setMinDate(min.getTime());
    }
}

...

new DatePickerDialog1964(context).show();

截屏:

日期选择器中的 1964 年 10 月日历视图

当然,期望是微调器和日历视图将显示相同的日期。

我会继续调试这个,但如果任何 SO 用户有经验或其他见解,请分享。

有关的:

4

5 回答 5

21

TL;博士

这不是 1964 而是 2100 格式错误。CalendarView有错误。formatDateRange()在 2038 年之后不起作用。

解决方法 #1

class DatePickerDialog1964 extends DatePickerDialog {
    DatePickerDialog1964(Context c) {
        super(c, null, 2013, 4, 21);

        @SuppressWarnings("deprecation")
        Date min = new Date(2013-1900, 4, 21);

        DatePicker p = getDatePicker();
        CalendarView cv = p.getCalendarView(); // should check for null
        long cur = cv.getDate();
        int d = cv.getFirstDayOfWeek();
        p.setMinDate(min.getTime());
        cv.setDate(cur + 1000L*60*60*24*40);
        cv.setFirstDayOfWeek((d + 1) % 7);
        cv.setDate(cur);
        cv.setFirstDayOfWeek(d);
    }
}

解决方法 #2 我实际使用过

// Calendar view is a cascade of bugs.
// Work around that by explicitly disabling it.
datePicker.setCalendarViewShown(false);

分析

CalendarView用于android.text.format.DateUtils.formatDateRange()在标题中生成月/年名称。日历视图仅在月份编号更改时更新标题。例如,如果月份编号相同但年份更改,则不更新标题。

在布局阶段的某个地方,底层ListView调用OnScrollListener日历视图,就好像列表滚动到末尾一样。如果更改月份编号,则使用上面的测试代码更新标头,传递给的毫秒值formatDateRange()41310432000002100 年末的某个地方,而 2100 是DatePicker.

formatDateRange()反过来android.text.format.Time用作其内部日历表示,特别是使用其set()方法设置毫秒。我没有检查底层的本机代码,但我有根据的猜测是,由于固有的2038 年问题time_t,以​​毫秒为单位的值被截断为 32 位秒值,包装为略高于 62 年的值。它本身使用which 表示自 1900 年以来的年份,因此导致1960 年代的 a 然后在标题中格式化。Timestruct tmintTime

这可以在没有设置最小日期的情况下使用纯文本进行DatePicker复制CalendarView。只需使用微调器将年份移动到 2038 年或更晚,更改月份(以触发标题更新),您就会看到标题换行中的年份为 1902。

现在,问题仍然是为什么设置最小日期会使日历视图滚动到最大日期。答案似乎是日历视图的周ListView适配器。它索引从最小日期开始的周数,但是当最小日期更改时,notifyDataSetChanged()不会调用适配器。所以上面的解决方法 #1 有效,因为:

  • 将日期更改超过一个月会使月份标题更改。
  • 更改一周的第一天会使一周列表视图注意到其适配器的数据已更改。
于 2013-05-21T13:58:15.213 回答
13

关于这个错误的来源,上述答案是正确的。对于我的应用程序,我正在使用此解决方法:

每次更改 DatePicker 的日期或 minDate 时,使用应在选择器/日历中选择的日期调用以下例程:

private void fixUpDatePickerCalendarView(Calendar date) {
    // Workaround for CalendarView bug relating to setMinDate():
    // https://code.google.com/p/android/issues/detail?id=42750
    // Set then reset the date on the calendar so that it properly
    // shows today's date. The choice of 24 months is arbitrary.
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
        final CalendarView cal = datePicker.getDatePicker().getCalendarView();
        if (cal != null) {
            date.add(Calendar.MONTH, 24);
            cal.setDate(date.getTimeInMillis(), false, true);
            date.add(Calendar.MONTH, -24);
            cal.setDate(date.getTimeInMillis(), false, true);
        }
    }
}
于 2013-10-01T20:50:55.573 回答
6

我最终使用的解决方案:

private void fixBuggyCalendarview(CalendarView cv) {
    long current = cv.getDate();
    cv.setDate(cv.getMaxDate(), false, true);
    cv.setDate(current, false, true);
}

当您为 CalendarView 设置最小/最大日期时,这也有效

于 2015-06-22T04:41:54.000 回答
0

我同意@laalto。原作CalendarView一团糟。我也遇到了语言环境、Android 4.1.2 上的字体大小(哦,是的,它被向下移动了)、缺乏辅助功能等问题。所以我决定复制源代码并实现我自己的日历。例如,现在可以正确显示标题文本。

/**
 * The source code has the Year 2038 problem and doesn't honor CalendarView's mCurrentLocale.
 */
private void setMonthDisplayed(Calendar calendar) {
    mMonthName.setText(DateFormat.format("MMMM, yyyy", calendar));
    mMonthName.invalidate();

    mCurrentMonthDisplayed = calendar.get(Calendar.MONTH);
    mAdapter.setFocusMonth(mCurrentMonthDisplayed);
}

/**
 * This imlementation formats the date correctly and uses the stand-alone form of a month (for the languages where it's important).
 */
private void setMonthDisplayed(Calendar calendar) {
    mMonthName.setText(new SimpleDateFormat("LLLL yyyy", mCurrentLocale).format(calendar.getTime()));
    mMonthName.invalidate();

    mCurrentMonthDisplayed = calendar.get(Calendar.MONTH);
    mAdapter.setFocusMonth(mCurrentMonthDisplayed);
}
于 2016-04-22T10:10:59.187 回答
0

在实施上述任何建议之前,请检查以确保您的模拟器在今天的日期和时间运行。如果打开一段时间,它可能会滞后几天。我只需重新启动模拟器即可解决我的问题。

于 2016-04-26T23:44:03.377 回答