/*
* 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.sql.Timestamp;
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 DateTimeType extends AbstractValueType {
private static final long serialVersionUID = -149385659514790222L;
@SuppressWarnings("StaticNonFinalField")
private static WeakReference<DateTimeType> instance;
/**
* Preferred date time format.
*/
private static final SimpleDateFormat ISO_8601 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX");
/**
* These are used to support other common date time formats.
*/
private final SimpleDateFormat[] dateFormats = new SimpleDateFormat[] { //
ISO_8601, //
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX"), //
new SimpleDateFormat("yyyy-MM-dd'T'HH:mmX"), //
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"), //
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"), //
new SimpleDateFormat("yyyy-MM-dd'T'HH:mmZ"), //
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSzzz"), //
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"), //
new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"), //
new SimpleDateFormat("yyyy.MM.dd HH:mm:ss"), //
new SimpleDateFormat("yyyy MM dd HH:mm:ss"), //
new SimpleDateFormat("yyyy-MM-dd HH:mm"), //
new SimpleDateFormat("yyyy/MM/dd HH:mm"), //
new SimpleDateFormat("yyyy.MM.dd HH:mm"), //
new SimpleDateFormat("yyyy MM dd HH:mm") };
private String dateFormatPatterns = "";
private DateTimeType() {
// 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 DateTimeType get() {
if(instance == null || instance.get() == null) {
instance = MagmaEngine.get().registerInstance(new DateTimeType());
}
return instance.get();
}
@Override
public boolean isDateTime() {
return true;
}
@Override
public boolean isNumeric() {
return false;
}
@Override
public Class<?> getJavaClass() {
return Date.class;
}
@NotNull
@Override
public String getName() {
return "datetime";
}
@Override
public boolean acceptsJavaClass(@NotNull Class<?> clazz) {
return Date.class.isAssignableFrom(clazz) || java.sql.Date.class.isAssignableFrom(clazz) ||
Timestamp.class.isAssignableFrom(clazz) || Calendar.class.isAssignableFrom(clazz);
}
@Override
public String toString(Object object) {
// DateFormat is not thread safe
synchronized(ISO_8601) {
return ISO_8601.format((Date) object);
}
}
@NotNull
@Override
public Value valueOf(@Nullable String string) {
if(string == null) {
return nullValue();
}
String dateToParse = string;
if(string.endsWith("Z")) {
// Java before 7 does not support the 'Zulu' timezone (Z). Replace it with a SimpleDateFormat-friendly timezone
dateToParse = string.replaceFirst("Z$", "UTC");
}
for(SimpleDateFormat format : dateFormats) {
try {
return parseDate(format, dateToParse);
} 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, format.parse(string));
}
}
@NotNull
@Override
public Value valueOf(@Nullable Object object) {
if(object == null) {
return nullValue();
}
Class<?> type = object.getClass();
if(type.equals(Date.class)) {
return Factory.newValue(this, (Serializable) object);
}
if(type.equals(MagmaDate.class)) {
return Factory.newValue(this, ((MagmaDate) object).asDate());
}
if(Date.class.isAssignableFrom(type)) {
return Factory.newValue(this, new Date(((Date) object).getTime()));
}
if(Calendar.class.isAssignableFrom(type)) {
Calendar c = (Calendar) object;
return Factory.newValue(this, c.getTime());
}
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 Date());
}
}