huanghaibin-dev / CalendarView

Android上一个优雅、万能自定义UI、仿iOS、自定义动画,支持垂直、水平方向切换、支持周视图、自定义周起始、性能高效的日历控件,支持热插拔实现的UI定制!支持标记、自定义颜色、农历、自定义月视图各种显示模式等。Canvas绘制,速度快、占用内存低,你真的想不到日历居然还可以如此优雅!An elegant, highly customized and high-performance Calendar Widget on Android.
Apache License 2.0
9.09k stars 1.79k forks source link

java.lang.IndexOutOfBoundsException in WeekView #644

Open yccheok opened 4 years ago

yccheok commented 4 years ago

Sometimes, I will encounter the following crash in Firebase Crashlytics

Fatal Exception: java.lang.IndexOutOfBoundsException: Invalid index 1, size is 1
       at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:255)
       at java.util.ArrayList.get(ArrayList.java:308)
       at com.haibin.calendarview.WeekView.onDraw(WeekView.java:47)
       at android.view.View.draw(View.java:17545)
       at android.view.View.updateDisplayListIfDirty(View.java:16534)
       at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3910)
       at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3890)
       at android.view.View.updateDisplayListIfDirty(View.java:16494)
       at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3910)
       at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3890)

May I know why it is so? Isn't the size for mItems should be either 0 or 7? In what situation, its size will become 1? Is initCalendarForWeekView will always return an array with size 7?

HolenZhou commented 4 years ago

我遇到同样问题了,而且是3月1号之后突然大量出现,好奇怪

HolenZhou commented 4 years ago

我看源码WeekView已处理了相关代码,不过MultiWeekView没处理,我出现了~ calendarview.MultiWeekView.onDraw + 47 (MultiWeekView.java:47)

yccheok commented 4 years ago

我有些怀疑 getFirstCalendarStartWithMinCalendar 的正确性。

这种把 ONE_DAY = 1000 3600 24;

会有出错的可能性吗?尤其是在一些有 Day Light Saving 的国家?

蛮多国家的 Day Light Saving 是在三月发生的。再加上,三月是,突然我有蛮多用户面临奔溃。因此,我的猜测应该是 Day Light Saving?

2020-03-24 21_11_51-wenote – WeekView java line 47 – Firebase console

https://en.wikipedia.org/wiki/Daylight_saving_time_by_country

可否考虑引进 implementation 'com.jakewharton.threetenabp:threetenabp:1.2.1' 处理 getFirstCalendarStartWithMinCalendar 计算?

yccheok commented 4 years ago

我尝试验证我的猜测。我写了以下代码在 Desktop 执行。

package javaapplication2;

import java.util.TimeZone;

/**
 *
 * @author yccheok
 */
public class JavaApplication2 {

    private static final long ONE_DAY = 1000 * 3600 * 24;

    static Calendar getFirstCalendarStartWithMinCalendar(TimeZone timeZone, int minYear, int minYearMonth, int minYearDay, int week, int weekStart) {
        java.util.Calendar date = java.util.Calendar.getInstance(timeZone);

        date.set(minYear, minYearMonth - 1, minYearDay);//

        long firstTimeMills = date.getTimeInMillis();//获得起始时间戳

        long weekTimeMills = (week - 1) * 7 * ONE_DAY;

        long timeCountMills = weekTimeMills + firstTimeMills;

        date.setTimeInMillis(timeCountMills);

        int startDiff = getWeekViewStartDiff(timeZone, 
                date.get(java.util.Calendar.YEAR),
                date.get(java.util.Calendar.MONTH) + 1,
                date.get(java.util.Calendar.DAY_OF_MONTH), weekStart);

        timeCountMills -= startDiff * ONE_DAY;
        date.setTimeInMillis(timeCountMills);

        Calendar calendar = new Calendar();
        calendar.setYear(date.get(java.util.Calendar.YEAR));
        calendar.setMonth(date.get(java.util.Calendar.MONTH) + 1);
        calendar.setDay(date.get(java.util.Calendar.DAY_OF_MONTH));

        return calendar;
    }

    private static int getWeekViewStartDiff(TimeZone timeZone, int year, int month, int day, int weekStart) {
        java.util.Calendar date = java.util.Calendar.getInstance(timeZone);
        date.set(year, month - 1, day);//
        int week = date.get(java.util.Calendar.DAY_OF_WEEK);
        if (weekStart == 1) {
            return week - 1;
        }
        if (weekStart == 2) {
            return week == 1 ? 6 : week - weekStart;
        }
        return week == 7 ? 0 : week;
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        final TimeZone timeZone = TimeZone.getTimeZone("Europe/Berlin");
        System.out.println(timeZone);

        int dayOfWeek = 1;
        for (int x=1000; x<=3000; x++) {
            Calendar result = getFirstCalendarStartWithMinCalendar(timeZone, 1971, 1, 1, x, dayOfWeek);

            System.out.println(result.getDay() + "/" + result.getMonth() + "/" + result.getYear());
            java.util.Calendar date = java.util.Calendar.getInstance();
            date.set(result.getYear(), result.getMonth()-1, result.getDay());
            if (dayOfWeek != date.get(java.util.Calendar.DAY_OF_WEEK)) {
                throw new java.lang.RuntimeException("ERROR");
            }
        }

        dayOfWeek = 2;
        for (int x=1000; x<=3000; x++) {
            Calendar result = getFirstCalendarStartWithMinCalendar(timeZone, 1971, 1, 1, x, dayOfWeek);

            System.out.println(result.getDay() + "/" + result.getMonth() + "/" + result.getYear());
            java.util.Calendar date = java.util.Calendar.getInstance();
            date.set(result.getYear(), result.getMonth()-1, result.getDay());
            if (dayOfWeek != date.get(java.util.Calendar.DAY_OF_WEEK)) {
                throw new java.lang.RuntimeException("ERROR");
            }
        }

        dayOfWeek = 7;
        for (int x=1000; x<=3000; x++) {
            Calendar result = getFirstCalendarStartWithMinCalendar(timeZone, 1971, 1, 1, x, dayOfWeek);

            System.out.println(result.getDay() + "/" + result.getMonth() + "/" + result.getYear());
            java.util.Calendar date = java.util.Calendar.getInstance();
            date.set(result.getYear(), result.getMonth()-1, result.getDay());
            if (dayOfWeek != date.get(java.util.Calendar.DAY_OF_WEEK)) {
                throw new java.lang.RuntimeException("ERROR");
            }
        }
    }

}

还是没有办法验证我的猜测。以上代码看来执行无误。

yccheok commented 4 years ago

我也粗略的测试以下的代码片段。

    /**
     * 生成周视图的7个item
     *
     * @param calendar  calendar
     * @param mDelegate mDelegate
     * @param weekStart weekStart
     * @return 生成周视图的7个item
     */
    static List<Calendar> initCalendarForWeekView(Calendar calendar, CalendarViewDelegate mDelegate, int weekStart) {

        java.util.Calendar date = java.util.Calendar.getInstance();//当天时间
        date.set(calendar.getYear(), calendar.getMonth() - 1, calendar.getDay());
        long curDateMills = date.getTimeInMillis();//生成选择的日期时间戳

        int weekEndDiff = getWeekViewEndDiff(calendar.getYear(), calendar.getMonth(), calendar.getDay(), weekStart);
        List<Calendar> mItems = new ArrayList<>();

        date.setTimeInMillis(curDateMills);
        Calendar selectCalendar = new Calendar();
        selectCalendar.setYear(date.get(java.util.Calendar.YEAR));
        selectCalendar.setMonth(date.get(java.util.Calendar.MONTH) + 1);
        selectCalendar.setDay(date.get(java.util.Calendar.DAY_OF_MONTH));
        if (selectCalendar.equals(mDelegate.getCurrentDay())) {
            selectCalendar.setCurrentDay(true);
        }
        LunarCalendar.setupLunarCalendar(selectCalendar);
        selectCalendar.setCurrentMonth(true);
        mItems.add(selectCalendar);

        for (int i = 1; i <= weekEndDiff; i++) {
            date.setTimeInMillis(curDateMills + i * ONE_DAY);
            Calendar calendarDate = new Calendar();
            calendarDate.setYear(date.get(java.util.Calendar.YEAR));
            calendarDate.setMonth(date.get(java.util.Calendar.MONTH) + 1);
            calendarDate.setDay(date.get(java.util.Calendar.DAY_OF_MONTH));
            if (calendarDate.equals(mDelegate.getCurrentDay())) {
                calendarDate.setCurrentDay(true);
            }
            LunarCalendar.setupLunarCalendar(calendarDate);
            calendarDate.setCurrentMonth(true);
            mItems.add(calendarDate);
        }
        return mItems;
    }

我调整不同的 weekStart,我发现返回的 weekEndDiff 都是 6。

如果我采用粗暴的方法,用

// int weekEndDiff = getWeekViewEndDiff(calendar.getYear(), calendar.getMonth(), calendar.getDay(), weekStart);
int weekEndDiff = 6;

是可行的方案吗?

HolenZhou commented 4 years ago

是这部分代码的问题,现在库里的所有时间戳,比如周起始,天起始,小时和分钟是当前时间,所以在夏令时的时区计算就会有问题,我项目的crash集中在东部夏令时晚上11点30-12点之间,所以我通过将周起始、天起始之类的时间戳手动设置为中午12点来解决了这个问题

yccheok commented 4 years ago

@HolenZhou

https://github.com/huanghaibin-dev/CalendarView/commits/master

作者已经在主干解决这问题。我会在这个月内尝试结合,看看有没啥问题。(虽然我还不知如何准确的复制这问题)