在 DatePicker 上设置 MinDate 会将 CalendarView 移动到 1964

Posted

技术标签:

【中文标题】在 DatePicker 上设置 MinDate 会将 CalendarView 移动到 1964【英文标题】:Setting MinDate on DatePicker moves CalendarView to 1964 【发布时间】:2013-05-15 21:34:34 【问题描述】:

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

这是一个演示问题的代码 sn-p:

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

屏幕截图:

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

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

相关:

Issue 42750: CalendarView initially scrolls to year 1964 using CalendarView to pick a date in future(安卓开发者)

【问题讨论】:

我的第一直觉是,如果在使用不推荐使用的方法时出现错误行为,请尝试不使用不推荐使用的方法。如果您使用日历设置日期,是否会出现同样的问题? 这与弃用无关。 setMinDate() 接受 long 毫秒值,我检查过它是否已传递给 DatePicker 好吧。只需使用已弃用的 Date 构造函数来制作一个演示此问题的示例。 如果您可以使用固定的 minDate,那么通过 XML 设置它实际上是可行的。否则,可以通过将 CalendarView.java 拉出 AOSP 来获得更优雅的修复。 【参考方案1】:

TL;DR

不是 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() 的毫秒值是4131043200000,这是在 2100 年末的某个地方,而 2100 是DatePicker 的默认最大值。

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

这可以用普通的DatePickerCalendarView 复制,没有设置最小日期。只需使用微调器将年份移动到 2038 年或更晚,更改月份(触发标题更新),您就会看到标题换行中的年份为 1902。

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

将日期更改超过一个月会导致月份标题更改。 更改一周的第一天会使周列表视图注意到其适配器的数据已更改。

【讨论】:

当我将一月设置为一个月时,它会返回二月。 @deepakgoel 这是另一个问题。一月是 0,二月是 1,以此类推。从零开始的索引。 那么我们该如何解决这个问题。我试过你的代码。在这种情况下,微调视图给出一月,日历视图给出二月 @deepakgoel,这不是错误,它是这样定义的:developer.android.com/reference/android/widget/… 月份值在 0-11 范围内 第一个解决方案不适用于三星 7.0 对 setCalendarView() 的弃用。第二个解决方案确实适用于我从 5.0 到 7.0 尝试过的所有设备【参考方案2】:

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

每次更改 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);
        
    

【讨论】:

这使我的应用程序在 2016 年 2 月 29 日崩溃,因为一年前比 2 月 27 日还早,并且低于最短日期,也不是设置的日期。 其实月份的选择并不是随意的。为了防止 2 月 29 日的问题,它应该是 48 而不是 24。 getCalendarView() 已弃用,在我的三星平板电脑 OS 7.0 上它崩溃了。还有其他解决方案吗?【参考方案3】:

我最终使用的解决方案:

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

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

【讨论】:

像魅力一样工作。谢谢! 谢谢,到目前为止最好和最简单的答案。该错误在 2021 年仍然存在【参考方案4】:

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

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

【讨论】:

【参考方案5】:

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

【讨论】:

以上是关于在 DatePicker 上设置 MinDate 会将 CalendarView 移动到 1964的主要内容,如果未能解决你的问题,请参考以下文章

在javascript中为datepicker设置maxDate和minDate,不起作用

在 DatePIcker 中设置 MinDate 和 MaxDate

设置mindate和maxdate后,jquery datepicker未禁用

minDate:0,不在datepicker中工作

如何将 MaxDate 和 MinDate 设置为从 ios 上的 web 服务返回的 DatePicker

jquery datepicker 选择时间小时的怎么设置