/*
* Rapid Beans Framework: PropertyDate.java
*
* Copyright (C) 2009 Martin Bluemel
*
* Creation Date: 12/24/2005
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU Lesser General Public License as published by the Free Software Foundation;
* either version 3 of the License, or (at your option) any later version.
* This program 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 Lesser General Public License for more details.
* You should have received a copies of the GNU Lesser General Public License and the
* GNU General Public License along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
package org.rapidbeans.core.basic;
import java.text.DateFormat;
import java.text.FieldPosition;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import org.rapidbeans.core.common.PrecisionDate;
import org.rapidbeans.core.common.RapidBeansLocale;
import org.rapidbeans.core.exception.PropValueNullException;
import org.rapidbeans.core.exception.RapidBeansRuntimeException;
import org.rapidbeans.core.exception.ValidationException;
import org.rapidbeans.core.type.TypeProperty;
import org.rapidbeans.core.type.TypePropertyDate;
import org.rapidbeans.core.util.StringHelper;
/**
* A <b>Date</b> bean property encapsulates Date values which express a certain
* point of time in the precision of milliseconds.<br/>
* The precision of the property is configurable from millisecond to year<br/>
* In addition it optionally enforces validation of:<br/>
* - minimal date<br/>
* - maximal date<br/>
*
* @author Martin Bluemel
*/
public class PropertyDate extends Property {
/**
* the property's Date value. !!! do not initialize here because the
* superclass does it with the property type's default value
*/
private Date value;
/**
* formatter for language independent format 1.
*/
private static final DateFormat LANG_INDEP_DATE_FORMAT_1 = DateFormat.getDateInstance(DateFormat.MEDIUM,
Locale.GERMAN);
/**
* formatter for language independent format 2.
*/
private static final DateFormat LANG_INDEP_DATE_FORMAT_2 = DateFormat.getDateTimeInstance(DateFormat.MEDIUM,
DateFormat.MEDIUM, Locale.GERMAN);
/**
* constructor for a new Date Property.
*
* @param type
* the Property's type
* @param parentBean
* the parent bean
*/
public PropertyDate(final TypeProperty type, final RapidBean parentBean) {
super(type, parentBean);
}
/**
* generic value getter.
*
* @return the value of this Property as java.util.Date
*/
public Date getValue() {
Date date = null;
if (getBean() instanceof RapidBeanImplSimple) {
date = (Date) Property.getValueFieldByReflection(getBean(), getName());
} else {
if (this.value != null) {
// clone the date value object in order
// to compensate the Java design error of a mutable Date object
date = new Date(this.value.getTime());
}
}
return date;
}
/**
* special value getter.
*
* @return the value of this Property as long which represents the internal
* time value of a java.util.Date
*/
public long getValueTime() {
final Date date = getValue();
if (date == null) {
throw new PropValueNullException("value for property not defined");
}
return date.getTime();
}
/**
* String getter. Converts the Date Property into a String depending on the
* configured precision.<br/>
* Examples:<br/>
* precision = year, date = 2005 => <b>2005</b><br/>
* precision = day, date = March 15, 2005 => <b>20050315</b><br/>
* precision = millisecond, date = March 15, 2005, time = 11:23:45.678 pm =>
* <b>20050315112345678</b><br/>
*
* @return the String representation of this Property's value.
*/
public String toString() {
final Date date = getValue();
if (date == null) {
return null;
} else {
return format(date, ((TypePropertyDate) this.getType()).getPrecision());
}
}
/**
* generic value setter. Accepts the following datatypes:<br/>
* <b>Date:</b> the Date<br/>
* <b>String:</b> the Date as string<br/>
*
* @param newValue
* the new value to set
*/
public void setValue(final Object newValue) {
super.setValueWithEvents(getValue(), newValue, new PropertyValueSetter() {
public void setValue(final Object newValue) {
if (getBean() instanceof RapidBeanImplSimple) {
Property.setValueByReflection(getBean(), getName(), newValue);
} else {
value = (Date) newValue;
}
}
});
}
/**
* validate a value for a Date Property.
*
* @param newValue
* the value to convert and validate.
*
* @return the converted value which is the internal representation or if a
* primitive type the corresponding value object
*/
public Date validate(final Object newValue) {
final Date date = (Date) super.validate(newValue);
if (!ThreadLocalValidationSettings.getValidation()) {
return date;
}
if (newValue == null) {
return null;
}
final long time = date.getTime();
final TypePropertyDate type = (TypePropertyDate) this.getType();
// check against max boundary
final long maxTime = type.getMaxVal();
if (time > maxTime) {
throw new ValidationException("invalid.prop.date.greater", this, "invalid date \""
+ PropertyDate.format(new Date(time), type.getPrecision()) + "\" greater than maximal date \""
+ PropertyDate.format(new Date(maxTime), type.getPrecision()) + ".", new String[] {});
}
// check against min boundary
final long minTime = type.getMinVal();
if (time < minTime) {
throw new ValidationException("invalid.prop.date.lower", this, "invalid date \""
+ PropertyDate.format(new Date(time), type.getPrecision()) + "\" lower than minimal date \""
+ PropertyDate.format(new Date(minTime), type.getPrecision()) + ".");
}
return date;
}
/**
* converts the following classes into a time value for a Date.<br/>
*
* - <b>Date:</b> cut to the appropriate precision<br/>
* - <b>String:</b> parsed from String representation<br/>
* - <b>Long:</b> seconds set as the time of the Date<br/>
*
* @param argVal
* the value object to convert
*
* @return the converted Date
*/
public Date convertValue(final Object argVal) {
if (argVal == null) {
return null;
}
long time;
if (argVal instanceof Date) {
time = cutPrecisionLong(((Date) argVal).getTime());
} else if (argVal instanceof String) {
time = PropertyDate.parse(cutPrecisionString((String) argVal)).getTime();
} else if (argVal instanceof Long) {
time = cutPrecisionLong(((Long) argVal).longValue());
} else {
throw new ValidationException("invalid.prop.date.type", this, "invalid data type \""
+ argVal.getClass().getName() + "\".\nOnly \"Date\", \"String\", and Long are valid types.");
}
return new Date(time);
}
/**
* Cut the given norm String to the appropriate precision.
*
* @param newValue
* the norm String
* @return the cut String
*/
private String cutPrecisionString(final String newValue) {
final int len = newValue.length();
int maxlen;
switch (((TypePropertyDate) this.getType()).getPrecision()) {
case year:
maxlen = 4;
break;
case month:
maxlen = 6;
break;
case day:
maxlen = 8;
break;
case hour:
maxlen = 10;
break;
case minute:
maxlen = 12;
break;
case second:
maxlen = 14;
break;
case millisecond:
maxlen = 17;
break;
default:
throw new RapidBeansRuntimeException("wrong precision");
}
String cutValue = newValue;
if (len > maxlen) {
cutValue = newValue.substring(0, maxlen);
}
return cutValue;
}
/**
* Cut the given norm String to the appropriate precision.
*
* @param newValue
* the norm String
*
* @return the cut String
*/
public long cutPrecisionLong(final long newValue) {
return cutPrecisionLong(newValue, ((TypePropertyDate) this.getType()).getPrecision());
}
/**
* Cut the given norm String to the appropriate precision.
*
* @param newValue
* the norm String
* @param precision
* the precision to apply
*
* @return the cut String
*/
public static long cutPrecisionLong(final long newValue, final PrecisionDate precision) {
long cutValue = newValue;
switch (precision) {
case year:
cutValue = parse(format(new Date(newValue), precision)).getTime();
break;
case month:
cutValue = parse(format(new Date(newValue), precision)).getTime();
break;
case day:
cutValue = parse(format(new Date(newValue), precision)).getTime();
// cutValue = ((newValue + 3600000) / 86400000) * 86400000 -
// 3600000;
break;
case hour:
cutValue = parse(format(new Date(newValue), precision)).getTime();
break;
case minute:
cutValue = parse(format(new Date(newValue), precision)).getTime();
break;
case second:
cutValue = (newValue / 1000) * 1000;
break;
case millisecond:
cutValue = newValue;
break;
default:
throw new RapidBeansRuntimeException("wrong precision");
}
return cutValue;
}
/**
* convert a Date into a String.
*
* @param d
* the Date to convert
* @param precision
* the precision given
*
* @return the String representation of this property.
*/
public static String format(final Date d, final PrecisionDate precision) {
String sDate = PropertyDate.LANG_INDEP_DATE_FORMAT_2.format(d);
// get the year
StringBuffer buf = new StringBuffer(sDate.substring(6, 10));
if (precision == PrecisionDate.year) {
return buf.toString();
}
buf.append(sDate.substring(3, 5));
if (precision == PrecisionDate.month) {
return buf.toString();
}
buf.append(sDate.substring(0, 2));
if (precision == PrecisionDate.day) {
return buf.toString();
}
buf.append(sDate.substring(11, 13));
if (precision == PrecisionDate.hour) {
return buf.toString();
}
buf.append(sDate.substring(14, 16));
if (precision == PrecisionDate.minute) {
return buf.toString();
}
buf.append(sDate.substring(17, 19));
if (precision == PrecisionDate.second) {
return buf.toString();
}
buf.append(Integer.toString((int) (d.getTime() % 1000)));
return buf.toString();
}
/**
* parse a Date in norm format.
*
* @param s
* the String to parse
*
* @return the parsed Date object.
*/
public static Date parse(final String s) {
Date date = null;
String sDate = null;
int len = s.length();
if (!StringHelper.isDigitsOnly(s)) {
throw new ValidationException("invalid.prop.date.string.norm.number", null, "invalid date norm string \""
+ s + "\", must have at least 4 and at maximum 17 digits." + "\nExample: 19641014235945999 means"
+ " October 14, 1964 11:59 pm 45 seconds and 999 milliseconds.");
}
if (len < 4) {
throw new ValidationException("invalid.prop.date.string.norm.short", null, "invalid " + len
+ " digit date norm string \"" + s + "\", must have at least 4 digits."
+ "\nExample: 1964 means the year 1964.");
}
if (len > 17) {
throw new ValidationException("invalid.prop.date.string.norm.long", null, "invalid " + len
+ " digit date (point of time) norm string \"" + s + "\", must have at maximum 17 characters."
+ "\nExample: 19641014235945999 means"
+ " October 14, 1964 11:59 pm 45 seconds and 999 milliseconds.");
}
int iDay = -1;
int iMonth = -1;
int iYear = -1;
int iMillis = -1;
GregorianCalendar cal = null;
switch (len) {
case 4:
sDate = "01.01." + s.substring(0, 4);
break;
case 5:
throw new ValidationException("invalid.prop.date.string.norm.5", null, "invalid " + len
+ " digit date norm string \"" + s + "\", must have 4 or 6 digits."
+ "\nExample: 196410 means October 14.");
case 6:
sDate = "01." + s.substring(4, 6) + "." + s.substring(0, 4);
break;
case 7:
throw new ValidationException("invalid.prop.date.string.norm.7", null, "invalid " + len
+ " digit date norm string \"" + s + "\", must have 6 or 8 digits."
+ "\nExample: 19641014 means October 14, 1964.");
case 8:
iDay = Integer.parseInt(s.substring(6, 8));
iMonth = Integer.parseInt(s.substring(4, 6)) - 1;
iYear = Integer.parseInt(s.substring(0, 4));
cal = new GregorianCalendar(iYear, iMonth, iDay);
break;
case 9:
throw new ValidationException("invalid.prop.date.string.norm.9", null, "invalid " + len
+ " digit date (point of time) norm string \"" + s + "\", must have 8 or 10 digits."
+ "\nExample: 1964101423 means" + " October 14, 1964 11 pm seconds.");
case 10:
sDate = s.substring(6, 8) + "." + s.substring(4, 6) + "." + s.substring(0, 4) + " " + s.substring(8, 10)
+ ":00:00";
break;
case 11:
throw new ValidationException("invalid.prop.date.string.norm.11", null, "invalid " + len
+ " digit date (point of time) norm string \"" + s + "\", must have 10 or 12 digits."
+ "\nExample: 196410142359 means" + " October 14, 1964 11:59 pm.");
case 12:
sDate = s.substring(6, 8) + "." + s.substring(4, 6) + "." + s.substring(0, 4) + " " + s.substring(8, 10)
+ ":" + s.substring(10, 12) + ":00";
break;
case 13:
throw new ValidationException("invalid.prop.date.string.norm.13", null, "invalid " + len
+ " digit date (point of time) norm string \"" + s + "\", must have 12 or 14 digits."
+ "\nExample: 19641014235945 means" + " October 14, 1964 11:59 pm 45 seconds.");
case 14:
sDate = s.substring(6, 8) + "." + s.substring(4, 6) + "." + s.substring(0, 4) + " " + s.substring(8, 10)
+ ":" + s.substring(10, 12) + ":" + s.substring(12, 14);
break;
case 15:
case 16:
throw new ValidationException("invalid.prop.date.string.norm.1516", null, "invalid " + len
+ " digit date (point of time) norm string \"" + s + "\", must have 14 or 17 digits."
+ "\nExample: 19641014235945999 means"
+ " October 14, 1964 11:59 pm 45 seconds and 999 milliseconds.");
case 17:
sDate = s.substring(6, 8) + "." + s.substring(4, 6) + "." + s.substring(0, 4) + " " + s.substring(8, 10)
+ ":" + s.substring(10, 12) + ":" + s.substring(12, 14);
iMillis = Integer.parseInt(s.substring(14, 17));
break;
default:
throw new RapidBeansRuntimeException("This should never never happen!!!");
}
if (cal != null) {
if (iDay != cal.get(Calendar.DAY_OF_MONTH)) {
throw new ValidationException("invalid.prop.date.dayofmonth", null, "invalid date \"" + s + "\"."
+ "\nDay of month \"" + new Integer(iDay).toString() + "\" is incorrect.");
}
if (iMonth != cal.get(Calendar.MONTH)) {
throw new ValidationException("invalid.prop.date.month", null, "invalid date \"" + s + "\"."
+ "\"Month \"" + new Integer(iMonth).toString() + "\" is incorrect.");
}
if (iYear != cal.get(Calendar.YEAR)) {
throw new ValidationException("invalid.prop.date.year", null, "invalid date \"" + s + "\"."
+ "\nYear \"" + new Integer(iYear).toString() + "\" is incorrect.");
}
date = cal.getTime();
} else {
DateFormat df;
if (len > 8) {
df = PropertyDate.LANG_INDEP_DATE_FORMAT_2;
} else {
df = PropertyDate.LANG_INDEP_DATE_FORMAT_1;
}
try {
date = df.parse(sDate);
if (iMillis > -1) {
date = new Date(date.getTime() + iMillis);
}
} catch (java.text.ParseException e) {
throw new ValidationException("invalid.prop.date.parse", null, "invalid non parseable date \"" + sDate
+ "\".");
}
}
return date;
}
/**
* @param locale
* the Locale
* @parame mode the mode
* @return a string for the property's value for UI
*/
public String toStringGui(final RapidBeansLocale locale) {
return this.format(locale, DateFormat.MEDIUM, -1);
}
/**
* format a Date.
*
* @param locale
* the locale
* @param format
* the format (see class java.text.DateFormat)
* @param field
* the field (see class java.text.DateFormat). Set this argument
* to -1 if all field shoud be shown
* @return the formatted string
*/
public String format(final RapidBeansLocale locale, final int format, final int field) {
return formatDate(this.value, locale, format, field);
}
/**
* format a Date.
*
* @param locale
* the Locale
* @param sFormat
* { DateFormat.SHORT | MEDIUM | LONG }
* @param sField
* { DateFormat.DATE_FIELD, MONTH_FIELD, YEAR_FIELD, ...
* @return a string for the property's value for UI
*/
public String format(final RapidBeansLocale locale, final String sFormat, final String sField) {
if (this.value == null) {
return "-";
}
int format = -1;
switch (sFormat.charAt(0)) {
case 'S':
if (!sFormat.equals("SHORT")) {
throw new RapidBeansRuntimeException("Unknown Date Format \"" + sFormat + "\"");
}
format = DateFormat.SHORT;
break;
case 'M':
if (!sFormat.equals("MEDIUM")) {
throw new RapidBeansRuntimeException("Unknown Date Format \"" + sFormat + "\"");
}
format = DateFormat.MEDIUM;
break;
case 'L':
if (!sFormat.equals("LONG")) {
throw new RapidBeansRuntimeException("Unknown Date Format \"" + sFormat + "\"");
}
format = DateFormat.LONG;
break;
default:
throw new RapidBeansRuntimeException("Unknown Date Format \"" + sFormat + "\"");
}
int field = -1;
// DATE_FIELD
// DAY_OF_WEEK_FIELD
// DAY_OF_WEEK_IN_MONTH_FIELD
// DAY_OF_YEAR_FIELD
// DAY_OF_WEEK_IN_MONTH_FIELD
// DAY_OF_WEEK_IN_MONTH
// DATE_FIELD
// DAY_OF_WEEK_FIELD
// DAY_OF_YEAR_FIELD
// ERA_FIELD
// HOUR_OF_DAY0_FIELD
// HOUR_OF_DAY1_FIELD
// HOUR0_FIELD
// HOUR1_FIELD
// MILLISECOND_FIELD
// MILLISECOND field
// MINUTE_FIELD
// MONTH_FIELD
// SECOND_FIELD
// TIMEZONE_FIELD
// WEEK_OF_MONTH_FIELD
// WEEK_OF_YEAR_FIELD
// YEAR_FIELD
String sfField = sField + "_FIELD";
switch (sfField.charAt(0)) {
case 'A':
if (!sfField.equals("AM_PM_FIELD")) {
throw new RapidBeansRuntimeException("Unknown date format \"" + sfField + "\"");
}
field = DateFormat.AM_PM_FIELD;
break;
case 'D':
if (!sfField.equals("DATE_FIELD")) {
throw new RapidBeansRuntimeException("Unknown date format \"" + sfField + "\"");
}
field = DateFormat.DATE_FIELD;
break;
case 'M':
if (!sfField.equals("MONTH_FIELD")) {
throw new RapidBeansRuntimeException("Unknown date format \"" + sfField + "\"");
}
field = DateFormat.MONTH_FIELD;
break;
case 'Y':
if (!sfField.equals("YEAR_FIELD")) {
throw new RapidBeansRuntimeException("Unknown date format \"" + sfField + "\"");
}
field = DateFormat.YEAR_FIELD;
break;
default:
throw new RapidBeansRuntimeException("Unknown date field \"" + sField + "\"");
}
return formatDate(this.value, locale, format, field);
}
/**
* @param val
* the date value as Date
* @param locale
* the Locale
* @return a string for the property's value for UI
*/
public static String formatDate(final Date val, final RapidBeansLocale locale) {
return formatDate(val, locale, DateFormat.MEDIUM, -1);
}
/**
* @param date
* the date to format
* @param locale
* the Locale
* @param format
* { DateFormat.SHORT | MEDIUM | LONG }
* @param field
* { DateFormat.DATE_FIELD, MONTH_FIELD, YEAR_FIELD, ...
* @return a string for the property's value for UI
*/
public static String formatDate(final Date date, final RapidBeansLocale locale, final int format, final int field) {
if (date == null) {
return "-";
}
String s = null;
final DateFormat formatter = DateFormat.getDateInstance(format, locale.getLocale());
final StringBuffer sb = new StringBuffer();
if (field > -1) {
final FieldPosition fp = new FieldPosition(field);
formatter.format(date, sb, fp);
s = sb.toString().substring(fp.getBeginIndex(), fp.getEndIndex());
} else {
s = formatter.format(date);
}
return s;
}
/**
* tests if two date intervals overlap.
*
* @param i1From
* start date of interval 1
* @param i1To
* end date of interval 1
* @param i2From
* start date of interval 2
* @param i2To
* end date of interval 2
*
* @return if the intervals overlap or not
*/
public static boolean dateIntervalsOverlap(final Date i1From, final Date i1To, final Date i2From, final Date i2To) {
if (i1From == null) {
throw new IllegalArgumentException("null value given for i1from.");
}
if (i1To == null) {
throw new IllegalArgumentException("null value given for i1To.");
}
if (i2From == null) {
throw new IllegalArgumentException("null value given for i2From.");
}
if (i2To == null) {
throw new IllegalArgumentException("null value given for i2To.");
}
return (((i1From.compareTo(i2From) > 0) && (i1From.compareTo(i2To) < 0)) || (i1To.compareTo(i2From) > 0)
&& (i1To.compareTo(i2To) < 0));
}
}