Input:
package io.github.memfis19.cadar.view;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Process;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.List;
import io.github.memfis19.cadar.CalendarController;
import io.github.memfis19.cadar.R;
import io.github.memfis19.cadar.data.entity.Event;
import io.github.memfis19.cadar.event.CalendarPrepareCallback;
import io.github.memfis19.cadar.event.DisplayEventCallback;
import io.github.memfis19.cadar.event.OnDayChangeListener;
import io.github.memfis19.cadar.event.OnMonthChangeListener;
import io.github.memfis19.cadar.internal.helper.ScrollManager;
import io.github.memfis19.cadar.internal.process.EventsProcessor;
import io.github.memfis19.cadar.internal.process.MonthEventsProcessor;
import io.github.memfis19.cadar.internal.ui.month.MonthCalendarHelper;
import io.github.memfis19.cadar.internal.ui.month.adapter.MonthAdapter;
import io.github.memfis19.cadar.internal.ui.month.adapter.MonthHandlerThread;
import io.github.memfis19.cadar.internal.utils.DateUtils;
import io.github.memfis19.cadar.settings.MonthCalendarConfiguration;

/**
 * Created by memfis on 7/14/16.
 */
public class MonthCalendar extends LinearLayout implements ViewPager.OnPageChangeListener,
        ScrollManager.OnScrollChanged, CalendarController<MonthCalendarConfiguration, Calendar>, ViewTreeObserver.OnPreDrawListener {

    private static final String TAG = "MonthView3";

    private Calendar currentMonth;
    private int currentPosition = 0;

    private MonthHandlerThread monthHandlerThread;
    private EventsProcessor<Calendar, SparseArray<List<Event>>> eventsAsyncProcessor;

    private LayoutInflater layoutInflater;
    private ViewGroup monthHeaderView;
    private ViewPager monthGridView;

    private MonthAdapter monthAdapter;

    private MonthCalendarConfiguration monthCalendarConfiguration;
    private SimpleDateFormat weekDayFormatter;

Context:
cadar/src/main/java/io/github/memfis19/cadar/CalendarController.java
public interface CalendarController<T extends BaseCalendarConfiguration, E> {

    void prepareCalendar(T configuration);

    void releaseCalendar();

    void setSelectedDay(Calendar selectedDay, boolean scrollToSelectedDay);

    void displayEvents(List<Event> eventList, @Nullable DisplayEventCallback<E> callback);
}
cadar/src/main/java/io/github/memfis19/cadar/internal/process/MonthEventsProcessor.java
public class MonthEventsProcessor extends EventsProcessor<Calendar, SparseArray<List<Event>>> {

    public MonthEventsProcessor(boolean shouldProcess, EventCalculator eventProcessor) {
        super(shouldProcess, eventProcessor, false);
    }

    @Override
    protected SparseArray<List<Event>> processEvents(Calendar target) {

        List<Event> result;

        final SparseArray<List<Event>> calendarEvents = new SparseArray<>();

        if (isShouldProcess()) {
            Date start = DateUtils.setTimeToMonthStart(target.getTime());
            Date end = DateUtils.setTimeToMonthEnd(target.getTime());

            result = getEventProcessor().getEventsForPeriod(start, end);
        } else
            result = new ArrayList<>(getEventList() == null ? new ArrayList<Event>() : getEventList());

        Calendar temp = DateUtils.getCalendarInstance();
        for (Event event : result) {
            temp.setTime(event.getEventStartDate());

            if (!DateUtils.isSameMonth(temp, target)) continue;

            List<Event> events = calendarEvents.get(temp.get(Calendar.DAY_OF_MONTH), new ArrayList<Event>());
            events.add(event);

            calendarEvents.put(temp.get(Calendar.DAY_OF_MONTH), events);
        }
        result.clear();

        return calendarEvents;
    }
}
cadar/src/main/java/io/github/memfis19/cadar/internal/utils/DateUtils.java
public final class DateUtils {

    private final static String TAG = "DateUtils";

    public static final int MAX_YEAR_RANGE = 50;

    public static final long WEEK_TIME_MILLIS = 7 * 24 * 60 * 60 * 1000;

    public static final String MMMM_yyyy = "MMMM yyyy";
    public static final String cccc_dd = "cccc dd";

    public static final Calendar today = getCalendarInstance();//TODO: need update current value correctly

    public static Locale danish = new Locale("da", "DK");
    public static Map<String, DateFormat> map = new HashMap<>();

    public static Locale getLocale() {
        return danish;
    }

    public static Calendar getCalendarInstance() {
        try {
            return Calendar.getInstance(getTimeZone(), getLocale());
        } catch (Exception e) {
            Log.e(TAG, "Can't create instance of the specified calendar. ");
        }
        return Calendar.getInstance();
    }

    public static TimeZone getTimeZone() {
        return TimeZone.getDefault();
    }

    public static Calendar setTimeToYearStart(Calendar calendar) {
        calendar.set(Calendar.MONTH, Calendar.JANUARY);
        calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMinimum(Calendar.DAY_OF_MONTH));
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        return calendar;
    }

    public static Calendar setTimeToYearEnd(Calendar calendar) {
        calendar.set(Calendar.MONTH, Calendar.DECEMBER);
        calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
        calendar.set(Calendar.HOUR_OF_DAY, 23);
        calendar.set(Calendar.MINUTE, 59);
        calendar.set(Calendar.SECOND, 59);
        calendar.set(Calendar.MILLISECOND, 999);
        return calendar;
    }

    public static Date setTimeToYearStart(Date date) {
        Calendar calendar = getCalendarInstance();
        calendar.setTime(date);
        setTimeToMidnight(calendar);
        calendar.set(Calendar.MONTH, Calendar.JANUARY);
        calendar.set(Calendar.DAY_OF_YEAR, 1);
        return calendar.getTime();
    }

    public static Date setTimeToYearEnd(Date date) {
        Calendar calendar = getCalendarInstance();
        calendar.setTime(date);
        setTimeToEndOfTheDay(calendar);
        calendar.set(Calendar.MONTH, Calendar.DECEMBER);
        calendar.set(Calendar.DAY_OF_MONTH, 31);
        return calendar.getTime();
    }

    public static Date setTimeToMonthStart(Date date) {
        Calendar calendar = getCalendarInstance();
        calendar.setTime(date);
        setTimeToMidnight(calendar);
        calendar.set(Calendar.DAY_OF_MONTH, 1);
        return calendar.getTime();
    }

    public static Calendar setTimeToMonthStart(Calendar calendar) {
        calendar = setTimeToMidnight(calendar);
        calendar.set(Calendar.DAY_OF_MONTH, 1);
        return calendar;
    }

    public static Date setTimeToMonthEnd(Date date) {
        Calendar calendar = getCalendarInstance();
        calendar.setTime(date);
        setTimeToEndOfTheDay(calendar);
        calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
        return calendar.getTime();
    }

    public static Calendar setTimeToMidnight(Calendar calendar) {
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);

        return calendar;
    }

    /**
     * Sets the date time to the midnight.
     *
     * @param date - to set the time
     * @return - the same date with new time
     */
    public static Date setTimeToMidnight(Date date) {
        Calendar calendar = getCalendarInstance();
        calendar.setTimeInMillis(date.getTime());
        calendar = setTimeToMidnight(calendar);

        return calendar.getTime();
    }

    public static Calendar setTimeToEndOfTheDay(Calendar calendar) {
        calendar.set(Calendar.HOUR_OF_DAY, 23);
        calendar.set(Calendar.MINUTE, 59);
        calendar.set(Calendar.SECOND, 59);
        calendar.set(Calendar.MILLISECOND, 0);

        return calendar;
    }

    /**
     * Sets the date time to the end of the day.
     *
     * @param date - to set the time
     * @return - the same date with new time
     */
    public static Date setTimeToEndOfTheDay(Date date) {
        Calendar calendar = getCalendarInstance();
        calendar.setTimeInMillis(date.getTime());
        calendar = setTimeToEndOfTheDay(calendar);

        return calendar.getTime();
    }

    /**
     * Method returns number of days between two dates using hours/minutes for calculation.
     * Example: start = 20/12/2015T15:00:00, end = 21/12/2015T13:00:00, result = 0 days.
     * Use the same time for different dates to avoid such problem.
     *
     * @param start - start date
     * @param end   - end period
     * @return - number of years between start and end date.
     * @see #setTimeToMidnight(java.util.Date)
     * @see #setTimeToEndOfTheDay(java.util.Date)
     */
    public static int daysBetween(Date start, Date end) {
        DateTime startDateTime = new DateTime(start);
        DateTime endDateTime = new DateTime(end);

        return Days.daysBetween(startDateTime, endDateTime).getDays();
    }

    public static int daysBetweenPure(Date start, Date end) {
        DateTime startDateTime = new DateTime(setTimeToMidnight(start));
        DateTime endDateTime = new DateTime(setTimeToMidnight(end));

        return Days.daysBetween(startDateTime, endDateTime).getDays();
    }

    public static int monthBetween(Date start, Date end) {
        DateTime startDateTime = new DateTime(start);
        DateTime endDateTime = new DateTime(end);

        return Months.monthsBetween(startDateTime, endDateTime).getMonths();
    }

    public static int monthBetweenPure(Date start, Date end) {
        Calendar startCalendar = getCalendarInstance();
        startCalendar.setTime(start);
        Calendar endCalendar = getCalendarInstance();
        endCalendar.setTime(end);

        int diffYear = endCalendar.get(Calendar.YEAR) - startCalendar.get(Calendar.YEAR);
        int diffMonth = diffYear * 12 + endCalendar.get(Calendar.MONTH) - startCalendar.get(Calendar.MONTH);
        return diffMonth;
    }

    public static int weeksBetween(Date start, Date end) {
        DateTime startDateTime = new DateTime(start);
        DateTime endDateTime = new DateTime(end);

        return Weeks.weeksBetween(startDateTime, endDateTime).getWeeks();
    }

    /**
     * Method returns number of years between two dates using day/month for calculation.
     * Example: start = 20/12/2015, end = 20/11/2016, result = 0 years.
     *
     * @param start - start date
     * @param end   - end period
     * @return - number of years between start and end date.
     */
    public static int yearsBetween(Date start, Date end) {
        DateTime startDateTime = new DateTime(start);
        DateTime endDateTime = new DateTime(end);

        return Years.yearsBetween(startDateTime, endDateTime).getYears();
    }

    /**
     * Method returns number of years between two dates using years for calculation.
     * Example: start = 20/12/2015, end = 20/11/2016, result = 1 year.
     *
     * @param start - start date
     * @param end   - end period
     * @return - number of years between start and end date.
     */
    public static int yearsBetweenPure(Date start, Date end) {
        DateTime startDateTime = new DateTime(start);
        DateTime endDateTime = new DateTime(end);

        return Math.abs(endDateTime.getYear() - startDateTime.getYear());
    }

    public static void resetTime(GregorianCalendar calendar) {
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
    }

    /**
     * Return the minimum date for using in application. Now it is 01/01/1900
     *
     * @return - minimum supported date.
     */
    public static Date getMinimumSupportedDate() {
        DateTime minimumDate = new DateTime(1900, 1, 1, 0, 0);
        return minimumDate.toDate();
    }

    /**
     * Return the maximum date for using in application.
     *
     * @return - maximum supported date.
     */
    public static Date getMaximumSupportedDate() {
        DateTime maximumDate = new DateTime(DateTime.now().year().get() + MAX_YEAR_RANGE / 2, 1, 1, 0, 0);
        return maximumDate.toDate();
    }

    public static Date getCurrentDate() {
        DateTime dateTime = new DateTime();
        DateTime currentDate = new DateTime(dateTime.getYear(), dateTime.getMonthOfYear(), dateTime.getDayOfMonth(), 0, 0);
        return currentDate.toDate();
    }

    public static long getCurrentTime() {
        Calendar calendar = getCalendarInstance();
        long offset = calendar.get(Calendar.ZONE_OFFSET);
        return calendar.getTimeInMillis() - offset;
    }

    public static boolean isLastDayOfTheMonth(Date date) {
        Calendar calendar = getCalendarInstance();
        calendar.setTime(date);
        return calendar.get(Calendar.DAY_OF_MONTH) == calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
    }

    public static Date addYearToDate(Date date, int yearsToAdd) {
        Calendar calendar = getCalendarInstance();
        calendar.setTime(date);
        calendar.add(Calendar.YEAR, yearsToAdd);
        return calendar.getTime();
    }

    public static Date addMonthToDate(Date date, int monthsToAdd) {
        Calendar calendar = getCalendarInstance();
        calendar.setTime(date);
        calendar.add(Calendar.MONTH, monthsToAdd);
        return calendar.getTime();
    }

    public static Date addDayToDate(Date date, int daysToAdd) {
        Calendar calendar = getCalendarInstance();
        calendar.setTime(date);
        calendar.add(Calendar.DAY_OF_YEAR, daysToAdd);
        return calendar.getTime();
    }

    public static String dateToString(Calendar calendar, String pattern) {
        Date date = calendarToDate(calendar);
        return dateToString(date, pattern);
    }

    public static Date calendarToDate(Calendar calendar) {
        return new Date(calendar.getTimeInMillis());
    }

    public static String dateToString(Date date, String pattern) {
        return getDateFormat(pattern).format(date);
    }

    public static DateFormat getDateFormat(String pattern) {
        if (!map.containsKey(pattern)) {
            DateFormat df = new SimpleDateFormat(pattern, getLocale());
            map.put(pattern, df);
        }
        return map.get(pattern);
    }

    public static boolean isSameDay(Date firstDate, Date secondDate) {
        Calendar firstCalendar = getCalendarInstance();
        Calendar secondCalendar = getCalendarInstance();

        firstCalendar.setTime(firstDate);
        secondCalendar.setTime(secondDate);

        return isSameDay(firstCalendar, secondCalendar);
    }

    public static boolean isSameMonth(Calendar firstCalendar, Calendar secondCalendar) {
        if (firstCalendar == null || secondCalendar == null) return false;
        return firstCalendar.get(Calendar.YEAR) == secondCalendar.get(Calendar.YEAR)
                && firstCalendar.get(Calendar.MONTH) == secondCalendar.get(Calendar.MONTH);
    }

    public static boolean isSameYear(Calendar firstCalendar, Calendar secondCalendar) {
        if (firstCalendar == null || secondCalendar == null) return false;
        return firstCalendar.get(Calendar.YEAR) == secondCalendar.get(Calendar.YEAR);
    }

    public static boolean isSameDay(Calendar firstDate, Calendar secondDate) {
        return (firstDate.get(Calendar.DAY_OF_YEAR) == secondDate.get(Calendar.DAY_OF_YEAR)
                && firstDate.get(Calendar.YEAR) == secondDate.get(Calendar.YEAR));
    }

    //TODO: find other solution without creation of Calendar instance for performance

    public static Date roundDateToMinute(Date date) {
        if (date == null) return null;
        Calendar calendar = getCalendarInstance();
        calendar.setTime(date);

        if (calendar.get(Calendar.SECOND) > 30) {
            calendar.add(Calendar.MINUTE, 1);
        }

        calendar.set(Calendar.SECOND, 0);

        return calendar.getTime();
    }

    public static Date cutDateSeconds(Date date) {
        if (date == null) return null;
        Calendar calendar = getCalendarInstance();
        calendar.setTime(date);
        calendar.set(Calendar.SECOND, 0);
        return calendar.getTime();
    }

    /**
     *
Checks if a date is today.

     *
     * @param date the date, not altered, not null.
     * @return true if the date is today.
     * @throws IllegalArgumentException if the date is null
     */
    public static boolean isToday(Date date) {
        return isSameDay(date, today.getTime());
    }

    /**
     *
Checks if a calendar date is today.

     *
     * @param calendar the calendar, not altered, not null
     * @return true if cal date is today
     * @throws IllegalArgumentException if the calendar is null
     */
    public static boolean isToday(Calendar calendar) {
        return isSameDay(calendar, today);
    }
}
cadar/src/main/java/io/github/memfis19/cadar/internal/ui/month/adapter/MonthHandlerThread.java
public class MonthHandlerThread extends HandlerThread {

    private static final String TAG = "MonthHandlerThread";

    private static final int REQUEST_MONTH_PREPARATION = 852;

    private static final int NUM_OF_DAYS_IN_WEEK = 7;

    private class MonthInfoDto {
        private Calendar calendar;
        private Integer shiftValue;
        private RecyclerView recyclerView;

        MonthInfoDto(Calendar calendar, Integer shiftValue, RecyclerView recyclerView) {
            this.calendar = calendar;
            this.shiftValue = shiftValue;
            this.recyclerView = recyclerView;
        }
    }

    interface AdapterPrepareListener {
        void onReadyAdapter(Calendar month, List<Calendar> monthDays, RecyclerView recyclerView);
    }

    private boolean hasQuit = false;

    private Handler requestHandler;
    private Handler responseHandler;
    private AdapterPrepareListener adapterPrepareListener;

    public MonthHandlerThread() {
        super(TAG, Process.THREAD_PRIORITY_BACKGROUND);
    }

    void setAdapterPrepareListener(AdapterPrepareListener adapterPrepareListener) {
        this.adapterPrepareListener = adapterPrepareListener;
    }

    @Override
    protected void onLooperPrepared() {
        responseHandler = new Handler(Looper.getMainLooper());

        requestHandler = new Handler(Looper.myLooper()) {
            @Override
            public void handleMessage(Message msg) {
                if (msg.what == REQUEST_MONTH_PREPARATION) {
                    handlePreparation((MonthInfoDto) msg.obj);
                }
            }
        };
    }

    @Override
    public boolean quit() {
        hasQuit = true;
        return super.quit();
    }

    @Override
    public boolean quitSafely() {
        hasQuit = true;
        return (Build.VERSION.SDK_INT > 17) ? super.quitSafely() : super.quit();
    }

    void queuePreparation(Calendar calendar, Integer shiftValue, RecyclerView recyclerView) {
        if (hasQuit || requestHandler == null) return;
        requestHandler.obtainMessage(REQUEST_MONTH_PREPARATION,
                new MonthInfoDto(calendar, shiftValue, recyclerView)).sendToTarget();
    }

    private void handlePreparation(final MonthInfoDto monthInfoDto) {
        final Calendar month = (Calendar) monthInfoDto.calendar.clone();
        month.add(Calendar.MONTH, monthInfoDto.shiftValue);

        month.set(Calendar.DAY_OF_MONTH, 1);
        final List<Calendar> monthDays = prepare(month);

        if (!hasQuit && adapterPrepareListener != null)
            responseHandler.post(new Runnable() {
                @Override
                public void run() {
                    adapterPrepareListener.onReadyAdapter(month, monthDays, monthInfoDto.recyclerView);
                }
            });
    }

    private List<Calendar> prepare(Calendar monthCalendar) {

        List<Calendar> monthDays = new ArrayList<>();
        Calendar iterator = (Calendar) monthCalendar.clone();

        int month = iterator.get(Calendar.MONTH);
        int lastDay = iterator.getActualMaximum(Calendar.DATE);

        iterator.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
        if (iterator.get(Calendar.MONTH) == month && iterator.get(Calendar.DATE) > 1) {
            iterator.add(Calendar.DATE, -7);
        }

        boolean finish = false;
        while (!finish) {

            for (int i = 0; i < NUM_OF_DAYS_IN_WEEK; i++) {
                if (month == iterator.get(Calendar.MONTH) && lastDay == iterator.get(Calendar.DATE)) {
                    finish = true;
                }
                monthDays.add((Calendar) iterator.clone());
                iterator.add(Calendar.DATE, 1);
            }
        }

        return monthDays;
    }

}
cadar/src/main/java/io/github/memfis19/cadar/internal/helper/ScrollManager.java
public final class ScrollManager {

    static private Map<View, ScrollManager> viewScrollManagerMap = new HashMap<>();

    public static final int SCROLL_STATE_IDLE = 1;
    public static final int SCROLL_STATE_DRAGGING = 2;
    public static final int SCROLL_STATE_SETTLING = 3;

    @IntDef({SCROLL_STATE_IDLE, SCROLL_STATE_DRAGGING, SCROLL_STATE_SETTLING})
    @Retention(RetentionPolicy.SOURCE)
    public @interface ScrollState {
    }

    public interface OnScrollChanged {
        void onScrollStateChanged(@ScrollState int scrollState);
    }

    private View view;
    private int currentScrollState = SCROLL_STATE_IDLE;
    private List<WeakReference<OnScrollChanged>> subscribers = new ArrayList<>();

    public static ScrollManager geViewInstance(View view) {
        if (viewScrollManagerMap.get(view) == null) {
            viewScrollManagerMap.put(view, new ScrollManager(view));
        }
        return viewScrollManagerMap.get(view);
    }

    public void releaseScrollManager() {
        subscribers = null;
        viewScrollManagerMap.remove(view);
        view = null;
    }

    private ScrollManager(View view) {
        this.view = view;
        subscribers = new ArrayList<>();
    }

    public void notifyScrollStateChanged(@ScrollState int scrollState) {
        if (currentScrollState != scrollState) {
            currentScrollState = scrollState;
            notifyAllSubscribers();
        }
    }

    public int getCurrentScrollState() {
        return currentScrollState;
    }

    public void subscribeForScrollStateChanged(OnScrollChanged onScrollChanged) {
        subscribers.add(new WeakReference<>(onScrollChanged));
    }

    public void unSubscribeForScrollStateChanged(@NonNull OnScrollChanged onScrollChanged) {
        for (WeakReference<OnScrollChanged> weakReference : subscribers) {
            if (onScrollChanged.equals(weakReference.get())) {
                subscribers.remove(weakReference);
                return;
            }
        }
    }

    private void notifyAllSubscribers() {
        for (WeakReference<OnScrollChanged> weakReference : subscribers) {
            if (weakReference.get() != null)
                weakReference.get().onScrollStateChanged(currentScrollState);
            else subscribers.remove(weakReference);
        }
    }

}
cadar/src/main/java/io/github/memfis19/cadar/settings/MonthCalendarConfiguration.java
public final class MonthCalendarConfiguration extends BaseCalendarConfiguration {

    @CadarSettings.DayOfWeeks
    private int firstDayOfWeek = Calendar.MONDAY;

    private boolean displayDaysOutOfMonth = true;

    private MonthDayDecoratorFactory monthDayDecoratorFactory;
    private WeekDayDecorator weekDayDecorator;
    private EventsProcessor<Calendar, SparseArray<List<Event>>> eventsProcessor;

    @LayoutRes
    private int
            monthLayoutId = R.layout.month_calendar_event_layout,
            weekTitleLayoutId = R.layout.month_calendar_day_of_week_layout;

    private MonthCalendarConfiguration() {
        super();
    }

    public static class Builder extends BaseCalendarConfigurationBuilder<MonthCalendarConfiguration> {

        private MonthCalendarConfiguration monthCalendarConfiguration;

        public Builder() {
            super();
            monthCalendarConfiguration = new MonthCalendarConfiguration();
        }

        public Builder setFirstDayOfWeek(@CadarSettings.DayOfWeeks int firstDayOfWeek) {
            monthCalendarConfiguration.firstDayOfWeek = firstDayOfWeek;
            return this;
        }

        public Builder setMonthDayLayout(@LayoutRes int monthLayoutId, MonthDayDecoratorFactory monthDayDecoratorFactory) {
            monthCalendarConfiguration.monthLayoutId = monthLayoutId;
            monthCalendarConfiguration.monthDayDecoratorFactory = monthDayDecoratorFactory;
            return this;
        }

        public Builder setEventsProcessor(EventsProcessor<Calendar, SparseArray<List<Event>>> eventsProcessor) {
            monthCalendarConfiguration.eventsProcessor = eventsProcessor;
            return this;
        }

        public Builder setDayWeekTitleLayout(@LayoutRes int weekTitleLayoutId, WeekDayDecorator weekDayDecorator) {
            monthCalendarConfiguration.weekTitleLayoutId = weekTitleLayoutId;
            monthCalendarConfiguration.weekDayDecorator = weekDayDecorator;
            return this;
        }

        public Builder setDisplayDaysOutOfMonth(boolean displayDaysOutOfMonth) {
            monthCalendarConfiguration.displayDaysOutOfMonth = displayDaysOutOfMonth;
            return this;
        }

        @Override
        public MonthCalendarConfiguration build() throws NullPointerException, IllegalStateException, IllegalArgumentException {
            if (initialDay == null)
                throw new NullPointerException("Passed initial day can't be null.");
            monthCalendarConfiguration.initialDay = initialDay;

            if (eventProcessingEnabled && eventCalculator == null)
                throw new IllegalStateException("Configuration set to process events. But event processor not passed or null.");
            monthCalendarConfiguration.eventProcessingEnabled = eventProcessingEnabled;
            monthCalendarConfiguration.eventCalculator = eventCalculator;

            if (eventProcessingEnabled && eventFactory == null)
                throw new NullPointerException("Event factory is null. Please setup it.");
            monthCalendarConfiguration.eventFactory = eventFactory;
            monthCalendarConfiguration.eventCalculator.setEventFactory(monthCalendarConfiguration.eventFactory);

            if (weekDayTitleTranslationEnabled) {
                if (mondayTitle == 0
                        || tuesdayTitle == 0
                        || wednesdayTitle == 0
                        || thursdayTitle == 0
                        || fridayTitle == 0
                        || saturdayTitle == 0
                        || sundayTitle == 0) {
                    throw new IllegalArgumentException("Configuration set to override default week day titles, but not all titles are passed.");
                }
            }
            monthCalendarConfiguration.weekDayTitleTranslationEnabled = weekDayTitleTranslationEnabled;
            if (monthCalendarConfiguration.weekDayTitleTranslationEnabled) {
                monthCalendarConfiguration.mondayTitle = mondayTitle;
                monthCalendarConfiguration.tuesdayTitle = tuesdayTitle;
                monthCalendarConfiguration.wednesdayTitle = wednesdayTitle;
                monthCalendarConfiguration.thursdayTitle = thursdayTitle;
                monthCalendarConfiguration.fridayTitle = fridayTitle;
                monthCalendarConfiguration.saturdayTitle = saturdayTitle;
                monthCalendarConfiguration.sundayTitle = sundayTitle;
            }

            if (periodType != Calendar.MONTH && periodType != Calendar.YEAR)
                throw new IllegalArgumentException("Period type should be Calendar.MONTH or Calendar.YEAR only.");
            if (periodValue < 1)
                throw new IllegalArgumentException("Period value should be more then 1.");
            if (periodType == Calendar.MONTH && periodValue < 3)
                throw new IllegalStateException("In case with Calendar.MONTH period type, minimum value should be GE 3.");

            monthCalendarConfiguration.periodType = periodType;
            monthCalendarConfiguration.periodValue = periodValue;

            return monthCalendarConfiguration;
        }
    }

    public int getFirstDayOfWeek() {
        return firstDayOfWeek;
    }

    public int getMonthLayoutId() {
        return monthLayoutId;
    }

    public MonthDayDecoratorFactory getMonthDayDecoratorFactory() {
        return monthDayDecoratorFactory;
    }

    public int getWeekTitleLayoutId() {
        return weekTitleLayoutId;
    }

    public WeekDayDecorator getWeekDayDecorator() {
        return weekDayDecorator;
    }

    public boolean isDisplayDaysOutOfMonth() {
        return displayDaysOutOfMonth;
    }

    public EventsProcessor<Calendar, SparseArray<List<Event>>> getEventsProcessor() {
        return eventsProcessor;
    }
}
cadar/src/main/java/io/github/memfis19/cadar/internal/ui/month/MonthCalendarHelper.java
public final class MonthCalendarHelper {

    private final static Calendar today = DateUtils.getCalendarInstance();
    private static Calendar selectedDay = today;

    private MonthCalendarHelper() {

    }

    public static Calendar getToday() {
        return today;
    }

    public static Calendar getSelectedDay() {
        return selectedDay;
    }

    public static void updateSelectedDay(Calendar selectedDay) {
        MonthCalendarHelper.selectedDay = selectedDay;
    }

}
cadar/src/main/java/io/github/memfis19/cadar/data/entity/Event.java
public interface Event extends Serializable {

    Long getEventId();

    String getEventTitle();

    String getEventDescription();

    Date getOriginalEventStartDate();

    Date getEventStartDate();

    Date getEventEndDate();

    /***
     * Sets the new start date for new generating event.
     *
     * @param startEventDate
     */
    void setEventStartDate(Date startEventDate);

    /***
     * Sets the new end date for new generating event.
     *
     * @param endEventDate
     */
    void setEventEndDate(Date endEventDate);

    /***
     * Sets calendarId field generated with system calendar after sync it
     *
     * @param calendarId
     * @see SyncUtils
     */
    void setCalendarId(Long calendarId);

    /***
     * Return calendarId field generated with system calendar after sync it
     *
     * @return
     * @see SyncUtils
     */
    Long getCalendarId();

    /***
     * @return repeat period for current entity instance
     * @see EventProperties.RepeatPeriod
     */
    @EventProperties.RepeatPeriod
    int getEventRepeatPeriod();

    /***
     * @return event icon url
     */
    String getEventIconUrl();

    /***
     * @return boolean which indicates is event editable
     */
    Boolean isEditable();

    /***
     * @return boolean which indicates is event length All day long
     */
    Boolean isAllDayEvent();
}
cadar/src/main/java/io/github/memfis19/cadar/event/OnMonthChangeListener.java
public interface OnMonthChangeListener {
    void onMonthChanged(Calendar calendar);
}
cadar/src/main/java/io/github/memfis19/cadar/internal/ui/month/adapter/MonthAdapter.java
public class MonthAdapter extends PagerAdapter implements OnDayChangeListener,
        EventsProcessorCallback<Calendar, SparseArray<List<Event>>>, MonthHandlerThread.AdapterPrepareListener {

    private static final String TAG = "MonthAdapter";

    private static int CAPACITY = 11;
    private static int CAPACITY_STEP = 11;

    private Context context;
    private LayoutInflater inflater;
    private MonthCalendarConfiguration monthCalendarConfiguration;

    private MonthHandlerThread monthHandlerThread;
    private Handler backgroundHandler;
    private Handler uiHandler;

    private Set<Event> events = new HashSet<>();
    private List<Event> eventList = new ArrayList<>();

    private EventsProcessor<Calendar, SparseArray<List<Event>>> eventsAsyncProcessor;
    private OnDayChangeListener onDateChangeListener;
    private DisplayEventCallback<Calendar> callback;
    private MonthGridCallback monthGridCallback;

    public interface MonthGridCallback {
        void onMonthGridReady(Calendar month);
    }

    @LayoutRes
    private int monthDayLayoutId;
    private MonthDayDecoratorFactory monthDayDecoratorFactory;

    private List<RecyclerView> monthFragments = new ArrayList<>();

    private Calendar initialDate = DateUtils.getCalendarInstance();

    private static int INITIAL_POSITION = 0;

    public MonthAdapter(Context context,
                        MonthHandlerThread monthHandlerThread,
                        EventsProcessor<Calendar, SparseArray<List<Event>>> eventsAsyncProcessor,
                        MonthCalendarConfiguration monthCalendarConfiguration) {

        this(context, eventsAsyncProcessor, monthCalendarConfiguration.getMonthLayoutId(), monthCalendarConfiguration.getMonthDayDecoratorFactory());

        this.monthCalendarConfiguration = monthCalendarConfiguration;

        Calendar calendar = DateUtils.getCalendarInstance();
        calendar = DateUtils.setTimeToMidnight(calendar);

        Calendar startPeriod = DateUtils.setTimeToMonthStart((Calendar) calendar.clone());
        startPeriod.set(monthCalendarConfiguration.getPeriodType(), calendar.get(monthCalendarConfiguration.getPeriodType()) - monthCalendarConfiguration.getPeriodValue());

        Calendar endPeriod = DateUtils.setTimeToMonthStart((Calendar) calendar.clone());
        endPeriod.set(monthCalendarConfiguration.getPeriodType(), calendar.get(monthCalendarConfiguration.getPeriodType()) + monthCalendarConfiguration.getPeriodValue());

        CAPACITY = DateUtils.monthBetweenPure(startPeriod.getTime(), endPeriod.getTime());
        CAPACITY_STEP = CAPACITY;

        this.initialDate = monthCalendarConfiguration.getInitialDay();
        this.monthHandlerThread = monthHandlerThread;
        this.monthHandlerThread.setAdapterPrepareListener(this);

        backgroundHandler = new Handler(monthHandlerThread.getLooper());
        uiHandler = new Handler(Looper.getMainLooper());
    }

    MonthAdapter(Context context,
                 EventsProcessor<Calendar, SparseArray<List<Event>>> eventsAsyncProcessor,
                 @LayoutRes int monthDayLayoutId,
                 MonthDayDecoratorFactory monthDayDecoratorFactory) {
        this.context = context;
        inflater = LayoutInflater.from(context);

        this.eventsAsyncProcessor = eventsAsyncProcessor;
        this.monthDayLayoutId = monthDayLayoutId;
        this.monthDayDecoratorFactory = monthDayDecoratorFactory;

        this.eventsAsyncProcessor.setEventsProcessorCallback(this);
    }

    public void setMonthGridCallback(MonthGridCallback monthGridCallback) {
        this.monthGridCallback = monthGridCallback;
    }

    public void setOnDateChangeListener(OnDayChangeListener onDateChangeListener) {
        this.onDateChangeListener = onDateChangeListener;
    }

    public static int getInitialPosition(Calendar initialDate) {
        if (INITIAL_POSITION == 0) {
            INITIAL_POSITION = CAPACITY / 2 + 1;
        }
        return INITIAL_POSITION;
    }

    public Calendar getDayForPosition(int position) {
        Calendar month = (Calendar) initialDate.clone();
        int shiftValue = position - getInitialPosition(initialDate);
        month.add(Calendar.MONTH, shiftValue);
        return month;
    }

    @Override
    public int getItemPosition(Object object) {
        return getInitialPosition(initialDate) + (Integer) ((RecyclerView) object).getTag();
//        return getDayPosition(((MonthGridAdapter) ((RecyclerView) object).getAdapter()).getMonth());
    }

    @Override
    public int getCount() {
        return CAPACITY;
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view.equals(object);
    }

    @Override
    public Object instantiateItem(ViewGroup collection, int position) {
        RecyclerView recyclerView = (RecyclerView) inflater.inflate(R.layout.month_grid_layout, collection, false);
        monthFragments.add(recyclerView);

        int shiftValue = position - getInitialPosition(initialDate);

        recyclerView.setTag(shiftValue);
        monthHandlerThread.queuePreparation(initialDate, shiftValue, recyclerView);

        collection.addView(recyclerView);
        return recyclerView;
    }

    public int getCountOfInstantiateItems() {
        return monthFragments.size();
    }

    @Override
    public void destroyItem(ViewGroup collection, int position, Object view) {
        monthFragments.remove(view);

        collection.removeView((View) view);
    }

    public void refresh() {
        this.callback = null;
        displayEvents();
    }

    public void displayEvents(List<Event> eventList, DisplayEventCallback<Calendar> callback) {
        events.clear();
        events.addAll(eventList);

        this.eventList.clear();
        this.eventList.addAll(events);

        this.callback = callback;

        eventsAsyncProcessor.setEvents(this.eventList);
        displayEvents();
    }

    public void addEvents(List<Event> eventList) {
        events.addAll(eventList);

        this.eventList.clear();
        this.eventList.addAll(events);

        eventsAsyncProcessor.setEvents(this.eventList);
        displayEvents();
    }

    public void displayEvents() {
        for (RecyclerView recyclerView : monthFragments) {
            if (recyclerView.getAdapter() != null)
                ((MonthGridAdapter) recyclerView.getAdapter()).requestMonthEvents();
        }
    }

    public void notifyDayWasSelected() {
        for (RecyclerView recyclerView : monthFragments) {
            if (recyclerView.getAdapter() != null)
                ((MonthGridAdapter) recyclerView.getAdapter()).notifyMonthSetChanged();
        }
    }

    public int getDayPosition(Calendar day) {
        int shiftValue = DateUtils.monthBetweenPure(initialDate.getTime(), day.getTime());
        return getInitialPosition(initialDate) + shiftValue;
    }

    @Override
    public void onDayChanged(Calendar calendar) {
        notifyDayWasSelected();

        if (onDateChangeListener != null) onDateChangeListener.onDayChanged(calendar);
    }

    @Override
    public void onEventsProcessed(Calendar calendar, SparseArray<List<Event>> calendarEvents) {
        for (RecyclerView recyclerView : monthFragments) {
            if (recyclerView.getAdapter() != null)
                if (((MonthGridAdapter) recyclerView.getAdapter()).getMonth().equals(calendar)) {
                    ((MonthGridAdapter) recyclerView.getAdapter()).displayEventsForMonth(calendarEvents);
                    if (callback != null) callback.onEventsDisplayed(calendar);
                }
        }
    }

    public final static int FORWARD = 1;
    public final static int BACKWARD = 2;

    public void increaseSize(int direction) {
        if (direction == FORWARD) {
            CAPACITY += CAPACITY_STEP;

            notifyDataSetChanged();
        } else if (direction == BACKWARD) {
            CAPACITY += CAPACITY_STEP;
            INITIAL_POSITION += CAPACITY_STEP;

            notifyDataSetChanged();
        }
    }

    @Override
    public void onReadyAdapter(Calendar month, List<Calendar> monthDays, RecyclerView recyclerView) {
        MonthGridAdapter monthGridAdapter = new MonthGridAdapter(month, monthDays, monthCalendarConfiguration.isDisplayDaysOutOfMonth());

        monthGridAdapter.setOnDateChangeListener(this);
        monthGridAdapter.setEventsAsyncProcessor(eventsAsyncProcessor);
        monthGridAdapter.setMonthDayLayoutId(monthDayLayoutId);
        monthGridAdapter.setMonthDayDecoratorFactory(monthDayDecoratorFactory);

        CalendarLayoutManager gridLayoutManager = new CalendarLayoutManager(context, 7);
        recyclerView.setLayoutManager(gridLayoutManager);
        recyclerView.setAdapter(monthGridAdapter);

        if (monthGridCallback != null) monthGridCallback.onMonthGridReady(month);
//        monthGridAdapter.requestDisplayEvents();
    }

    private class CalendarLayoutManager extends GridLayoutManager {

        public CalendarLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
        }

        public CalendarLayoutManager(Context context, int spanCount) {
            super(context, spanCount);
        }

        public CalendarLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) {
            super(context, spanCount, orientation, reverseLayout);
        }

        @Override
        public boolean supportsPredictiveItemAnimations() {
            return false;
        }

        /***
         * Workaround for know RV issue
         * @link https://code.google.com/p/android/issues/detail?id=77846#c10
         */
        @Override
        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
            try {
                super.onLayoutChildren(recycler, state);
            } catch (IndexOutOfBoundsException e) {
                Log.e(MonthAdapter.TAG, "IndexOutOfBoundsException in RecyclerView happens");
            }
        }
    }
}
cadar/src/main/java/io/github/memfis19/cadar/event/CalendarPrepareCallback.java
public interface CalendarPrepareCallback {
    void onCalendarReady(CalendarController calendar);
}
cadar/src/main/java/io/github/memfis19/cadar/internal/process/EventsProcessor.java
public abstract class EventsProcessor<T, E> extends HandlerThread implements ScrollManager.OnScrollChanged {

    private static final String TAG = "EventsProcessor";

    private static final int MESSAGE_EVENTS_PROCESS = 126;

    private EventCalculator eventCalculator;
    private List<Event> eventList;
    private Queue<Pair<T, E>> resultQueue = new CircularFifoQueue<>(3);

    private boolean shouldProcess;
    private boolean processAsync = false;
    private boolean hasQuit = false;

    private Handler requestHandler;
    private Handler responseHandler = new Handler(Looper.getMainLooper());

    private EventsProcessorCallback<T, E> eventsProcessorCallback;

    private ScrollManager scrollManager;

    public EventsProcessor(boolean shouldProcess, EventCalculator eventProcessor, boolean processAsync) {
        super(String.valueOf(System.currentTimeMillis()), Process.THREAD_PRIORITY_BACKGROUND);

        this.shouldProcess = shouldProcess;
        this.eventCalculator = eventProcessor;
        this.processAsync = processAsync;
    }

    public void setScrollManager(ScrollManager scrollManager) {
        this.scrollManager = scrollManager;
        this.scrollManager.subscribeForScrollStateChanged(this);
    }

    protected EventCalculator getEventProcessor() {
        return eventCalculator;
    }

    protected List<Event> getEventList() {
        return eventList;
    }

    protected boolean isShouldProcess() {
        return shouldProcess;
    }

    public void setEventProcessor(EventCalculator eventProcessor) {
        this.eventCalculator = eventProcessor;
    }

    public void setEvents(List<Event> eventList) {
        this.eventList = eventList;
        this.eventCalculator.setEventsToProcess(eventList);
    }

    public void setEventsProcessorCallback(EventsProcessorCallback<T, E> eventsProcessorCallback) {
        this.eventsProcessorCallback = eventsProcessorCallback;
    }

    public void queueEventsProcess(T target) {
        if (target == null) return;
        requestHandler.obtainMessage(MESSAGE_EVENTS_PROCESS, target).sendToTarget();
    }

    @Override
    protected void onLooperPrepared() {
        requestHandler = new Handler(Looper.myLooper()) {
            @Override
            public void handleMessage(Message message) {
                if (message.what == MESSAGE_EVENTS_PROCESS) {
                    T target = (T) message.obj;
                    handleRequest(target);
                }
            }
        };
    }

    @Override
    public boolean quit() {
        release();
        return super.quit();
    }

    @Override
    public boolean quitSafely() {
        release();
        return (Build.VERSION.SDK_INT > 17) ? super.quitSafely() : super.quit();
    }

    private void release() {
        hasQuit = true;
        resultQueue.clear();
        if (scrollManager != null) scrollManager.unSubscribeForScrollStateChanged(this);
    }

    private void handleRequest(final T target) {
        if (processAsync) processEventsAsync(target, eventsProcessorCallback);
        else deliverResult(target, processEvents(target));
    }

    protected E processEvents(final T target) {
        return null;
    }

    protected void processEventsAsync(final T target, EventsProcessorCallback<T, E> eventsProcessorCallback) {
    }

    private void deliverResult(final T target, final E result) {
        if (scrollManager != null && scrollManager.getCurrentScrollState() == ScrollManager.SCROLL_STATE_IDLE) {
            responseHandler.post(new Runnable() {
                @Override
                public void run() {
                    if (hasQuit) return;

                    if (eventsProcessorCallback != null)
                        eventsProcessorCallback.onEventsProcessed(target, result);

                }
            });
        } else {
            resultQueue.add(new Pair<>(target, result));
        }
    }

    @Override
    public void onScrollStateChanged(@ScrollManager.ScrollState int scrollState) {
        if (scrollState == ScrollManager.SCROLL_STATE_IDLE) {
            for (final Pair<T, E> pair : resultQueue) {
                responseHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        if (hasQuit) return;

                        if (eventsProcessorCallback != null)
                            eventsProcessorCallback.onEventsProcessed(pair.first, pair.second);

                        resultQueue.remove(pair);
                    }
                });
            }
        }
    }

}
cadar/src/main/java/io/github/memfis19/cadar/event/OnDayChangeListener.java
public interface OnDayChangeListener {
    void onDayChanged(Calendar calendar);
}
cadar/src/main/java/io/github/memfis19/cadar/event/DisplayEventCallback.java
public interface DisplayEventCallback<T> {
    void onEventsDisplayed(T period);
}

Answer: private Calendar workingCalendar = DateUtils.getCalendarInstance();