/*
This file is part of Cyclos (www.cyclos.org).
A project of the Social Trade Organisation (www.socialtrade.org).
Cyclos is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
Cyclos is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Cyclos; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package nl.strohalm.cyclos.utils;
import java.math.BigDecimal;
import java.math.MathContext;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import nl.strohalm.cyclos.entities.reports.ThroughTimeRange;
import nl.strohalm.cyclos.entities.settings.LocalSettings;
import org.apache.commons.lang.time.DateUtils;
/**
* Helper class for dates
* @author luis
*/
public class DateHelper {
/**
* Returns the number of days between 2 dates. Will be negative if date2 < date1, 0 if both are the same day or positive otherwise
*/
public static int daysBetween(Calendar date1, Calendar date2) {
if (date1 == null || date2 == null) {
return 0;
}
date1 = truncate(date1);
date2 = truncate(date2);
return (int) ((date2.getTimeInMillis() - date1.getTimeInMillis()) / DateUtils.MILLIS_PER_DAY);
}
/**
* inverse function of decimalDaysBetween: adds a BigDecimal number of days to a Calendar.
* @param date1 the date to which something is added
* @param augend the number of days which is added, as a BigDecimal. May be negative.
* @return null if one of the arguments is null, else the date + augend days.
*/
public static Calendar decimalDaysAdd(final Calendar date1, final BigDecimal augend) {
if (date1 == null || augend == null) {
return null;
}
final BigDecimal date1InMillis = new BigDecimal(date1.getTimeInMillis());
final BigDecimal augendInMillis = augend.multiply(new BigDecimal(DateUtils.MILLIS_PER_DAY));
final BigDecimal resultAsBig = date1InMillis.add(augendInMillis);
final Calendar result = Calendar.getInstance();
result.setTimeInMillis(resultAsBig.longValue());
return result;
}
/**
* Returns the number of days between 2 dates as a BigDecimal. Will be negative if date2 < date1, or positive otherwise.
*/
public static BigDecimal decimalDaysBetween(final Calendar date1, final Calendar date2) {
if (date1 == null || date2 == null) {
return BigDecimal.ZERO;
}
final BigDecimal difference = new BigDecimal(date2.getTimeInMillis() - date1.getTimeInMillis());
final MathContext mathContext = new MathContext(LocalSettings.MAX_PRECISION);
final BigDecimal result = difference.divide(new BigDecimal(DateUtils.MILLIS_PER_DAY), mathContext);
return result;
}
/**
* sets the time of the day of the first argument, so that it is equal to the time of the day of the second argument.
* @param toEqualize a Calendar, whose time will be equalized to the source's time of the day
* @param source the time of the day of this Calendar is taken.
* @return a new Calendar object, with the date equal to toEqualize, but the time of the date is equal to that of the source Calendar.
*/
public static Calendar equalizeTime(final Calendar toEqualize, final Calendar source) {
if (source == null) {
return null;
}
final int hourOfDay = source.get(Calendar.HOUR_OF_DAY);
final int minute = source.get(Calendar.MINUTE);
final int second = source.get(Calendar.SECOND);
final int milliSecond = source.get(Calendar.MILLISECOND);
final Calendar result = (Calendar) toEqualize.clone();
result.set(Calendar.HOUR_OF_DAY, hourOfDay);
result.set(Calendar.MINUTE, minute);
result.set(Calendar.SECOND, second);
result.set(Calendar.MILLISECOND, milliSecond);
return result;
}
/**
* compares two Calendars on a certain precision level. <br>
* For example, if you had the datetime of 12 Mar 2011 14:31:07.847, and a second datetime of 12 Mar 2011 14:31:11.734, they would evaluate as
* equal on the Calendar.MINUTE level and above. They would evaluate as not equal on levels Calendar.SECOND and Calendar.MILLISECOND.<br>
* Fields are rounded, so 12 Mar 2011 14:31:07.847 and 12 Mar 2011 14:31:08.123 would evaluate as equal on the Calendar.SECOND level.
* @param cal1 if null, returns false
* @param cal2 if null, returns false
* @param level, for example Calendar.MINUTE
* @return true if equal on this level, false if not.
*/
public static boolean equals(final Calendar cal1, final Calendar cal2, final int level) {
if (cal1 == null || cal2 == null) {
return false;
}
final Calendar temp1 = DateUtils.round((Calendar) cal1.clone(), level);
final Calendar temp2 = DateUtils.round((Calendar) cal2.clone(), level);
return (temp1.equals(temp2));
}
/**
* Returns a date at 23:59:59 of the given day
*/
public static Calendar getDayEnd(final Calendar date) {
return TimePeriod.ONE_DAY.currentPeriod(date).getEnd();
}
/**
* a null proof method returning the earliest of any number of Calendars
* @param dates any null arguments are ignored. If all arguments are null just returns null.
* @return the earliest date of the arguments.
*/
public static Calendar getEarliest(final Calendar... dates) {
Calendar oldest = null;
for (final Calendar date : dates) {
if (oldest == null || (date != null && oldest.after(date))) {
oldest = date;
}
}
return oldest;
}
public static Map<String, Object> getLastCompletedMonthAndYear() {
final Calendar now = Calendar.getInstance();
int month = now.get(Calendar.MONTH);
int year = now.get(Calendar.YEAR);
if (month == 0) {
month = 12;
year--;
} else {
month--;
}
final Map<String, Object> completedMonthAndYear = new HashMap<String, Object>();
completedMonthAndYear.put("month", month);
completedMonthAndYear.put("year", year);
return completedMonthAndYear;
}
public static Map<String, Object> getLastCompletedQuarterAndYear() {
final Calendar now = Calendar.getInstance();
final int month = now.get(Calendar.MONTH);
int year = now.get(Calendar.YEAR);
Quarter quarter = null;
switch (month) {
case 0:
case 1:
case 2:
quarter = Quarter.FOURTH;
year--;
break;
case 3:
case 4:
case 5:
quarter = Quarter.FIRST;
break;
case 6:
case 7:
case 8:
quarter = Quarter.SECOND;
break;
case 9:
case 10:
case 11:
quarter = Quarter.THIRD;
break;
}
final Map<String, Object> completedQuarterAndYear = new HashMap<String, Object>();
completedQuarterAndYear.put("quarter", quarter);
completedQuarterAndYear.put("year", year);
return completedQuarterAndYear;
}
public static Period[] getPeriodsThroughTheTime(final Period period, final ThroughTimeRange throughTimeRange) {
final Calendar calendarIni = period.getBegin();
final Calendar calendarFini = period.getEnd();
final List<Period> result = new ArrayList<Period>();
final int monthIni = calendarIni.get(Calendar.MONTH);
final int monthFini = calendarFini.get(Calendar.MONTH);
final int yearIni = calendarIni.get(Calendar.YEAR);
final int yearFini = calendarFini.get(Calendar.YEAR);
for (int year = calendarIni.get(Calendar.YEAR); year <= calendarFini.get(Calendar.YEAR); year++) {
Period periodAux = null;
int monthIniAux = 0;
int monthFiniAux = 11;
if (year == yearIni) {
monthIniAux = monthIni;
} else {
monthIniAux = 0;
}
if (year == yearFini) {
monthFiniAux = monthFini;
} else {
monthFiniAux = 11;
}
int increment = 1;
// months or quarters
if (throughTimeRange == ThroughTimeRange.MONTH || throughTimeRange == ThroughTimeRange.QUARTER) {
// only quarters
if (throughTimeRange == ThroughTimeRange.QUARTER) {
increment = 3;
}
for (int month = monthIniAux; month <= monthFiniAux; month = month + increment) {
final Calendar calendarIniAux = new GregorianCalendar(year, month, 1, 0, 0, 0);
final Calendar calendarFiniHlp = new GregorianCalendar(year, (month + increment - 1), 1);
final Calendar calendarFiniAux = new GregorianCalendar(year, (month + increment - 1), calendarFiniHlp.getActualMaximum(Calendar.DAY_OF_MONTH), 23, 59, 59);
periodAux = new Period(calendarIniAux, calendarFiniAux);
result.add(periodAux);
}
}
// years, it doesn't need to iterate over months.
else if (throughTimeRange == ThroughTimeRange.YEAR) {
final Calendar calendarIniAux = new GregorianCalendar(year, 0, 1, 0, 0, 0);
final Calendar calendarFiniAux = new GregorianCalendar(year, 11, 31, 23, 59, 59);
periodAux = new Period(calendarIniAux, calendarFiniAux);
result.add(periodAux);
}
}
final Period[] periodResult = new Period[result.size()];
return result.toArray(periodResult);
}
public static Period getYearPeriod(final int year) {
// First day of the year
Calendar begin = Calendar.getInstance();
begin.set(Calendar.YEAR, year);
begin = truncate(begin);
// First day of the next year
final Calendar end = Calendar.getInstance();
end.set(Calendar.YEAR, year + 1);
final Period yearPeriod = new Period();
yearPeriod.setBegin(begin);
yearPeriod.setEnd(end);
return yearPeriod;
}
/**
* checks if the two dates are on the same calendar day.
*/
public static boolean sameDay(final Calendar first, final Calendar second) {
final Calendar equalizedFirst = DateHelper.equalizeTime(first, second);
return (equalizedFirst.equals(second));
}
/**
* Returns the number of seconds since the given time
*/
public static double secondsSince(final long since) {
return (System.currentTimeMillis() - since) / 1000.0;
}
/**
* Truncates a date, handling null. Doesn't modify the Calendar parameter, returning a new modified instance
*/
public static Calendar truncate(final Calendar date) {
if (date == null) {
return null;
}
return DateUtils.truncate(date, Calendar.DATE);
}
/**
* Truncates a date and adds 1 day, handling null. Doesn't modify the Calendar parameter, returning a new modified instance
*/
public static Calendar truncateNextDay(Calendar date) {
if (date == null) {
return null;
}
date = (Calendar) date.clone();
date.add(Calendar.DATE, 1);
return truncate(date);
}
/**
* Truncates a date and subtracts 1 day, handling null. Doesn't modify the Calendar parameter, returning a new modified instance
*/
public static Calendar truncatePreviosDay(Calendar date) {
if (date == null) {
return null;
}
date = (Calendar) date.clone();
date.add(Calendar.DATE, -1);
return truncate(date);
}
}