/*
* Copyright (c) 2017 OBiBa. All rights reserved.
*
* This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0.
*
* 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.obiba.magma.type;
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import javax.annotation.Nullable;
import javax.validation.constraints.NotNull;
import org.obiba.magma.MagmaDate;
import org.obiba.magma.MagmaEngine;
import org.obiba.magma.MagmaRuntimeException;
import org.obiba.magma.Value;
import org.obiba.magma.support.ValueComparator;
public class DateType extends AbstractValueType {
private static final long serialVersionUID = -149385659514790222L;
@SuppressWarnings("StaticNonFinalField")
private static WeakReference<DateType> instance;
/**
* Preferred format.
*/
private static final SimpleDateFormat ISO_8601 = new SimpleDateFormat("yyyy-MM-dd");
/**
* These are used to support common date formats.
*/
private final SimpleDateFormat[] dateFormats = new SimpleDateFormat[] { //
ISO_8601, //
new SimpleDateFormat("yyyy/MM/dd"), //
new SimpleDateFormat("yyyy.MM.dd"), //
new SimpleDateFormat("yyyy MM dd"), //
new SimpleDateFormat("dd-MM-yyyy"), //
new SimpleDateFormat("dd/MM/yyyy"), //
new SimpleDateFormat("dd.MM.yyyy"), //
new SimpleDateFormat("dd MM yyyy") };
private String dateFormatPatterns = "";
private DateType() {
// Force strict year parsing, otherwise 2 digits can be interpreted as a 4 digits year...
for(SimpleDateFormat format : dateFormats) {
format.setLenient(false);
if(dateFormatPatterns.isEmpty()) {
dateFormatPatterns = "'" + format.toPattern() + "'";
} else {
dateFormatPatterns += ", '" + format.toPattern() + "'";
}
}
}
@SuppressWarnings("ConstantConditions")
@edu.umd.cs.findbugs.annotations.SuppressWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE")
@NotNull
public static DateType get() {
if(instance == null || instance.get() == null) {
instance = MagmaEngine.get().registerInstance(new DateType());
}
return instance.get();
}
@Override
public boolean isDateTime() {
return true;
}
@Override
public boolean isNumeric() {
return false;
}
@Override
public Class<?> getJavaClass() {
return MagmaDate.class;
}
@NotNull
@Override
public String getName() {
return "date";
}
@Override
public boolean acceptsJavaClass(@NotNull Class<?> clazz) {
// MAGMA-166: Although the API states that this method should return true for Date instances, it conflicts with
// DateTimeType.
// There is a loss of precision if we map Date instances to this ValueType, so it is safer to not accept these
// types.
return MagmaDate.class.isAssignableFrom(clazz);// || Date.class.isAssignableFrom(clazz) ||
// java.sql.Date.class.isAssignableFrom(clazz) ||
// java.sql.Timestamp.class.isAssignableFrom(clazz) ||
// Calendar.class.isAssignableFrom(clazz);
}
@NotNull
@Override
public Value valueOf(@Nullable String string) {
if(string == null) {
return nullValue();
}
for(SimpleDateFormat format : dateFormats) {
try {
return parseDate(format, string);
} catch(ParseException e) {
// ignored
}
}
throw new MagmaRuntimeException(
"Cannot parse date from string value '" + string + "'. Expected format is one of " + dateFormatPatterns);
}
private Value parseDate(SimpleDateFormat format, String string) throws ParseException {
// DateFormat is not thread safe
synchronized(format) {
return Factory.newValue(this, new MagmaDate(format.parse(string)));
}
}
@NotNull
@Override
public Value valueOf(@Nullable Object object) {
if(object == null) {
return nullValue();
}
Class<?> type = object.getClass();
if(type.equals(MagmaDate.class)) {
return Factory.newValue(this, (Serializable) object);
}
if(type.equals(Date.class)) {
return Factory.newValue(this, new MagmaDate((Date) object));
}
if(Date.class.isAssignableFrom(type)) {
return Factory.newValue(this, new MagmaDate(new Date(((Date) object).getTime())));
}
if(Calendar.class.isAssignableFrom(type)) {
return Factory.newValue(this, new MagmaDate((Calendar) object));
}
if(type.equals(String.class)) {
return valueOf((String) object);
}
if(type.equals(Value.class)) {
Value value = (Value) object;
return value.isNull() ? nullValue() : valueOf(value.getValue());
}
return valueOf(object.toString());
}
@Override
public int compare(Value o1, Value o2) {
return ValueComparator.INSTANCE.compare(o1, o2);
}
/**
* Returns a {@code Value} that holds today's date.
*
* @return a new {@code Value} initialized with today's date.
*/
public Value now() {
return valueOf(new MagmaDate(new Date()));
}
@Override
protected String toString(Object object) {
if(object != null) {
synchronized(ISO_8601) {
return ISO_8601.format(((MagmaDate) object).asDate());
}
}
return null;
}
}