Установка MinDate в DatePicker перемещает CalendarView на 1964 - программирование
Подтвердить что ты не робот

Установка MinDate в DatePicker перемещает CalendarView на 1964

Я отлаживаю проблему, когда CalendarView в DatePicker перемещается в октябре 1964 года, если существует минимальная установленная минимальная дата. Это воспроизводится по крайней мере на эмуляторе 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();

Снимок экрана:

October 1964 calendar view in date picker

Конечно, ожидание состоит в том, что прядильщики и календарный вид будут показывать одну и ту же дату.

Я буду отлаживать это, но если у каких-либо пользователей SO есть опыт или другое понимание, пожалуйста, поделитесь.

по теме:

4b9b3361

Ответ 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 в представлении календаря, как если бы список был прокручен до конца. Если номер месяца изменен, заголовок обновляется тестовым кодом выше, значение millis, переданное в formatDateRange(), равно 4131043200000, которое находится где-то в конце 2100, а 2100 является максимальным значением по умолчанию для DatePicker.

formatDateRange() в свою очередь использует android.text.format.Time как свое внутреннее представление календаря, в частности, установку миллиса, используя его метод set(). Я не проверял базовый код, но я понял, что переданное в миллисекундах значение усекается до 32-разрядного значения time_t секунд с присущим Year 2038 проблема, обертывая ценность чуть более 62 лет. Time сам использует struct tm, который представляет годы как int с 1900 года, что приводит к Time в 1960-х годах, который затем отформатируется в заголовке.

Это можно воспроизвести с помощью обычного DatePicker с CalendarView без установки даты min. Просто используйте прядильщики, чтобы переместить год до 2038 или более поздних версий, измените месяц (чтобы запустить обновление заголовка), и вы увидите год в заголовке обтекания до 1902 года.

Теперь остается вопрос, почему установка минимальной даты делает прокрутку календаря до максимальной даты. Ответ кажется адаптером календарного вида недели ListView. Он индексирует недели с минимальной даты, но когда изменяется минимальная дата, адаптер notifyDataSetChanged() не вызывается. Таким образом, обходной путь №1 работает, потому что:

  • Изменение даты более чем на месяц приводит к изменению заголовка месяца.
  • Изменение первого дня недели приводит к тому, что в списке недельного просмотра данные его адаптера изменились.

Ответ 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 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);
        }
    }
}

Ответ 3

Решение, в результате которого я закончил:

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

Это также работает, когда вы установили минимальную/максимальную дату для CalendarView

Ответ 4

Я согласен с @laalto. Оригинальный CalendarView - полный беспорядок. У меня также были проблемы с локалями, размер шрифта на Android 4.1.2 (о да, и он был сдвинут вниз), отсутствие возможностей доступности и т.д. Поэтому я решил скопировать исходный код и реализовать собственный календарь. Например, теперь текст заголовка отображается правильно.

/**
 * The source code has the Year 2038 problem and doesn't honor CalendarView 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 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

Прежде чем выполнять какие-либо из приведенных выше предложений, убедитесь, что ваш симулятор работает на сегодняшний день и время. Если вы останетесь открытым некоторое время, он может отстать на несколько дней. Я исправил свою проблему, просто перезапустив симулятор.