android-templates/src/main/java/ru/touchin/templates/calendar/CalendarUtils.java

272 lines
12 KiB
Java

/*
* Copyright (c) 2016 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
*
* This file is part of RoboSwag library.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package ru.touchin.templates.calendar;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.joda.time.DateTime;
import org.joda.time.DateTimeFieldType;
import org.joda.time.Days;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import ru.touchin.roboswag.core.log.Lc;
/**
* Created by Ilia Kurtov on 17/03/2016.
* Utility class to simplify working with {@link CalendarAdapter}.
*/
public final class CalendarUtils {
private static final int DAYS_IN_WEEK = 7;
private static final long ONE_DAY = TimeUnit.DAYS.toMillis(1);
/**
* Method finds CalendarItem for specified position. Find process is optimized and use binary search algorithm.
*
* @param calendarItems List of {@link CalendarItem} where need to find specific element;
* @param position Position of adapter;
* @return CalendarItem for specified position.
*/
@Nullable
public static CalendarItem findItemByPosition(@NonNull final List<CalendarItem> calendarItems, final long position) {
return find(calendarItems, position, false);
}
/**
* Method finds position of Header that respond to requested position.
*
* @param calendarItems List of {@link CalendarItem} where need to find specific element;
* @param position Position of adapter;
* @return Position of Header that respond to requested position.
* Returns null if Header or related CalendarItem was not found for specified position.
*/
@Nullable
public static Integer findPositionOfSelectedMonth(@NonNull final List<CalendarItem> calendarItems, final long position) {
final CalendarItem calendarItem = find(calendarItems, position, true);
if (calendarItem != null) {
return calendarItem.getStartRange();
}
return null;
}
/**
* Method finds position of calendar cell that respond to specified date.
*
* @param calendarItems List of {@link CalendarItem} where need to find specific element;
* @param date Requested date in milliseconds.
* @return Position of Calendar cell that that has specific date.
* Returns null if CalendarItem was not found for specified position.
*/
@Nullable
public static Integer findPositionByDate(@NonNull final List<CalendarItem> calendarItems, final long date) {
int low = 0;
int high = calendarItems.size() - 1;
int addition = 0;
float count = 0;
while (true) {
final int mid = (low + high) / 2 + addition;
if (calendarItems.get(mid) instanceof CalendarDayItem) {
if (date < ((CalendarDayItem) calendarItems.get(mid)).getDateOfFirstDay()) {
if (mid == 0) {
Lc.assertion("Selected date smaller then min date in calendar");
return null;
}
high = mid - 1;
} else {
final long endDate = ((CalendarDayItem) calendarItems.get(mid)).getDateOfFirstDay()
+ (calendarItems.get(mid).getEndRange() - calendarItems.get(mid).getStartRange()) * ONE_DAY;
if (date > endDate) {
if (mid == calendarItems.size()) {
Lc.assertion("Selected date bigger then max date in calendar");
return null;
}
low = mid + 1;
} else {
return (int) (calendarItems.get(mid).getStartRange()
+ (date - (((CalendarDayItem) calendarItems.get(mid)).getDateOfFirstDay())) / ONE_DAY);
}
}
count = 0;
addition = 0;
} else {
count++;
addition = ((int) Math.ceil(count / 2)) * ((int) (StrictMath.pow(-1, (count - 1))));
}
}
}
/**
* Create list of {@link CalendarItem} according to start and end Dates.
*
* @param startDate Start date of the range;
* @param endDate End date of the range;
* @return List of CalendarItems that could be one of these: {@link CalendarHeaderItem}, {@link CalendarDayItem} or {@link CalendarEmptyItem}.
*/
@NonNull
@SuppressWarnings("checkstyle:MethodLength")
public static List<CalendarItem> fillRanges(@NonNull final DateTime startDate, @NonNull final DateTime endDate) {
final DateTime cleanStartDate = startDate.withTimeAtStartOfDay();
final DateTime cleanEndDate = endDate.plusDays(1).withTimeAtStartOfDay();
DateTime tempTime = cleanStartDate;
final List<CalendarItem> calendarItems = fillCalendarTillCurrentDate(cleanStartDate, tempTime);
tempTime = tempTime.plusDays(Days.ONE.getDays());
final int totalDaysCount = Days.daysBetween(tempTime, cleanEndDate).getDays();
int shift = calendarItems.get(calendarItems.size() - 1).getEndRange();
int firstDate = tempTime.getDayOfMonth() - 1;
int daysEnded = 1;
while (true) {
final int daysInCurrentMonth = tempTime.dayOfMonth().getMaximumValue();
final long firstRangeDate = tempTime.getMillis();
if ((daysEnded + (daysInCurrentMonth - firstDate)) <= totalDaysCount) {
tempTime = tempTime.plusMonths(1).withDayOfMonth(1);
calendarItems.add(new CalendarDayItem(firstRangeDate, firstDate + 1, shift + daysEnded,
shift + daysEnded + (daysInCurrentMonth - firstDate) - 1, ComparingToToday.AFTER_TODAY));
daysEnded += daysInCurrentMonth - firstDate;
if (daysEnded == totalDaysCount) {
break;
}
firstDate = 0;
final int firstDayInWeek = tempTime.getDayOfWeek() - 1;
if (firstDayInWeek != 0) {
calendarItems.add(new CalendarEmptyItem(shift + daysEnded, shift + daysEnded + (DAYS_IN_WEEK - firstDayInWeek - 1)));
shift += (DAYS_IN_WEEK - firstDayInWeek);
}
calendarItems.add(new CalendarHeaderItem(tempTime.getMonthOfYear() - 1, shift + daysEnded, shift + daysEnded));
shift += 1;
if (firstDayInWeek != 0) {
calendarItems.add(new CalendarEmptyItem(shift + daysEnded, shift + daysEnded + firstDayInWeek - 1));
shift += firstDayInWeek;
}
} else {
calendarItems.add(new CalendarDayItem(firstRangeDate, firstDate + 1, shift + daysEnded, shift + totalDaysCount,
ComparingToToday.AFTER_TODAY));
break;
}
}
return calendarItems;
}
@Nullable
@SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.ModifiedCyclomaticComplexity", "PMD.StdCyclomaticComplexity"})
private static CalendarItem find(@NonNull final List<CalendarItem> calendarItems, final long position, final boolean getHeaderPosition) {
int low = 0;
int high = calendarItems.size() - 1;
while (true) {
final int mid = (low + high) / 2;
if (position < calendarItems.get(mid).getStartRange()) {
if (mid == 0 || position > calendarItems.get(mid - 1).getEndRange()) {
Lc.assertion("CalendarAdapter cannot find item with that position");
return null;
}
high = mid - 1;
} else if (position > calendarItems.get(mid).getEndRange()) {
if (mid == calendarItems.size() || position < calendarItems.get(mid + 1).getStartRange()) {
Lc.assertion("CalendarAdapter cannot find item with that position");
return null;
}
low = mid + 1;
} else {
if (getHeaderPosition) {
int calendarShift = mid;
while (true) {
calendarShift--;
if (calendarShift == -1) {
return null;
}
if (calendarItems.get(calendarShift) instanceof CalendarHeaderItem) {
return calendarItems.get(calendarShift);
}
}
}
return calendarItems.get(mid);
}
}
}
@NonNull
private static List<CalendarItem> fillCalendarTillCurrentDate(@NonNull final DateTime cleanStartDate, @NonNull final DateTime startDate) {
DateTime temp = startDate;
final List<CalendarItem> calendarItems = new ArrayList<>();
int shift = 0;
final int firstDate = temp.getDayOfMonth() - 1; //?? - 1 ?
// add first month header
calendarItems.add(new CalendarHeaderItem(temp.get(DateTimeFieldType.monthOfYear()) - 1, shift, shift)); // is Month starts from 1 or 0 ?
temp = temp.withDayOfMonth(1);
shift += 1;
final int firstDayInTheWeek = temp.getDayOfWeek() - 1;
// check if first day is Monday. If not - add empty items. Otherwise do nothing
if (firstDayInTheWeek != 0) {
calendarItems.add(new CalendarEmptyItem(shift, shift + firstDayInTheWeek - 1));
}
shift += firstDayInTheWeek;
// add range with days before today
calendarItems.add(new CalendarDayItem(temp.getMillis(), 1, shift, shift + firstDate - 1, ComparingToToday.BEFORE_TODAY));
shift += firstDate;
// add today item
temp = cleanStartDate;
calendarItems.add(new CalendarDayItem(temp.getMillis(), firstDate + 1, shift, shift, ComparingToToday.TODAY));
//add empty items and header if current day the last day in the month
if (temp.getDayOfMonth() == temp.dayOfMonth().getMaximumValue()) {
addItemsIfCurrentDayTheLastDayInTheMonth(startDate, calendarItems);
}
return calendarItems;
}
private static void addItemsIfCurrentDayTheLastDayInTheMonth(@NonNull final DateTime dateTime,
@NonNull final List<CalendarItem> calendarItems) {
int shift = calendarItems.get(calendarItems.size() - 1).getEndRange();
final DateTime nextMonthFirstDay = dateTime.plusDays(1);
final int firstFayInNextMonth = nextMonthFirstDay.getDayOfWeek() - 1;
calendarItems.add(new CalendarEmptyItem(shift, shift + (7 - firstFayInNextMonth) - 1));
shift += 7 - firstFayInNextMonth;
calendarItems.add(new CalendarHeaderItem(nextMonthFirstDay.getMonthOfYear() + 1, shift, shift));
shift += 1;
calendarItems.add(new CalendarEmptyItem(shift, shift + firstFayInNextMonth - 1));
}
private CalendarUtils() {
}
}