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());
    }
  }
  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;
  }
 @Override
 public void onClick(View v) {
   if (v == mTvLeftMonth || v == mTvRightMonth) {
     if (v == mTvLeftMonth) {
       mDisplayMonth.add(MONTH, -1);
     } else {
       mDisplayMonth.add(MONTH, 1);
     }
     setDate(mDisplayMonth.getTime(), false);
   }
 }
 @Override
 public void handleClick(MonthCellDescriptor cell) {
   if (!cell.isSelected()) {
     clearAllOldSelections();
     mSelectedCellList.add(cell);
     cell.setSelected(true);
     Calendar newlySelectedCal = Calendar.getInstance(Locale.getDefault());
     newlySelectedCal.setTime(cell.getDate());
     mSelectedCalendarList.add(newlySelectedCal);
     flushView();
   }
 }
 private void initConstruct(Context context) {
   mTodayCalendar = Calendar.getInstance(Locale.getDefault());
   setListener(mListener);
   mTvLeftMonth = (TextView) mTitleView.findViewById(R.id.tv_left_month);
   mTvRightMonth = (TextView) mTitleView.findViewById(R.id.tv_right_month);
   mTvLeftMonth.setOnClickListener(mOnClickListener);
   mTvRightMonth.setOnClickListener(mOnClickListener);
 }
  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 void setDate(Date date, boolean selected) {
    if (selected) {
      clearAllOldSelections();
    } else {
      clearOldCellSelections();
    }
    mDisplayMonth = Calendar.getInstance();
    mDisplayMonth.setTime(date);
    if (selected) {
      mSelectedCalendarList.add(mDisplayMonth);
    }

    Locale locale = Locale.getDefault();
    SimpleDateFormat monthNameFormat =
        new SimpleDateFormat(getContext().getString(R.string.month_name_format), locale);
    MonthDescriptor month =
        new MonthDescriptor(
            mDisplayMonth.get(MONTH), mDisplayMonth.get(YEAR), date, monthNameFormat.format(date));
    final List<List<MonthCellDescriptor>> monthCells =
        getMonthCells(month, mDisplayMonth.getTime());
    initData(month, monthCells, false, null, null);
  }
Exemple #9
0
 public static void main(String[] args) {
   Calendar cal = Calendar.getInstance();
   // 结果是YEAR字段加1,MONTH字段为1(二月)
   cal.set(MONTH, 13); // ①
   System.out.println(cal.getTime());
   // 关闭容错性
   cal.setLenient(false);
   // 导致运行时异常
   cal.set(MONTH, 13); // ②
   System.out.println(cal.getTime());
 }
 private void scrollToSelectedDates() {
   Integer selectedIndex = null;
   Integer todayIndex = null;
   Calendar today = Calendar.getInstance(locale);
   for (int c = 0; c < months.size(); c++) {
     MonthDescriptor month = months.get(c);
     if (selectedIndex == null) {
       for (Calendar selectedCal : selectedCals) {
         if (sameMonth(selectedCal, month)) {
           selectedIndex = c;
           break;
         }
       }
       if (selectedIndex == null && todayIndex == null && sameMonth(today, month)) {
         todayIndex = c;
       }
     }
   }
   if (selectedIndex != null) {
     scrollToSelectedMonth(selectedIndex);
   } else if (todayIndex != null) {
     scrollToSelectedMonth(todayIndex);
   }
 }
  /** 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;
  }
  /*
   * (non-Javadoc)
   *
   * @see net.cockamamy.fauxflix.util.csv.AbstractPropertyConverterTest#
   * provideConvertValueData()
   */
  @Override
  protected Object[][] buildConvertValueSuccessData() {

    Object[][] theData = new Object[3][2];

    Calendar aCalendar = Calendar.getInstance();
    aCalendar.set(2008, 0, 1, 0, 0);
    aCalendar.set(SECOND, 0);
    aCalendar.set(MILLISECOND, 0);

    theData[0][0] = "01/01/2008";
    theData[0][1] = aCalendar.getTime();

    theData[1][0] = null;
    theData[1][1] = null;

    theData[2][0] = "";
    theData[2][1] = null;

    return theData;
  }
 private static boolean sameDate(Calendar cal, Calendar selectedDate) {
   return cal.get(MONTH) == selectedDate.get(MONTH)
       && cal.get(YEAR) == selectedDate.get(YEAR)
       && cal.get(DAY_OF_MONTH) == selectedDate.get(DAY_OF_MONTH);
 }
 /** Clears out the hours/minutes/seconds/millis of a Calendar. */
 static void setMidnight(Calendar cal) {
   cal.set(HOUR_OF_DAY, 0);
   cal.set(MINUTE, 0);
   cal.set(SECOND, 0);
   cal.set(MILLISECOND, 0);
 }
  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;
  }
  List<List<MonthCellDescriptor>> getMonthCells(MonthDescriptor month, Date startDate) {
    Calendar cal = Calendar.getInstance(Locale.getDefault());
    cal.setTime(startDate);
    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);

    while ((cal.get(MONTH) < month.getMonth() + 1 || cal.get(YEAR) < month.getYear())
        && cal.get(YEAR) <= month.getYear()) {
      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 && CalendarPickerView.containsDate(mSelectedCalendarList, cal);
        boolean isSelectable = true;
        boolean isToday = CalendarPickerView.sameDate(cal, mTodayCalendar);
        boolean isHighlighted = false;
        int value = cal.get(DAY_OF_MONTH);
        if (isToday) {}
        MonthCellDescriptor.RangeState rangeState = MonthCellDescriptor.RangeState.NONE;

        final MonthCellDescriptor cellDescriptor =
            new MonthCellDescriptor(
                date,
                isCurrentMonth,
                isSelectable,
                isSelected,
                isToday,
                isHighlighted,
                value,
                rangeState);
        weekCells.add(cellDescriptor);
        if (isSelected) {
          mSelectedCellList.add(cellDescriptor);
        }
        cal.add(DATE, 1);
      }
    }
    return cells;
  }
 private static boolean sameMonth(Calendar cal, MonthDescriptor month) {
   return (cal.get(MONTH) == month.getMonth() && cal.get(YEAR) == month.getYear());
 }
 private static boolean betweenDates(Calendar cal, Calendar minCal, Calendar maxCal) {
   final Date date = cal.getTime();
   return betweenDates(date, minCal, maxCal);
 }
 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
 }
  public static void test() {
    TimeZone.setDefault(TimeZone.getTimeZone("GMT-0800"));

    // Any characters not explicitly defined as conversions, date/time
    // conversion suffixes, or flags are illegal and are reserved for
    // future extensions.  Use of such a character in a format string will
    // cause an UnknownFormatConversionException or
    // UnknownFormatFlagsException to be thrown.
    tryCatch("%q", UnknownFormatConversionException.class);
    tryCatch("%t&", UnknownFormatConversionException.class);
    tryCatch("%&d", UnknownFormatConversionException.class);
    tryCatch("%^b", UnknownFormatConversionException.class);

    // ---------------------------------------------------------------------
    // Formatter.java class javadoc examples
    // ---------------------------------------------------------------------
    test(Locale.FRANCE, "e = %+10.4f", "e =    +2,7183", Math.E);
    test("%4$2s %3$2s %2$2s %1$2s", " d  c  b  a", "a", "b", "c", "d");
    test(
        "Amount gained or lost since last statement: $ %,(.2f",
        "Amount gained or lost since last statement: $ (6,217.58)", (new BigDecimal("-6217.58")));
    Calendar c = new GregorianCalendar(1969, JULY, 20, 16, 17, 0);
    testSysOut("Local time: %tT", "Local time: 16:17:00", c);

    test(
        "Unable to open file '%1$s': %2$s",
        "Unable to open file 'food': No such file or directory",
        "food",
        "No such file or directory");
    Calendar duke = new GregorianCalendar(1995, MAY, 23, 19, 48, 34);
    duke.set(Calendar.MILLISECOND, 584);
    test("Duke's Birthday: %1$tB %1$te, %1$tY", "Duke's Birthday: May 23, 1995", duke);
    test("Duke's Birthday: %1$tB %1$te, %1$tY", "Duke's Birthday: May 23, 1995", duke.getTime());
    test(
        "Duke's Birthday: %1$tB %1$te, %1$tY",
        "Duke's Birthday: May 23, 1995", duke.getTimeInMillis());

    test("%4$s %3$s %2$s %1$s %4$s %3$s %2$s %1$s", "d c b a d c b a", "a", "b", "c", "d");
    test("%s %s %<s %<s", "a b b b", "a", "b", "c", "d");
    test("%s %s %s %s", "a b c d", "a", "b", "c", "d");
    test("%2$s %s %<s %s", "b a a b", "a", "b", "c", "d");

    // ---------------------------------------------------------------------
    // %b
    //
    // General conversion applicable to any argument.
    // ---------------------------------------------------------------------
    test("%b", "true", true);
    test("%b", "false", false);
    test("%B", "TRUE", true);
    test("%B", "FALSE", false);
    test("%b", "true", Boolean.TRUE);
    test("%b", "false", Boolean.FALSE);
    test("%B", "TRUE", Boolean.TRUE);
    test("%B", "FALSE", Boolean.FALSE);
    test("%14b", "          true", true);
    test("%-14b", "true          ", true);
    test("%5.1b", "    f", false);
    test("%-5.1b", "f    ", false);

    test("%b", "true", "foo");
    test("%b", "false", (Object) null);

    // Boolean.java hardcodes the Strings for "true" and "false", so no
    // localization is possible.
    test(Locale.FRANCE, "%b", "true", true);
    test(Locale.FRANCE, "%b", "false", false);

    // If you pass in a single array to a varargs method, the compiler
    // uses it as the array of arguments rather than treating it as a
    // single array-type argument.
    test("%b", "false", (Object[]) new String[2]);
    test("%b", "true", new String[2], new String[2]);

    int[] ia = {1, 2, 3};
    test("%b", "true", ia);

    // ---------------------------------------------------------------------
    // %b - errors
    // ---------------------------------------------------------------------
    tryCatch("%#b", FormatFlagsConversionMismatchException.class);
    tryCatch("%-b", MissingFormatWidthException.class);
    // correct or side-effect of implementation?
    tryCatch("%.b", UnknownFormatConversionException.class);
    tryCatch("%,b", FormatFlagsConversionMismatchException.class);

    // ---------------------------------------------------------------------
    // %c
    //
    // General conversion applicable to any argument.
    // ---------------------------------------------------------------------
    test("%c", "i", 'i');
    test("%C", "I", 'i');
    test("%4c", "   i", 'i');
    test("%-4c", "i   ", 'i');
    test("%4C", "   I", 'i');
    test("%-4C", "I   ", 'i');
    test("%c", "i", new Character('i'));
    test("%c", "H", (byte) 72);
    test("%c", "i", (short) 105);
    test("%c", "!", (int) 33);
    test("%c", "\u007F", Byte.MAX_VALUE);
    test("%c", new String(Character.toChars(Short.MAX_VALUE)), Short.MAX_VALUE);
    test("%c", "null", (Object) null);

    // ---------------------------------------------------------------------
    // %c - errors
    // ---------------------------------------------------------------------
    tryCatch("%c", IllegalFormatConversionException.class, Boolean.TRUE);
    tryCatch("%c", IllegalFormatConversionException.class, (float) 0.1);
    tryCatch("%c", IllegalFormatConversionException.class, new Object());
    tryCatch("%c", IllegalFormatCodePointException.class, Byte.MIN_VALUE);
    tryCatch("%c", IllegalFormatCodePointException.class, Short.MIN_VALUE);
    tryCatch("%c", IllegalFormatCodePointException.class, Integer.MIN_VALUE);
    tryCatch("%c", IllegalFormatCodePointException.class, Integer.MAX_VALUE);

    tryCatch("%#c", FormatFlagsConversionMismatchException.class);
    tryCatch("%,c", FormatFlagsConversionMismatchException.class);
    tryCatch("%(c", FormatFlagsConversionMismatchException.class);
    tryCatch("%$c", UnknownFormatConversionException.class);
    tryCatch("%.2c", IllegalFormatPrecisionException.class);

    // ---------------------------------------------------------------------
    // %s
    //
    // General conversion applicable to any argument.
    // ---------------------------------------------------------------------
    test("%s", "Hello, Duke", "Hello, Duke");
    test("%S", "HELLO, DUKE", "Hello, Duke");
    test("%20S", "         HELLO, DUKE", "Hello, Duke");
    test("%20s", "         Hello, Duke", "Hello, Duke");
    test("%-20s", "Hello, Duke         ", "Hello, Duke");
    test("%-20.5s", "Hello               ", "Hello, Duke");
    test("%s", "null", (Object) null);

    StringBuffer sb = new StringBuffer("foo bar");
    test("%s", sb.toString(), sb);
    test("%S", sb.toString().toUpperCase(), sb);

    // ---------------------------------------------------------------------
    // %s - errors
    // ---------------------------------------------------------------------
    tryCatch("%-s", MissingFormatWidthException.class);
    tryCatch("%--s", DuplicateFormatFlagsException.class);
    tryCatch("%#s", FormatFlagsConversionMismatchException.class, 0);
    tryCatch("%#s", FormatFlagsConversionMismatchException.class, 0.5f);
    tryCatch("%#s", FormatFlagsConversionMismatchException.class, "hello");
    tryCatch("%#s", FormatFlagsConversionMismatchException.class, null);

    // ---------------------------------------------------------------------
    // %h
    //
    // General conversion applicable to any argument.
    // ---------------------------------------------------------------------
    test("%h", Integer.toHexString("Hello, Duke".hashCode()), "Hello, Duke");
    test("%10h", "  ddf63471", "Hello, Duke");
    test("%-10h", "ddf63471  ", "Hello, Duke");
    test("%-10H", "DDF63471  ", "Hello, Duke");
    test("%10h", "  402e0000", 15.0);
    test("%10H", "  402E0000", 15.0);

    // ---------------------------------------------------------------------
    // %h - errors
    // ---------------------------------------------------------------------
    tryCatch("%#h", FormatFlagsConversionMismatchException.class);

    // ---------------------------------------------------------------------
    // flag/conversion errors
    // ---------------------------------------------------------------------
    tryCatch("%F", UnknownFormatConversionException.class);

    tryCatch("%#g", FormatFlagsConversionMismatchException.class);

    // ---------------------------------------------------------------------
    // BigInteger - errors
    // ---------------------------------------------------------------------
    tryCatch("%f", IllegalFormatConversionException.class, new BigInteger("1"));

    // ---------------------------------------------------------------------
    // %d - BigInteger
    // ---------------------------------------------------------------------
    test("%d", "null", (Object) null);
    test("%d", "1234567", new BigInteger("1234567", 10));
    test("%,d", "1,234,567", new BigInteger("1234567", 10));
    test(Locale.FRANCE, "%,d", "1\u00a0234\u00a0567", new BigInteger("1234567", 10));
    test("%,d", "-1,234,567", new BigInteger("-1234567", 10));
    test("%(d", "1234567", new BigInteger("1234567", 10));
    test("%(d", "(1234567)", new BigInteger("-1234567", 10));
    test("% d", " 1234567", new BigInteger("1234567", 10));
    test("% d", "-1234567", new BigInteger("-1234567", 10));
    test("%+d", "+1234567", new BigInteger("1234567", 10));
    test("%+d", "-1234567", new BigInteger("-1234567", 10));
    test("%010d", "0001234567", new BigInteger("1234567", 10));
    test("%010d", "-001234567", new BigInteger("-1234567", 10));
    test("%(10d", " (1234567)", new BigInteger("-1234567", 10));
    test("%+d", "+1234567", new BigInteger("1234567", 10));
    test("%+d", "-1234567", new BigInteger("-1234567", 10));
    test("%-10d", "1234567   ", new BigInteger("1234567", 10));
    test("%-10d", "-1234567  ", new BigInteger("-1234567", 10));

    // ---------------------------------------------------------------------
    // %o - BigInteger
    // ---------------------------------------------------------------------
    test("%o", "null", (Object) null);
    test("%o", "1234567", new BigInteger("1234567", 8));
    test("%(o", "1234567", new BigInteger("1234567", 8));
    test("%(o", "(1234567)", new BigInteger("-1234567", 8));
    test("% o", " 1234567", new BigInteger("1234567", 8));
    test("% o", "-1234567", new BigInteger("-1234567", 8));
    test("%+o", "+1234567", new BigInteger("1234567", 8));
    test("%+o", "-1234567", new BigInteger("-1234567", 8));
    test("%010o", "0001234567", new BigInteger("1234567", 8));
    test("%010o", "-001234567", new BigInteger("-1234567", 8));
    test("%(10o", " (1234567)", new BigInteger("-1234567", 8));
    test("%+o", "+1234567", new BigInteger("1234567", 8));
    test("%+o", "-1234567", new BigInteger("-1234567", 8));
    test("%-10o", "1234567   ", new BigInteger("1234567", 8));
    test("%-10o", "-1234567  ", new BigInteger("-1234567", 8));
    test("%#10o", "  01234567", new BigInteger("1234567", 8));
    test("%#10o", " -01234567", new BigInteger("-1234567", 8));

    // ---------------------------------------------------------------------
    // %x - BigInteger
    // ---------------------------------------------------------------------
    test("%x", "null", (Object) null);
    test("%x", "1234567", new BigInteger("1234567", 16));
    test("%(x", "1234567", new BigInteger("1234567", 16));
    test("%(x", "(1234567)", new BigInteger("-1234567", 16));
    test("% x", " 1234567", new BigInteger("1234567", 16));
    test("% x", "-1234567", new BigInteger("-1234567", 16));
    test("%+x", "+1234567", new BigInteger("1234567", 16));
    test("%+x", "-1234567", new BigInteger("-1234567", 16));
    test("%010x", "0001234567", new BigInteger("1234567", 16));
    test("%010x", "-001234567", new BigInteger("-1234567", 16));
    test("%(10x", " (1234567)", new BigInteger("-1234567", 16));
    test("%+x", "+1234567", new BigInteger("1234567", 16));
    test("%+x", "-1234567", new BigInteger("-1234567", 16));
    test("%-10x", "1234567   ", new BigInteger("1234567", 16));
    test("%-10x", "-1234567  ", new BigInteger("-1234567", 16));
    test("%#10x", " 0x1234567", new BigInteger("1234567", 16));
    test("%#10x", "-0x1234567", new BigInteger("-1234567", 16));
    test("%#10X", " 0X1234567", new BigInteger("1234567", 16));
    test("%#10X", "-0X1234567", new BigInteger("-1234567", 16));
    test("%X", "1234567A", new BigInteger("1234567a", 16));
    test("%X", "-1234567A", new BigInteger("-1234567a", 16));

    // ---------------------------------------------------------------------
    // %t
    //
    // Date/Time conversions applicable to Calendar, Date, and long.
    // ---------------------------------------------------------------------
    test("%tA", "null", (Object) null);
    test("%TA", "NULL", (Object) null);

    // ---------------------------------------------------------------------
    // %t - errors
    // ---------------------------------------------------------------------
    tryCatch("%t", UnknownFormatConversionException.class);
    tryCatch("%T", UnknownFormatConversionException.class);
    tryCatch("%tP", UnknownFormatConversionException.class);
    tryCatch("%TP", UnknownFormatConversionException.class);
    tryCatch("%.5tB", IllegalFormatPrecisionException.class);
    tryCatch("%#tB", FormatFlagsConversionMismatchException.class);
    tryCatch("%-tB", MissingFormatWidthException.class);

    // ---------------------------------------------------------------------
    // %n
    // ---------------------------------------------------------------------
    test("%n", System.getProperty("line.separator"), (Object) null);
    test("%n", System.getProperty("line.separator"), "");

    tryCatch("%,n", IllegalFormatFlagsException.class);
    tryCatch("%.n", UnknownFormatConversionException.class);
    tryCatch("%5.n", UnknownFormatConversionException.class);
    tryCatch("%5n", IllegalFormatWidthException.class);
    tryCatch("%.7n", IllegalFormatPrecisionException.class);
    tryCatch("%<n", IllegalFormatFlagsException.class);

    // ---------------------------------------------------------------------
    // %%
    // ---------------------------------------------------------------------
    test("%%", "%", (Object) null);
    test("%%", "%", "");
    tryCatch("%%%", UnknownFormatConversionException.class);
    // perhaps an IllegalFormatArgumentIndexException should be defined?
    tryCatch("%<%", IllegalFormatFlagsException.class);
  }
  /**
   * 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();
  }