List<List<MonthCellDescriptor>> getMonthCells(MonthDescriptor month, Calendar startCal) {
    Calendar cal = Calendar.getInstance(locale);
    cal.setTime(startCal.getTime());
    List<List<MonthCellDescriptor>> cells = new ArrayList<List<MonthCellDescriptor>>();
    cal.set(DAY_OF_MONTH, 1);
    int firstDayOfWeek = cal.get(DAY_OF_WEEK);
    int offset = cal.getFirstDayOfWeek() - firstDayOfWeek;
    if (offset > 0) {
      offset -= 7;
    }
    cal.add(Calendar.DATE, offset);

    Calendar minSelectedCal = minDate(selectedCals);
    Calendar maxSelectedCal = maxDate(selectedCals);

    while ((cal.get(MONTH) < month.getMonth() + 1 || cal.get(YEAR) < month.getYear()) //
        && cal.get(YEAR) <= month.getYear()) {
      Logr.d("Building week row starting at %s", cal.getTime());
      List<MonthCellDescriptor> weekCells = new ArrayList<MonthCellDescriptor>();
      cells.add(weekCells);
      for (int c = 0; c < 7; c++) {
        Date date = cal.getTime();
        boolean isCurrentMonth = cal.get(MONTH) == month.getMonth();
        boolean isSelected = isCurrentMonth && containsDate(selectedCals, cal);
        boolean isSelectable =
            isCurrentMonth && betweenDates(cal, minCal, maxCal) && isDateSelectable(date);
        boolean isToday = sameDate(cal, today);
        boolean isHighlighted = containsDate(highlightedCals, cal);
        int value = cal.get(DAY_OF_MONTH);

        MonthCellDescriptor.RangeState rangeState = MonthCellDescriptor.RangeState.NONE;
        if (selectedCals.size() > 1) {
          if (sameDate(minSelectedCal, cal)) {
            rangeState = MonthCellDescriptor.RangeState.FIRST;
          } else if (sameDate(maxDate(selectedCals), cal)) {
            rangeState = MonthCellDescriptor.RangeState.LAST;
          } else if (betweenDates(cal, minSelectedCal, maxSelectedCal)) {
            rangeState = MonthCellDescriptor.RangeState.MIDDLE;
          }
        }

        weekCells.add(
            new MonthCellDescriptor(
                date,
                isCurrentMonth,
                isSelectable,
                isSelected,
                isToday,
                isHighlighted,
                value,
                rangeState));
        cal.add(DATE, 1);
      }
    }
    return cells;
  }
 private void validateDate(Date date) {
   if (date == null) {
     throw new IllegalArgumentException("Selected date must be non-null.");
   }
   if (date.getTime() == 0) {
     throw new IllegalArgumentException("Selected date must be non-zero.  " + date);
   }
   if (date.before(minCal.getTime()) || date.after(maxCal.getTime())) {
     throw new IllegalArgumentException(
         "selectedDate must be between minDate and maxDate.  " + date);
   }
 }
  /** ----------------------- */
  public CalendarPickerView(Context context, AttributeSet attrs) {
    super(context, attrs);
    adapter = new MonthAdapter();
    setDivider(null);

    setDividerHeight(100);

    setVerticalScrollBarEnabled(false);

    final int bg = getResources().getColor(R.color.calendar_bg);
    // setBackgroundColor(bg);
    setCacheColorHint(bg);
    locale = Locale.getDefault();
    today = Calendar.getInstance(locale);
    minCal = Calendar.getInstance(locale);
    maxCal = Calendar.getInstance(locale);
    monthCounter = Calendar.getInstance(locale);
    monthNameFormat = new SimpleDateFormat(context.getString(R.string.month_name_format), locale);
    weekdayNameFormat = new SimpleDateFormat(context.getString(R.string.day_name_format), locale);
    fullDateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM, locale);

    if (isInEditMode()) {
      Calendar nextYear = Calendar.getInstance(locale);
      nextYear.add(Calendar.YEAR, 1);

      init(new Date(), nextYear.getTime()) //
          .withSelectedDate(new Date());
    }
  }
 static boolean betweenDates(Date date, Calendar minCal, Calendar maxCal) {
   final Date min = minCal.getTime();
   return (date.equals(min) || date.after(min)) // >= minCal
       && date.before(maxCal.getTime()); // && < maxCal
 }
 private static boolean betweenDates(Calendar cal, Calendar minCal, Calendar maxCal) {
   final Date date = cal.getTime();
   return betweenDates(date, minCal, maxCal);
 }
  /**
   * Both date parameters must be non-null and their {@link Date#getTime()} must not return 0. Time
   * of day will be ignored. For instance, if you pass in {@code minDate} as 11/16/2012 5:15pm and
   * {@code maxDate} as 11/16/2013 4:30am, 11/16/2012 will be the first selectable date and
   * 11/15/2013 will be the last selectable date ({@code maxDate} is exclusive).
   *
   * <p>This will implicitly set the {@link SelectionMode} to {@link SelectionMode#SINGLE}. If you
   * want a different selection mode, use {@link FluentInitializer#inMode(SelectionMode)} on the
   * {@link FluentInitializer} this method returns.
   *
   * <p>The calendar will be constructed using the given locale. This means that all names (months,
   * days) will be in the language of the locale and the weeks start with the day specified by the
   * locale.
   *
   * @param minDate Earliest selectable date, inclusive. Must be earlier than {@code maxDate}.
   * @param maxDate Latest selectable date, exclusive. Must be later than {@code minDate}.
   */
  public FluentInitializer init(Date minDate, Date maxDate, Locale locale) {
    if (minDate == null || maxDate == null) {
      throw new IllegalArgumentException(
          "minDate and maxDate must be non-null.  " + dbg(minDate, maxDate));
    }
    if (minDate.after(maxDate)) {
      throw new IllegalArgumentException(
          "minDate must be before maxDate.  " + dbg(minDate, maxDate));
    }
    if (minDate.getTime() == 0 || maxDate.getTime() == 0) {
      throw new IllegalArgumentException(
          "minDate and maxDate must be non-zero.  " + dbg(minDate, maxDate));
    }
    if (locale == null) {
      throw new IllegalArgumentException("Locale is null.");
    }

    // Make sure that all calendar instances use the same locale.
    this.locale = locale;
    today = Calendar.getInstance(locale);
    minCal = Calendar.getInstance(locale);
    maxCal = Calendar.getInstance(locale);
    monthCounter = Calendar.getInstance(locale);
    monthNameFormat =
        new SimpleDateFormat(getContext().getString(R.string.month_name_format), locale);
    for (MonthDescriptor month : months) {
      month.setLabel(monthNameFormat.format(month.getDate()));
    }
    weekdayNameFormat =
        new SimpleDateFormat(getContext().getString(R.string.day_name_format), locale);
    fullDateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM, locale);

    this.selectionMode = SelectionMode.SINGLE;
    // Clear out any previously-selected dates/cells.
    selectedCals.clear();
    selectedCells.clear();
    highlightedCells.clear();

    // Clear previous state.
    cells.clear();
    months.clear();
    minCal.setTime(minDate);
    maxCal.setTime(maxDate);
    setMidnight(minCal);
    setMidnight(maxCal);
    displayOnly = false;

    // maxDate is exclusive: bump back to the previous day so if maxDate is
    // the first of a month,
    // we don't accidentally include that month in the view.
    maxCal.add(MINUTE, -1);

    // Now iterate between minCal and maxCal and build up our list of months
    // to show.
    monthCounter.setTime(minCal.getTime());
    final int maxMonth = maxCal.get(MONTH);
    final int maxYear = maxCal.get(YEAR);
    while ((monthCounter.get(MONTH) <= maxMonth // Up to, including the
            // month.
            || monthCounter.get(YEAR) < maxYear) // Up to the year.
        && monthCounter.get(YEAR) < maxYear + 1) { // But not > next yr.
      Date date = monthCounter.getTime();
      MonthDescriptor month =
          new MonthDescriptor(
              monthCounter.get(MONTH), monthCounter.get(YEAR), date, monthNameFormat.format(date));
      cells.add(getMonthCells(month, monthCounter));
      // Logr.d("Adding month %s", month);
      months.add(month);
      monthCounter.add(MONTH, 1);
    }

    validateAndUpdate();
    return new FluentInitializer();
  }