public DateIntervalType calculateIntervalSize(
      Date minDate, Date maxDate, ColumnGroup columnGroup) {

    DateIntervalType intervalType = DateIntervalType.getByName(columnGroup.getIntervalSize());
    if (intervalType == null) {
      intervalType = YEAR;
    }

    if (minDate == null || maxDate == null) {
      return intervalType;
    }

    long millis = (maxDate.getTime() - minDate.getTime());
    if (millis <= 0) {
      return intervalType;
    }

    // Calculate the interval type used according to the constraints set.
    int maxIntervals = columnGroup.getMaxIntervals();
    if (maxIntervals < 1) maxIntervals = 15;
    for (DateIntervalType type : values()) {
      long nintervals = millis / getDurationInMillis(type);
      if (nintervals < maxIntervals) {
        intervalType = type;
        break;
      }
    }

    // Ensure the interval mode obtained is always greater or equals than the preferred interval
    // size.
    DateIntervalType intervalSize = null;
    if (!StringUtils.isBlank(columnGroup.getIntervalSize())) {
      intervalSize = getByName(columnGroup.getIntervalSize());
    }
    if (intervalSize != null && compare(intervalType, intervalSize) == -1) {
      intervalType = intervalSize;
    }
    return intervalType;
  }
  public IntervalList build(DataSetHandler handler, ColumnGroup columnGroup) {
    IntervalDateRangeList results = new IntervalDateRangeList(columnGroup);
    DataSet dataSet = handler.getDataSet();
    List values = dataSet.getColumnById(columnGroup.getSourceId()).getValues();
    if (values.isEmpty()) {
      return results;
    }

    // Sort the column dates.
    DataSetSort sortOp = new DataSetSort();
    sortOp.addSortColumn(new ColumnSort(columnGroup.getSourceId(), SortOrder.ASCENDING));
    DataSetHandler sortResults = handler.sort(sortOp);
    List<Integer> sortedRows = sortResults.getRows();
    if (sortedRows == null || sortedRows.isEmpty()) {
      return results;
    }

    // Get the lower & upper limits (discard nulls).
    SortedList sortedValues = new SortedList(values, sortedRows);
    Date minDate = null;
    Date maxDate = null;
    for (int i = 0; minDate == null && i < sortedValues.size(); i++) {
      minDate = (Date) sortedValues.get(i);
    }
    for (int i = sortedValues.size() - 1; maxDate == null && i >= 0; i--) {
      maxDate = (Date) sortedValues.get(i);
    }

    // If min/max are equals then return a single interval.
    DateIntervalType intervalType = calculateIntervalSize(minDate, maxDate, columnGroup);
    if (minDate == null || minDate.compareTo(maxDate) == 0) {

      IntervalDateRange interval = new IntervalDateRange(0, intervalType, minDate, maxDate);
      for (int row = 0; row < sortedValues.size(); row++) interval.getRows().add(row);

      results.add(interval);
      results.setIntervalType(columnGroup.getIntervalSize());
      results.setMinValue(minDate);
      results.setMaxValue(maxDate);
      return results;
    }

    // Create the intervals according to the min/max dates.
    Calendar c = firstIntervalDate(intervalType, minDate, columnGroup);
    int index = 0;
    int counter = 0;
    while (c.getTime().compareTo(maxDate) <= 0) {
      Date intervalMinDate = c.getTime();

      // Create the next interval
      nextIntervalDate(c, intervalType, 1);
      Date intervalMaxDate = c.getTime();
      IntervalDateRange interval =
          new IntervalDateRange(counter++, intervalType, intervalMinDate, intervalMaxDate);
      results.add(interval);

      // Add the target rows
      boolean stop = false;
      while (!stop) {
        if (index >= sortedValues.size()) {
          stop = true;
        } else {
          Date dateValue = (Date) sortedValues.get(index);
          Integer row = sortedRows.get(index);
          if (dateValue == null) {
            index++;
          } else if (dateValue.before(intervalMaxDate)) {
            interval.getRows().add(row);
            index++;
          } else {
            stop = true;
          }
        }
      }
    }

    // Reverse intervals if requested
    boolean asc = columnGroup.isAscendingOrder();
    if (!asc) Collections.reverse(results);

    // Return the results
    results.setIntervalType(intervalType.toString());
    results.setMinValue(minDate);
    results.setMaxValue(maxDate);
    return results;
  }