/** * AnalyzerBeans * Copyright (C) 2014 Neopost - Customer Information Management * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * * 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 copy of the GNU Lesser General Public License * along with this distribution; if not, write to: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ package org.eobjects.analyzer.beans.convert; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import javax.inject.Inject; import org.eobjects.analyzer.beans.api.Categorized; import org.eobjects.analyzer.beans.api.Configured; import org.eobjects.analyzer.beans.api.Description; import org.eobjects.analyzer.beans.api.Initialize; import org.eobjects.analyzer.beans.api.OutputColumns; import org.eobjects.analyzer.beans.api.Transformer; import org.eobjects.analyzer.beans.api.TransformerBean; import org.eobjects.analyzer.beans.categories.ConversionCategory; import org.eobjects.analyzer.beans.categories.DateAndTimeCategory; import org.eobjects.analyzer.data.InputColumn; import org.eobjects.analyzer.data.InputRow; import org.eobjects.analyzer.util.convert.NowDate; import org.eobjects.analyzer.util.convert.TodayDate; import org.eobjects.analyzer.util.convert.YesterdayDate; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; /** * Attempts to convert anything to a Date value */ @TransformerBean("Convert to date") @Description("Converts anything to a date (or null).") @Categorized({ ConversionCategory.class, DateAndTimeCategory.class }) public class ConvertToDateTransformer implements Transformer<Date> { private static final String[] prototypePatterns = { "yyyy-MM-dd", "dd-MM-yyyy", "MM-dd-yyyy" }; private static final DateTimeFormatter NUMBER_BASED_DATE_FORMAT_LONG = DateTimeFormat.forPattern("yyyyMMdd"); private static final DateTimeFormatter NUMBER_BASED_DATE_FORMAT_SHORT = DateTimeFormat.forPattern("yyMMdd"); private static ConvertToDateTransformer internalInstance; @Inject @Configured(order = 1) InputColumn<?>[] input; @Inject @Configured(required = false, order = 2) Date nullReplacement; @Inject @Configured(required = false, order = 3) String[] dateMasks; private DateTimeFormatter[] _dateTimeFormatters; public static ConvertToDateTransformer getInternalInstance() { if (internalInstance == null) { internalInstance = new ConvertToDateTransformer(); internalInstance.init(); } return internalInstance; } public ConvertToDateTransformer() { dateMasks = getDefaultDateMasks(); } @Initialize public void init() { if (dateMasks == null) { dateMasks = getDefaultDateMasks(); } _dateTimeFormatters = new DateTimeFormatter[dateMasks.length]; for (int i = 0; i < dateMasks.length; i++) { final String dateMask = dateMasks[i]; _dateTimeFormatters[i] = DateTimeFormat.forPattern(dateMask); } } @Override public OutputColumns getOutputColumns() { String[] names = new String[input.length]; for (int i = 0; i < names.length; i++) { names[i] = input[i].getName() + " (as date)"; } return new OutputColumns(names); } @Override public Date[] transform(InputRow inputRow) { Date[] result = new Date[input.length]; for (int i = 0; i < input.length; i++) { Object value = inputRow.getValue(input[i]); Date d = transformValue(value); if (d == null) { d = nullReplacement; } result[i] = d; } return result; } public Date transformValue(Object value) { Date d = null; if (value != null) { if (value instanceof Date) { d = (Date) value; } else if (value instanceof Calendar) { d = ((Calendar) value).getTime(); } else if (value instanceof String) { d = convertFromString((String) value); } else if (value instanceof Number) { d = convertFromNumber((Number) value, true); } } return d; } protected Date convertFromString(final String value) { if ("now()".equalsIgnoreCase(value)) { return new NowDate(); } if ("today()".equalsIgnoreCase(value)) { return new TodayDate(); } if ("yesterday()".equalsIgnoreCase(value)) { return new YesterdayDate(); } for (DateTimeFormatter formatter : _dateTimeFormatters) { try { return formatter.parseDateTime(value).toDate(); } catch (Exception e) { // proceed to next formatter } } try { long longValue = Long.parseLong(value); return convertFromNumber(longValue, false); } catch (NumberFormatException e) { // do nothing, proceed to dateFormat parsing } // try also with SimpleDateFormat since it is more fault tolerant in // millisecond parsing final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.S"); try { return format.parse(value); } catch (ParseException e) { // do nothing } return null; } protected Date convertFromNumber(Number value) { return convertFromNumber(value, true); } protected Date convertFromNumber(Number value, boolean tryDateTimeFormatters) { Number numberValue = (Number) value; long longValue = numberValue.longValue(); String stringValue = Long.toString(longValue); if (tryDateTimeFormatters) { for (int i = 0; i < _dateTimeFormatters.length; i++) { String dateMask = dateMasks[i]; boolean isPotentialNumberDateMask = dateMask.indexOf("-") == -1 && dateMask.indexOf(".") == -1 && dateMask.indexOf("/") == -1; if (isPotentialNumberDateMask) { DateTimeFormatter formatter = _dateTimeFormatters[i]; try { return formatter.parseDateTime(stringValue).toDate(); } catch (Exception e) { // proceed to next formatter } } } } // test if the number is actually a format of the type yyyyMMdd if (stringValue.length() == 8 && (stringValue.startsWith("1") || stringValue.startsWith("2"))) { try { return NUMBER_BASED_DATE_FORMAT_LONG.parseDateTime(stringValue).toDate(); } catch (Exception e) { // do nothing, proceed to next method of conversion } } // test if the number is actually a format of the type yyMMdd if (stringValue.length() == 6) { try { return NUMBER_BASED_DATE_FORMAT_SHORT.parseDateTime(stringValue).toDate(); } catch (Exception e) { // do nothing, proceed to next method of conversion } } if (longValue > 5000000) { // this number is most probably amount of milliseconds since // 1970 return new Date(longValue); } else { // this number is most probably the amount of days since // 1970 return new Date(longValue * 1000 * 60 * 60 * 24); } } private String[] getDefaultDateMasks() { final List<String> defaultDateMasks = new ArrayList<String>(); defaultDateMasks.add("yyyy-MM-dd HH:mm:ss.S"); defaultDateMasks.add("yyyy-MM-dd HH:mm:ss"); defaultDateMasks.add("yyyy-MM-dd HH:mm"); defaultDateMasks.add("yyyyMMddHHmmssZ"); defaultDateMasks.add("yyMMddHHmmssZ"); for (String string : prototypePatterns) { defaultDateMasks.add(string); string = string.replaceAll("\\-", "\\."); defaultDateMasks.add(string); string = string.replaceAll("\\.", "\\/"); defaultDateMasks.add(string); } return defaultDateMasks.toArray(new String[defaultDateMasks.size()]); } public String[] getDateMasks() { return dateMasks; } public Date getNullReplacement() { return nullReplacement; } public void setNullReplacement(Date nullReplacement) { this.nullReplacement = nullReplacement; } }