///////////////////////////////////////////////////////////////////////////// // // Project ProjectForge Community Edition // www.projectforge.org // // Copyright (C) 2001-2014 Kai Reinhard (k.reinhard@micromata.de) // // ProjectForge is dual-licensed. // // This community edition 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; version 3 of the License. // // This community edition 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 this program; if not, see http://www.gnu.org/licenses/. // ///////////////////////////////////////////////////////////////////////////// package org.projectforge.common; import java.io.Serializable; import java.sql.Timestamp; import java.util.Calendar; import java.util.Date; import java.util.Locale; import java.util.TimeZone; import org.apache.commons.lang.Validate; import org.apache.commons.lang.builder.HashCodeBuilder; import org.projectforge.calendar.DayHolder; import org.projectforge.user.PFUserContext; /** * Parse and formats dates. * @author Kai Reinhard (k.reinhard@micromata.de) */ public class DateHolder implements Serializable, Cloneable, Comparable<DateHolder> { private static final long serialVersionUID = -5373883617915418698L; protected Calendar calendar; private DatePrecision precision; /** * Initializes calendar with current date and uses the time zone and the locale of the ContextUser if exists. * @see DateHelper#getCalendar() */ public DateHolder() { this.calendar = DateHelper.getCalendar(); } /** * Ensures the precision. * @param precision * @param timeZone * @param locale */ public DateHolder(final DatePrecision precision, final TimeZone timeZone, final Locale locale) { calendar = Calendar.getInstance(timeZone, locale); setPrecision(precision); } /** * Ensures the precision. * @param precision * @param locale * @see DateHelper#getCalendar(Locale) */ public DateHolder(final DatePrecision precision, final Locale locale) { calendar = DateHelper.getCalendar(locale); setPrecision(precision); } /** * Ensures the precision. * @see DateHelper#getCalendar() */ public DateHolder(final DatePrecision precision) { this.calendar = DateHelper.getCalendar(); setPrecision(precision); } /** * Ensures the precision. * @param date If null, the current date will be used. * @param precision * @see DateHelper#getCalendar() */ public DateHolder(final Date date, final DatePrecision precision) { this.calendar = DateHelper.getCalendar(); if (date != null) { this.calendar.setTime(date); } setPrecision(precision); } /** * Ensures the precision. * @param date * @see DateHelper#getCalendar(Locale) */ public DateHolder(final Date date, final DatePrecision precision, final Locale locale) { this.calendar = DateHelper.getCalendar(locale); this.calendar.setTime(date); setPrecision(precision); } /** * Ensures the precision. * @param date * @see DateHelper#getCalendar(TimeZone, Locale) */ public DateHolder(final Date date, final DatePrecision precision, final TimeZone timeZone, final Locale locale) { this.calendar = DateHelper.getCalendar(timeZone, locale); this.calendar.setTime(date); setPrecision(precision); } /** * Ensures the precision. * @param date * @see DateHelper#getCalendar() */ public DateHolder(final Date date) { this.calendar = DateHelper.getCalendar(); if (date != null) { calendar.setTime(date); } ensurePrecision(); } /** * Initializes calendar with given date and uses the given time zone and the locale of the ContextUser if exists. */ public DateHolder(final Date date, final TimeZone timeZone) { this(date); this.calendar.setTimeZone(timeZone); } /** * @see DateHelper#getCalendar(TimeZone, Locale) */ public DateHolder(final Date date, final TimeZone timeZone, final Locale locale) { this.calendar = DateHelper.getCalendar(timeZone, locale); this.calendar.setTime(date); } /** * Initializes calendar with current date and uses the given time zone and the locale of the ContextUser if exists. * @see DateHelper#getCalendar(TimeZone) */ public DateHolder(final TimeZone timeZone) { this(); this.calendar.setTimeZone(timeZone); } /** * Ensures the precision. * @param date * @param locale * @see DateHelper#getCalendar(Locale) */ public DateHolder(final Date date, final Locale locale) { this.calendar = DateHelper.getCalendar(locale); calendar.setTime(date); ensurePrecision(); } /** Clones calendar. */ public DateHolder(final Calendar cal, final DatePrecision precision) { this(cal); setPrecision(precision); } /** Clones calendar. */ public DateHolder(final Calendar cal) { this.calendar = (Calendar) cal.clone(); } public boolean before(final DateHolder date) { return this.getDate().before(date.getDate()); } public boolean before(final Date date) { return this.getDate().before(date); } public boolean after(final DateHolder date) { return this.getDate().after(date.getDate()); } public boolean after(final Date date) { return this.getDate().after(date); } public boolean isBetween(final Date from, final Date to) { final Date date = getDate(); if (from == null) { if (to == null) { return false; } return date.after(to) == false; } if (to == null) { return date.before(from) == false; } return !(date.after(to) == true || date.before(from) == true); } public boolean isBetween(final DateHolder from, final DateHolder to) { final Date fromDate = from != null ? from.getDate() : null; final Date toDate = to != null ? to.getDate() : null; return isBetween(fromDate, toDate); } /** Clones the calendar. */ public DateHolder setCalendar(final Calendar cal) { this.calendar = (Calendar) cal.clone(); ensurePrecision(); return this; } /** * Date will be set. Dependent on precision, also seconds etc. will be set to zero. Ensures the precision. */ public DateHolder setDate(final Date date) { if (date == null) { return this; } this.calendar.setTime(date); ensurePrecision(); return this; } /** * Date will be set. Dependent on precision, also seconds etc. will be set to zero. Ensures the precision. * @param millis UTC millis */ public DateHolder setDate(final long millis) { this.calendar.setTimeInMillis(millis); ensurePrecision(); return this; } /** * Sets the precision of the date represented by this object. Ensures the precision. * @param precision SECOND, MINUTE, MINUTE_15, HOUR_OF_DAY or DAY. */ public DateHolder setPrecision(final DatePrecision precision) { this.precision = precision; ensurePrecision(); return this; } /** * Ensure the given precision by setting / rounding fields such as minutes and seconds. If precision is MINUTE_15 then rounding the * minutes down: 00-14 -> 00; 15-29 -> 15, 30-44 -> 30, 45-59 -> 45. */ public DateHolder ensurePrecision() { if (this.precision == null) { return this; } switch (this.precision) { case DAY: calendar.set(Calendar.HOUR_OF_DAY, 0); case HOUR_OF_DAY: calendar.set(Calendar.MINUTE, 0); case MINUTE_15: case MINUTE_5: case MINUTE: calendar.set(Calendar.SECOND, 0); case SECOND: calendar.set(Calendar.MILLISECOND, 0); default: } if (this.precision == DatePrecision.MINUTE_15) { final int minute = calendar.get(Calendar.MINUTE); if (minute < 8) { calendar.set(Calendar.MINUTE, 0); } else if (minute < 23) { calendar.set(Calendar.MINUTE, 15); } else if (minute < 38) { calendar.set(Calendar.MINUTE, 30); } else if (minute < 53) { calendar.set(Calendar.MINUTE, 45); } else { calendar.set(Calendar.MINUTE, 0); calendar.add(Calendar.HOUR, 1); } } else if (this.precision == DatePrecision.MINUTE_5) { final int minute = calendar.get(Calendar.MINUTE); for (int i = 3; i < 60; i += 5) { if (minute < i) { calendar.set(Calendar.MINUTE, i - 3); break; } } if (minute > 57) { calendar.set(Calendar.MINUTE, 0); calendar.add(Calendar.HOUR, 1); } } return this; } public DatePrecision getPrecision() { return precision; } /** * Considers the time zone of the user, for example, if date is 20.11.1970 23:00:00 UTC but the user's locale is Europe/Berlin then the * java.sql.Date should be 21.11.1970! <br/> * This methods transforms first the day into UTC and then into java.sql.Date. */ public java.sql.Date getSQLDate() { final Calendar cal = Calendar.getInstance(DateHelper.UTC); cal.set(Calendar.YEAR, calendar.get(Calendar.YEAR)); cal.set(Calendar.DAY_OF_YEAR, calendar.get(Calendar.DAY_OF_YEAR)); cal.set(Calendar.HOUR_OF_DAY, 0); cal.set(Calendar.MINUTE, 0); cal.set(Calendar.SECOND, 0); cal.set(Calendar.MILLISECOND, 0); final java.sql.Date date = new java.sql.Date(cal.getTimeInMillis()); return date; } /** * Has the given date the same day? The given date will be converted into a calendar (clone from this) with same time zone. * @param date * @return */ public boolean isSameDay(final Date date) { final DateHolder other = new DateHolder(this.calendar); other.setDate(date); return isSameDay(other); } /** * Has the given date the same day? The given date will be converted into a calendar (clone from this) with same time zone. * @param date * @return */ public boolean isSameDay(final DateHolder date) { return getYear() == date.getYear() && getDayOfYear() == date.getDayOfYear(); } /** * Sets the date to the beginning of the year (first day of year) and calls setBeginOfDay. * @see #setBeginOfDay() */ public DateHolder setBeginOfYear() { calendar.set(Calendar.MONTH, Calendar.JANUARY); setBeginOfMonth(); return this; } /** * Sets the date to the beginning of the year (first day of year) and calls setBeginOfDay. * @see #setBeginOfDay() */ public DateHolder setEndOfYear() { calendar.set(Calendar.MONTH, Calendar.DECEMBER); setEndOfMonth(); return this; } /** * Sets the date to the beginning of the month (first day of month) and calls setBeginOfDay. * @see #setBeginOfDay() */ public DateHolder setBeginOfMonth() { calendar.set(Calendar.DAY_OF_MONTH, 1); setBeginOfDay(); return this; } public DateHolder setEndOfMonth() { final int day = calendar.getActualMaximum(Calendar.DAY_OF_MONTH); calendar.set(Calendar.DAY_OF_MONTH, day); setEndOfDay(); return this; } /** * Sets the date to the beginning of the week (first day of week) and calls setBeginOfDay. * @see #setBeginOfDay() */ public DateHolder setBeginOfWeek() { calendar.set(Calendar.DAY_OF_WEEK, getFirstDayOfWeek()); setBeginOfDay(); return this; } private static int getFirstDayOfWeek() { return PFUserContext.getCalendarFirstDayOfWeek(); } /** * Checks on day equals first day of week and hour, minute, second and millisecond equals zero. */ public boolean isBeginOfWeek() { return getDayOfWeek() == getFirstDayOfWeek() && getMilliSecond() == 0 && getSecond() == 0 && getMinute() == 0 && getHourOfDay() == 0; } /** * Sets the date to the ending of the week (last day of week) and calls setEndOfDay. * @see #setEndOfDay() */ public DateHolder setEndOfWeek() { final int firstDayOfWeek = getFirstDayOfWeek(); short endlessLoopDetection = 0; do { calendar.add(Calendar.DAY_OF_YEAR, 1); if (++endlessLoopDetection > 10) { throw new RuntimeException("Endless loop protection. Please contact developer!"); } } while (calendar.get(Calendar.DAY_OF_WEEK) != firstDayOfWeek); calendar.add(Calendar.DAY_OF_YEAR, -1); // Get one day before first day of next week. setEndOfDay(); return this; } /** * Sets the hour, minutes and seconds to 0; */ public DateHolder setBeginOfDay() { calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); return this; } /** * Sets hour=23, minute=59, second=59 */ public DateHolder setEndOfDay() { calendar.set(Calendar.HOUR_OF_DAY, 23); calendar.set(Calendar.MINUTE, 59); calendar.set(Calendar.SECOND, 59); calendar.set(Calendar.MILLISECOND, 999); return this; } /** * Setting the date from the given object (only year, month, day). Hours, minutes, seconds etc. will be preserved. * @param src The calendar from which to copy the values. * @see #ensurePrecision() */ public DateHolder setDay(final Calendar src) { calendar.set(Calendar.YEAR, src.get(Calendar.YEAR)); calendar.set(Calendar.MONTH, src.get(Calendar.MONTH)); calendar.set(Calendar.DAY_OF_MONTH, src.get(Calendar.DAY_OF_MONTH)); computeTime(); return this; } public Date getDate() { return calendar.getTime(); } /** * Gets the time of day in milliseconds since midnight. This method is used for comparing the times. * @return */ public long getTimeOfDay() { return getHourOfDay() * 3600 + getMinute() * 60 + getSecond(); } public Timestamp getTimestamp() { return new Timestamp(getDate().getTime()); } public Calendar getCalendar() { return this.calendar; } /** * Compute time of all fields by calling by calling calendar.getTimeInMillis. Don't forget to call ensurePrecision if needed. * @see #ensurePrecision() * @see Calendar#getTimeInMillis() */ public DateHolder computeTime() { calendar.getTimeInMillis(); return this; } public int getYear() { return calendar.get(Calendar.YEAR); } public int getMonth() { return calendar.get(Calendar.MONTH); } /** * @return * @see DateHelper#getWeekOfYear(Date) */ public int getWeekOfYear() { return DateHelper.getWeekOfYear(calendar); } public int getDayOfYear() { return calendar.get(Calendar.DAY_OF_YEAR); } public int getDayOfMonth() { return calendar.get(Calendar.DAY_OF_MONTH); } public int getDayOfWeek() { return calendar.get(Calendar.DAY_OF_WEEK); } /** Gets the hour of day (0-23). */ public int getHourOfDay() { return calendar.get(Calendar.HOUR_OF_DAY); } public DateHolder setMonth(final int month) { calendar.set(Calendar.MONTH, month); return this; } public DateHolder setDayOfMonth(final int day) { calendar.set(Calendar.DAY_OF_MONTH, day); return this; } public DateHolder setHourOfDay(final int hour) { calendar.set(Calendar.HOUR_OF_DAY, hour); return this; } /** Gets the minute (0-59) */ public int getMinute() { return calendar.get(Calendar.MINUTE); } /** * Ensures the precision. * @param minute */ public DateHolder setMinute(final int minute) { calendar.set(Calendar.MINUTE, minute); ensurePrecision(); return this; } public int getSecond() { return calendar.get(Calendar.SECOND); } /** * Ensures the precision. * @param second */ public DateHolder setSecond(final int second) { calendar.set(Calendar.SECOND, second); ensurePrecision(); return this; } public int getMilliSecond() { return calendar.get(Calendar.MILLISECOND); } /** * Ensures the precision. * @param milliSecond */ public DateHolder setMilliSecond(final int milliSecond) { calendar.set(Calendar.MILLISECOND, milliSecond); ensurePrecision(); return this; } public long getTimeInMillis() { return calendar.getTimeInMillis(); } /** * Stops calculation for more than 500 years. * @param other * @return other.days - this.days. */ public int daysBetween(final Date other) { final DateHolder o = new DateHolder(calendar); o.setDate(other); return daysBetween(o); } /** * @param other * @return days between this and given date (other - this). */ public int daysBetween(final DateHolder other) { final DateHolder from, to; if (this.getTimeInMillis() < other.getTimeInMillis()) { from = this; to = other; } else { from = other; to = this; } int result = 0; final int toYear = to.getYear(); final DateHolder dh = new DateHolder(from.getDate()); int endlessLoopProtection = 0; while (dh.getYear() < toYear) { final int fromDay = dh.getDayOfYear(); dh.setMonth(Calendar.DECEMBER); dh.setDayOfMonth(31); result += dh.getDayOfYear() - fromDay + 1; dh.add(Calendar.DAY_OF_MONTH, 1); if (++endlessLoopProtection > 5000) { throw new IllegalArgumentException("Days between doesn's support more than 5000 years"); } } result += to.getDayOfYear() - dh.getDayOfYear(); if (this.getTimeInMillis() < other.getTimeInMillis()) { return result; } else { return -result; } } /** * Adds the given number of days. * @see Calendar#add(int, int) * @param field * @param amount */ public DateHolder add(final int field, final int amount) { calendar.add(field, amount); return this; } /** * Adds the given number of days (non-working days will be skipped). Maximum allowed value is 10.000 (for avoiding end-less loops). * @param days Value can be positive or negative. */ public DateHolder addWorkingDays(final int days) { Validate.isTrue(days <= 10000); short sign = 1; if (days < 0) { sign = -1; } int counter = 0; for (int i = 0; i < 10000; i++) { if (counter == days) { break; } do { calendar.add(Calendar.DAY_OF_MONTH, sign); } while (new DayHolder(this).isWorkingDay() == false); counter += sign; } return this; } @Override public String toString() { return DateHelper.formatAsUTC(getDate()) + ", time zone=" + calendar.getTimeZone().getID() + ", date=" + getDate().toString(); } @Override public DateHolder clone() { final DateHolder res = new DateHolder(); res.calendar = (Calendar) this.calendar.clone(); // res.calendar.setTime(this.calendar.getTime()); res.precision = this.precision; return res; } /** * Sets hour, minute, second and millisecond to zero. * @param year * @param month * @param day * @see #setDate(int, int, int, int, int, int, int) */ public DateHolder setDate(final int year, final int month, final int day) { setDate(year, month, day, 0, 0, 0, 0); return this; } /** * Sets second and millisecond to zero. * @param year * @param month * @param day * @param hourOfDay * @param minute * @see #setDate(int, int, int, int, int, int, int) */ public DateHolder setDate(final int year, final int month, final int day, final int hourOfDay, final int minute) { setDate(year, month, day, hourOfDay, minute, 0, 0); return this; } /** * Sets the date by giving all datefields and compute all fields. Set millisecond to zero. * @param year * @param month * @param date * @param hourOfDay * @param minute * @param second * @see #setDate(int, int, int, int, int, int, int) */ public DateHolder setDate(final int year, final int month, final int date, final int hourOfDay, final int minute, final int second) { setDate(year, month, date, hourOfDay, minute, second, 0); return this; } /** * Sets the date by giving all datefields and compute all fields. * @param year * @param month * @param day * @param hour * @param minute * @param second * @param millisecond * @see #computeTime() * @see #ensurePrecision() */ public DateHolder setDate(final int year, final int month, final int date, final int hourOfDay, final int minute, final int second, final int millisecond) { calendar.set(year, month, date, hourOfDay, minute, second); calendar.set(Calendar.MILLISECOND, millisecond); computeTime(); ensurePrecision(); return this; } @Override public boolean equals(final Object obj) { if (obj instanceof DateHolder) { final DateHolder other = (DateHolder) obj; if (other.getTimeInMillis() == getTimeInMillis() && other.getPrecision() == getPrecision()) { return true; } } return false; } @Override public int hashCode() { final HashCodeBuilder hcb = new HashCodeBuilder(); hcb.append(this.getTimeInMillis()).append(getPrecision()); return hcb.toHashCode(); } /** * @see java.lang.Comparable#compareTo(java.lang.Object) */ public int compareTo(final DateHolder o) { return calendar.compareTo(o.calendar); } }