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;
  }
  /** Return cell and month-index (for scrolling) for a given Date. */
  private MonthCellWithMonthIndex getMonthCellWithIndexByDate(Date date) {
    int index = 0;
    Calendar searchCal = Calendar.getInstance(locale);
    searchCal.setTime(date);
    Calendar actCal = Calendar.getInstance(locale);

    for (List<List<MonthCellDescriptor>> monthCells : cells) {
      for (List<MonthCellDescriptor> weekCells : monthCells) {
        for (MonthCellDescriptor actCell : weekCells) {
          actCal.setTime(actCell.getDate());
          if (sameDate(actCal, searchCal) && actCell.isSelectable()) {
            return new MonthCellWithMonthIndex(actCell, index);
          }
        }
      }
      index++;
    }
    return null;
  }
  public void highlightDates(Collection<Date> dates) {
    for (Date date : dates) {
      validateDate(date);

      MonthCellWithMonthIndex monthCellWithMonthIndex = getMonthCellWithIndexByDate(date);
      if (monthCellWithMonthIndex != null) {
        Calendar newlyHighlightedCal = Calendar.getInstance();
        newlyHighlightedCal.setTime(date);
        MonthCellDescriptor cell = monthCellWithMonthIndex.cell;

        highlightedCells.add(cell);
        highlightedCals.add(newlyHighlightedCal);
        cell.setHighlighted(true);
      }
    }

    adapter.notifyDataSetChanged();
    setAdapter(adapter);
  }
  private boolean doSelectDate(Date date, MonthCellDescriptor cell) {
    Calendar newlySelectedCal = Calendar.getInstance(locale);
    newlySelectedCal.setTime(date);
    // Sanitize input: clear out the hours/minutes/seconds/millis.
    setMidnight(newlySelectedCal);

    // Clear any remaining range state.
    for (MonthCellDescriptor selectedCell : selectedCells) {
      selectedCell.setRangeState(RangeState.NONE);
    }

    switch (selectionMode) {
      case RANGE:
        if (selectedCals.size() > 1) {
          // We've already got a range selected: clear the old one.
          clearOldSelections();
        } else if (selectedCals.size() == 1 && newlySelectedCal.before(selectedCals.get(0))) {
          // We're moving the start of the range back in time: clear the
          // old start date.
          clearOldSelections();
        }
        break;

      case MULTIPLE:
        date = applyMultiSelect(date, newlySelectedCal);
        break;

      case SINGLE:
        clearOldSelections();
        break;
      default:
        throw new IllegalStateException("Unknown selectionMode " + selectionMode);
    }

    if (date != null) {
      // Select a new cell.
      if (selectedCells.size() == 0 || !selectedCells.get(0).equals(cell)) {
        selectedCells.add(cell);
        cell.setSelected(true);
      }
      selectedCals.add(newlySelectedCal);

      if (selectionMode == SelectionMode.RANGE && selectedCells.size() > 1) {
        // Select all days in between start and end.
        Date start = selectedCells.get(0).getDate();
        Date end = selectedCells.get(1).getDate();
        selectedCells.get(0).setRangeState(MonthCellDescriptor.RangeState.FIRST);
        selectedCells.get(1).setRangeState(MonthCellDescriptor.RangeState.LAST);

        for (List<List<MonthCellDescriptor>> month : cells) {
          for (List<MonthCellDescriptor> week : month) {
            for (MonthCellDescriptor singleCell : week) {
              if (singleCell.getDate().after(start)
                  && singleCell.getDate().before(end)
                  && singleCell.isSelectable()) {
                singleCell.setSelected(true);
                singleCell.setRangeState(MonthCellDescriptor.RangeState.MIDDLE);
                selectedCells.add(singleCell);
              }
            }
          }
        }
      }
    }

    // Update the adapter.
    validateAndUpdate();
    return date != null;
  }
  /**
   * 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();
  }