Dates: More strict parsing of ISO dates

If you are using the default date or the named identifiers of dates,
the current implementation was allowed to read a year with only one
digit. In order to make this more strict, this fixes a year to be at
least 4 digits. Same applies for month, day, hour, minute, seconds.

Also the new default is `strictDateOptionalTime` for indices created
with Elasticsearch 2.0 or newer.

In addition a couple of not exposed date formats have been exposed, as they
have been mentioned in the documentation.

Closes 
This commit is contained in:
Alexander Reelsen 2015-07-07 09:30:45 +02:00
parent 35ddc749b1
commit b612cab96a
16 changed files with 2709 additions and 68 deletions
core/src
main/java/org
test/java/org/elasticsearch
docs/reference

@ -118,6 +118,8 @@ public class Joda {
formatter = ISODateTimeFormat.ordinalDateTimeNoMillis();
} else if ("time".equals(input)) {
formatter = ISODateTimeFormat.time();
} else if ("timeNoMillis".equals(input) || "time_no_millis".equals(input)) {
formatter = ISODateTimeFormat.timeNoMillis();
} else if ("tTime".equals(input) || "t_time".equals(input)) {
formatter = ISODateTimeFormat.tTime();
} else if ("tTimeNoMillis".equals(input) || "t_time_no_millis".equals(input)) {
@ -126,10 +128,14 @@ public class Joda {
formatter = ISODateTimeFormat.weekDate();
} else if ("weekDateTime".equals(input) || "week_date_time".equals(input)) {
formatter = ISODateTimeFormat.weekDateTime();
} else if ("weekDateTimeNoMillis".equals(input) || "week_date_time_no_millis".equals(input)) {
formatter = ISODateTimeFormat.weekDateTimeNoMillis();
} else if ("weekyear".equals(input) || "week_year".equals(input)) {
formatter = ISODateTimeFormat.weekyear();
} else if ("weekyearWeek".equals(input)) {
} else if ("weekyearWeek".equals(input) || "weekyear_week".equals(input)) {
formatter = ISODateTimeFormat.weekyearWeek();
} else if ("weekyearWeekDay".equals(input) || "weekyear_week_day".equals(input)) {
formatter = ISODateTimeFormat.weekyearWeekDay();
} else if ("year".equals(input)) {
formatter = ISODateTimeFormat.year();
} else if ("yearMonth".equals(input) || "year_month".equals(input)) {
@ -140,6 +146,77 @@ public class Joda {
formatter = new DateTimeFormatterBuilder().append(new EpochTimePrinter(false), new EpochTimeParser(false)).toFormatter();
} else if ("epoch_millis".equals(input)) {
formatter = new DateTimeFormatterBuilder().append(new EpochTimePrinter(true), new EpochTimeParser(true)).toFormatter();
// strict date formats here, must be at least 4 digits for year and two for months and two for day
} else if ("strictBasicWeekDate".equals(input) || "strict_basic_week_date".equals(input)) {
formatter = StrictISODateTimeFormat.basicWeekDate();
} else if ("strictBasicWeekDateTime".equals(input) || "strict_basic_week_date_time".equals(input)) {
formatter = StrictISODateTimeFormat.basicWeekDateTime();
} else if ("strictBasicWeekDateTimeNoMillis".equals(input) || "strict_basic_week_date_time_no_millis".equals(input)) {
formatter = StrictISODateTimeFormat.basicWeekDateTimeNoMillis();
} else if ("strictDate".equals(input) || "strict_date".equals(input)) {
formatter = StrictISODateTimeFormat.date();
} else if ("strictDateHour".equals(input) || "strict_date_hour".equals(input)) {
formatter = StrictISODateTimeFormat.dateHour();
} else if ("strictDateHourMinute".equals(input) || "strict_date_hour_minute".equals(input)) {
formatter = StrictISODateTimeFormat.dateHourMinute();
} else if ("strictDateHourMinuteSecond".equals(input) || "strict_date_hour_minute_second".equals(input)) {
formatter = StrictISODateTimeFormat.dateHourMinuteSecond();
} else if ("strictDateHourMinuteSecondFraction".equals(input) || "strict_date_hour_minute_second_fraction".equals(input)) {
formatter = StrictISODateTimeFormat.dateHourMinuteSecondFraction();
} else if ("strictDateHourMinuteSecondMillis".equals(input) || "strict_date_hour_minute_second_millis".equals(input)) {
formatter = StrictISODateTimeFormat.dateHourMinuteSecondMillis();
} else if ("strictDateOptionalTime".equals(input) || "strict_date_optional_time".equals(input)) {
// in this case, we have a separate parser and printer since the dataOptionalTimeParser can't print
// this sucks we should use the root local by default and not be dependent on the node
return new FormatDateTimeFormatter(input,
StrictISODateTimeFormat.dateOptionalTimeParser().withZone(DateTimeZone.UTC),
StrictISODateTimeFormat.dateTime().withZone(DateTimeZone.UTC), locale);
} else if ("strictDateTime".equals(input) || "strict_date_time".equals(input)) {
formatter = StrictISODateTimeFormat.dateTime();
} else if ("strictDateTimeNoMillis".equals(input) || "strict_date_time_no_millis".equals(input)) {
formatter = StrictISODateTimeFormat.dateTimeNoMillis();
} else if ("strictHour".equals(input) || "strict_hour".equals(input)) {
formatter = StrictISODateTimeFormat.hour();
} else if ("strictHourMinute".equals(input) || "strict_hour_minute".equals(input)) {
formatter = StrictISODateTimeFormat.hourMinute();
} else if ("strictHourMinuteSecond".equals(input) || "strict_hour_minute_second".equals(input)) {
formatter = StrictISODateTimeFormat.hourMinuteSecond();
} else if ("strictHourMinuteSecondFraction".equals(input) || "strict_hour_minute_second_fraction".equals(input)) {
formatter = StrictISODateTimeFormat.hourMinuteSecondFraction();
} else if ("strictHourMinuteSecondMillis".equals(input) || "strict_hour_minute_second_millis".equals(input)) {
formatter = StrictISODateTimeFormat.hourMinuteSecondMillis();
} else if ("strictOrdinalDate".equals(input) || "strict_ordinal_date".equals(input)) {
formatter = StrictISODateTimeFormat.ordinalDate();
} else if ("strictOrdinalDateTime".equals(input) || "strict_ordinal_date_time".equals(input)) {
formatter = StrictISODateTimeFormat.ordinalDateTime();
} else if ("strictOrdinalDateTimeNoMillis".equals(input) || "strict_ordinal_date_time_no_millis".equals(input)) {
formatter = StrictISODateTimeFormat.ordinalDateTimeNoMillis();
} else if ("strictTime".equals(input) || "strict_time".equals(input)) {
formatter = StrictISODateTimeFormat.time();
} else if ("strictTimeNoMillis".equals(input) || "strict_time_no_millis".equals(input)) {
formatter = StrictISODateTimeFormat.timeNoMillis();
} else if ("strictTTime".equals(input) || "strict_t_time".equals(input)) {
formatter = StrictISODateTimeFormat.tTime();
} else if ("strictTTimeNoMillis".equals(input) || "strict_t_time_no_millis".equals(input)) {
formatter = StrictISODateTimeFormat.tTimeNoMillis();
} else if ("strictWeekDate".equals(input) || "strict_week_date".equals(input)) {
formatter = StrictISODateTimeFormat.weekDate();
} else if ("strictWeekDateTime".equals(input) || "strict_week_date_time".equals(input)) {
formatter = StrictISODateTimeFormat.weekDateTime();
} else if ("strictWeekDateTimeNoMillis".equals(input) || "strict_week_date_time_no_millis".equals(input)) {
formatter = StrictISODateTimeFormat.weekDateTimeNoMillis();
} else if ("strictWeekyear".equals(input) || "strict_weekyear".equals(input)) {
formatter = StrictISODateTimeFormat.weekyear();
} else if ("strictWeekyearWeek".equals(input) || "strict_weekyear_week".equals(input)) {
formatter = StrictISODateTimeFormat.weekyearWeek();
} else if ("strictWeekyearWeekDay".equals(input) || "strict_weekyear_week_day".equals(input)) {
formatter = StrictISODateTimeFormat.weekyearWeekDay();
} else if ("strictYear".equals(input) || "strict_year".equals(input)) {
formatter = StrictISODateTimeFormat.year();
} else if ("strictYearMonth".equals(input) || "strict_year_month".equals(input)) {
formatter = StrictISODateTimeFormat.yearMonth();
} else if ("strictYearMonthDay".equals(input) || "strict_year_month_day".equals(input)) {
formatter = StrictISODateTimeFormat.yearMonthDay();
} else if (Strings.hasLength(input) && input.contains("||")) {
String[] formats = Strings.delimitedListToStringArray(input, "||");
DateTimeParser[] parsers = new DateTimeParser[formats.length];
@ -171,6 +248,38 @@ public class Joda {
return new FormatDateTimeFormatter(input, formatter.withZone(DateTimeZone.UTC), locale);
}
public static FormatDateTimeFormatter getStrictStandardDateFormatter() {
// 2014/10/10
DateTimeFormatter shortFormatter = new DateTimeFormatterBuilder()
.appendFixedDecimal(DateTimeFieldType.year(), 4)
.appendLiteral('/')
.appendFixedDecimal(DateTimeFieldType.monthOfYear(), 2)
.appendLiteral('/')
.appendFixedDecimal(DateTimeFieldType.dayOfMonth(), 2)
.toFormatter()
.withZoneUTC();
// 2014/10/10 12:12:12
DateTimeFormatter longFormatter = new DateTimeFormatterBuilder()
.appendFixedDecimal(DateTimeFieldType.year(), 4)
.appendLiteral('/')
.appendFixedDecimal(DateTimeFieldType.monthOfYear(), 2)
.appendLiteral('/')
.appendFixedDecimal(DateTimeFieldType.dayOfMonth(), 2)
.appendLiteral(' ')
.appendFixedSignedDecimal(DateTimeFieldType.hourOfDay(), 2)
.appendLiteral(':')
.appendFixedSignedDecimal(DateTimeFieldType.minuteOfHour(), 2)
.appendLiteral(':')
.appendFixedSignedDecimal(DateTimeFieldType.secondOfMinute(), 2)
.toFormatter()
.withZoneUTC();
DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder().append(longFormatter.withZone(DateTimeZone.UTC).getPrinter(), new DateTimeParser[] {longFormatter.getParser(), shortFormatter.getParser()});
return new FormatDateTimeFormatter("yyyy/MM/dd HH:mm:ss||yyyy/MM/dd", builder.toFormatter().withZone(DateTimeZone.UTC), Locale.ROOT);
}
public static final DurationFieldType Quarters = new DurationFieldType("quarters") {
private static final long serialVersionUID = -8167713675442491871L;

@ -69,7 +69,8 @@ public class DateFieldMapper extends NumberFieldMapper {
public static final String CONTENT_TYPE = "date";
public static class Defaults extends NumberFieldMapper.Defaults {
public static final FormatDateTimeFormatter DATE_TIME_FORMATTER = Joda.forPattern("dateOptionalTime||epoch_millis", Locale.ROOT);
public static final FormatDateTimeFormatter DATE_TIME_FORMATTER = Joda.forPattern("strictDateOptionalTime||epoch_millis", Locale.ROOT);
public static final FormatDateTimeFormatter DATE_TIME_FORMATTER_BEFORE_2_0 = Joda.forPattern("dateOptionalTime", Locale.ROOT);
public static final TimeUnit TIME_UNIT = TimeUnit.MILLISECONDS;
public static final DateFieldType FIELD_TYPE = new DateFieldType();
@ -123,15 +124,13 @@ public class DateFieldMapper extends NumberFieldMapper {
}
protected void setupFieldType(BuilderContext context) {
FormatDateTimeFormatter dateTimeFormatter = fieldType().dateTimeFormatter;
// TODO MOVE ME OUTSIDE OF THIS SPACE?
if (Version.indexCreated(context.indexSettings()).before(Version.V_2_0_0)) {
boolean includesEpochFormatter = dateTimeFormatter.format().contains("epoch_");
if (!includesEpochFormatter) {
String format = fieldType().timeUnit().equals(TimeUnit.SECONDS) ? "epoch_second" : "epoch_millis";
fieldType().setDateTimeFormatter(Joda.forPattern(format + "||" + dateTimeFormatter.format()));
}
if (Version.indexCreated(context.indexSettings()).before(Version.V_2_0_0) &&
!fieldType().dateTimeFormatter().format().contains("epoch_")) {
String format = fieldType().timeUnit().equals(TimeUnit.SECONDS) ? "epoch_second" : "epoch_millis";
fieldType().setDateTimeFormatter(Joda.forPattern(format + "||" + fieldType().dateTimeFormatter().format()));
}
FormatDateTimeFormatter dateTimeFormatter = fieldType().dateTimeFormatter;
if (!locale.equals(dateTimeFormatter.locale())) {
fieldType().setDateTimeFormatter(new FormatDateTimeFormatter(dateTimeFormatter.format(), dateTimeFormatter.parser(), dateTimeFormatter.printer(), locale));
}
@ -159,6 +158,7 @@ public class DateFieldMapper extends NumberFieldMapper {
public Mapper.Builder<?, ?> parse(String name, Map<String, Object> node, ParserContext parserContext) throws MapperParsingException {
DateFieldMapper.Builder builder = dateField(name);
parseNumberField(builder, name, node, parserContext);
boolean configuredFormat = false;
for (Iterator<Map.Entry<String, Object>> iterator = node.entrySet().iterator(); iterator.hasNext();) {
Map.Entry<String, Object> entry = iterator.next();
String propName = Strings.toUnderscoreCase(entry.getKey());
@ -171,6 +171,7 @@ public class DateFieldMapper extends NumberFieldMapper {
iterator.remove();
} else if (propName.equals("format")) {
builder.dateTimeFormatter(parseDateTimeFormatter(propNode));
configuredFormat = true;
iterator.remove();
} else if (propName.equals("numeric_resolution")) {
builder.timeUnit(TimeUnit.valueOf(propNode.toString().toUpperCase(Locale.ROOT)));
@ -180,6 +181,13 @@ public class DateFieldMapper extends NumberFieldMapper {
iterator.remove();
}
}
if (!configuredFormat) {
if (parserContext.indexVersionCreated().onOrAfter(Version.V_2_0_0)) {
builder.dateTimeFormatter(Defaults.DATE_TIME_FORMATTER);
} else {
builder.dateTimeFormatter(Defaults.DATE_TIME_FORMATTER_BEFORE_2_0);
}
}
return builder;
}
}

@ -24,14 +24,12 @@ import org.apache.lucene.document.NumericDocValuesField;
import org.apache.lucene.index.IndexOptions;
import org.elasticsearch.Version;
import org.elasticsearch.action.TimestampParsingException;
import org.elasticsearch.common.Explicit;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.joda.FormatDateTimeFormatter;
import org.elasticsearch.common.joda.Joda;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.analysis.NamedAnalyzer;
import org.elasticsearch.index.analysis.NumericDateAnalyzer;
import org.elasticsearch.index.fielddata.FieldDataType;
import org.elasticsearch.index.mapper.MappedFieldType;
@ -41,10 +39,8 @@ import org.elasticsearch.index.mapper.MergeMappingException;
import org.elasticsearch.index.mapper.MergeResult;
import org.elasticsearch.index.mapper.ParseContext;
import org.elasticsearch.index.mapper.MetadataFieldMapper;
import org.elasticsearch.index.mapper.core.AbstractFieldMapper;
import org.elasticsearch.index.mapper.core.DateFieldMapper;
import org.elasticsearch.index.mapper.core.LongFieldMapper;
import org.elasticsearch.index.mapper.core.NumberFieldMapper;
import java.io.IOException;
import java.util.Iterator;
@ -59,15 +55,16 @@ public class TimestampFieldMapper extends MetadataFieldMapper {
public static final String NAME = "_timestamp";
public static final String CONTENT_TYPE = "_timestamp";
public static final String DEFAULT_DATE_TIME_FORMAT = "epoch_millis||dateOptionalTime";
public static final String DEFAULT_DATE_TIME_FORMAT = "epoch_millis||strictDateOptionalTime";
public static class Defaults extends DateFieldMapper.Defaults {
public static final String NAME = "_timestamp";
// TODO: this should be removed
public static final MappedFieldType PRE_20_FIELD_TYPE;
public static final FormatDateTimeFormatter DATE_TIME_FORMATTER = Joda.forPattern(DEFAULT_DATE_TIME_FORMAT);
public static final TimestampFieldType PRE_20_FIELD_TYPE;
public static final TimestampFieldType FIELD_TYPE = new TimestampFieldType();
public static final FormatDateTimeFormatter DATE_TIME_FORMATTER = Joda.forPattern(DEFAULT_DATE_TIME_FORMAT);
public static final FormatDateTimeFormatter DATE_TIME_FORMATTER_BEFORE_2_0 = Joda.forPattern("epoch_millis||dateOptionalTime");
static {
FIELD_TYPE.setStored(true);
@ -82,6 +79,9 @@ public class TimestampFieldMapper extends MetadataFieldMapper {
PRE_20_FIELD_TYPE = FIELD_TYPE.clone();
PRE_20_FIELD_TYPE.setStored(false);
PRE_20_FIELD_TYPE.setHasDocValues(false);
PRE_20_FIELD_TYPE.setDateTimeFormatter(DATE_TIME_FORMATTER_BEFORE_2_0);
PRE_20_FIELD_TYPE.setIndexAnalyzer(NumericDateAnalyzer.buildNamedAnalyzer(DATE_TIME_FORMATTER_BEFORE_2_0, Defaults.PRECISION_STEP_64_BIT));
PRE_20_FIELD_TYPE.setSearchAnalyzer(NumericDateAnalyzer.buildNamedAnalyzer(DATE_TIME_FORMATTER_BEFORE_2_0, Integer.MAX_VALUE));
PRE_20_FIELD_TYPE.freeze();
}
@ -146,8 +146,23 @@ public class TimestampFieldMapper extends MetadataFieldMapper {
if (explicitStore == false && context.indexCreatedVersion().before(Version.V_2_0_0)) {
fieldType.setStored(false);
}
if (fieldType().dateTimeFormatter().equals(Defaults.DATE_TIME_FORMATTER)) {
fieldType().setDateTimeFormatter(getDateTimeFormatter(context.indexSettings()));
}
setupFieldType(context);
return new TimestampFieldMapper(fieldType, defaultFieldType, enabledState, path, defaultTimestamp, ignoreMissing, context.indexSettings());
return new TimestampFieldMapper(fieldType, defaultFieldType, enabledState, path, defaultTimestamp,
ignoreMissing, context.indexSettings());
}
}
private static FormatDateTimeFormatter getDateTimeFormatter(Settings indexSettings) {
Version indexCreated = Version.indexCreated(indexSettings);
if (indexCreated.onOrAfter(Version.V_2_0_0)) {
return Defaults.DATE_TIME_FORMATTER;
} else {
return Defaults.DATE_TIME_FORMATTER_BEFORE_2_0;
}
}
@ -341,7 +356,9 @@ public class TimestampFieldMapper extends MetadataFieldMapper {
if (indexCreatedBefore2x && (includeDefaults || path != Defaults.PATH)) {
builder.field("path", path);
}
if (includeDefaults || !fieldType().dateTimeFormatter().format().equals(Defaults.DATE_TIME_FORMATTER.format())) {
// different format handling depending on index version
String defaultDateFormat = indexCreatedBefore2x ? Defaults.DATE_TIME_FORMATTER_BEFORE_2_0.format() : Defaults.DATE_TIME_FORMATTER.format();
if (includeDefaults || !fieldType().dateTimeFormatter().format().equals(defaultDateFormat)) {
builder.field("format", fieldType().dateTimeFormatter().format());
}
if (includeDefaults || !Defaults.DEFAULT_TIMESTAMP.equals(defaultTimestamp)) {

@ -49,7 +49,7 @@ public class RootObjectMapper extends ObjectMapper {
public static final FormatDateTimeFormatter[] DYNAMIC_DATE_TIME_FORMATTERS =
new FormatDateTimeFormatter[]{
DateFieldMapper.Defaults.DATE_TIME_FORMATTER,
Joda.forPattern("yyyy/MM/dd HH:mm:ss||yyyy/MM/dd")
Joda.getStrictStandardDateFormatter()
};
public static final boolean DATE_DETECTION = true;
public static final boolean NUMERIC_DETECTION = false;

@ -38,7 +38,7 @@ import com.google.common.collect.ImmutableList;
*/
public class SnapshotInfo implements ToXContent, Streamable {
private static final FormatDateTimeFormatter DATE_TIME_FORMATTER = Joda.forPattern("dateOptionalTime");
private static final FormatDateTimeFormatter DATE_TIME_FORMATTER = Joda.forPattern("strictDateOptionalTime");
private String name;

File diff suppressed because it is too large Load Diff

@ -22,8 +22,11 @@ package org.elasticsearch.deps.joda;
import org.elasticsearch.common.joda.FormatDateTimeFormatter;
import org.elasticsearch.common.joda.Joda;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.mapper.core.DateFieldMapper;
import org.elasticsearch.index.mapper.object.RootObjectMapper;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.joda.time.DateTime;
import org.joda.time.DateTimeFieldType;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDateTime;
import org.joda.time.MutableDateTime;
@ -361,6 +364,368 @@ public class SimpleJodaTests extends ElasticsearchTestCase {
assertThat(secondsDateTime.getMillis(), is(1234567890000l));
}
public void testThatDefaultFormatterChecksForCorrectYearLength() throws Exception {
// if no strict version is tested, this means the date format is already strict by itself
// yyyyMMdd
assertValidDateFormatParsing("basicDate", "20140303");
assertDateFormatParsingThrowingException("basicDate", "2010303");
// yyyyMMddT'HHmmss.SSSZ
assertValidDateFormatParsing("basicDateTime", "20140303T124343.123Z");
assertValidDateFormatParsing("basicDateTime", "00050303T124343.123Z");
assertDateFormatParsingThrowingException("basicDateTime", "50303T124343.123Z");
// yyyyMMddT'HHmmssZ
assertValidDateFormatParsing("basicDateTimeNoMillis", "20140303T124343Z");
assertValidDateFormatParsing("basicDateTimeNoMillis", "00050303T124343Z");
assertDateFormatParsingThrowingException("basicDateTimeNoMillis", "50303T124343Z");
// yyyyDDD
assertValidDateFormatParsing("basicOrdinalDate", "0005165");
assertDateFormatParsingThrowingException("basicOrdinalDate", "5165");
// yyyyDDDT'HHmmss.SSSZ
assertValidDateFormatParsing("basicOrdinalDateTime", "0005165T124343.123Z");
assertValidDateFormatParsing("basicOrdinalDateTime", "0005165T124343.123Z");
assertDateFormatParsingThrowingException("basicOrdinalDateTime", "5165T124343.123Z");
// yyyyDDDT'HHmmssZ
assertValidDateFormatParsing("basicOrdinalDateTimeNoMillis", "0005165T124343Z");
assertValidDateFormatParsing("basicOrdinalDateTimeNoMillis", "0005165T124343Z");
assertDateFormatParsingThrowingException("basicOrdinalDateTimeNoMillis", "5165T124343Z");
// HHmmss.SSSZ
assertValidDateFormatParsing("basicTime", "090909.123Z");
assertDateFormatParsingThrowingException("basicTime", "90909.123Z");
// HHmmssZ
assertValidDateFormatParsing("basicTimeNoMillis", "090909Z");
assertDateFormatParsingThrowingException("basicTimeNoMillis", "90909Z");
// 'THHmmss.SSSZ
assertValidDateFormatParsing("basicTTime", "T090909.123Z");
assertDateFormatParsingThrowingException("basicTTime", "T90909.123Z");
// THHmmssZ
assertValidDateFormatParsing("basicTTimeNoMillis", "T090909Z");
assertDateFormatParsingThrowingException("basicTTimeNoMillis", "T90909Z");
// xxxxW'wwe
assertValidDateFormatParsing("basicWeekDate", "0005W414");
assertValidDateFormatParsing("basicWeekDate", "5W414", "0005W414");
assertDateFormatParsingThrowingException("basicWeekDate", "5W14");
assertValidDateFormatParsing("strictBasicWeekDate", "0005W414");
assertDateFormatParsingThrowingException("strictBasicWeekDate", "0005W47");
assertDateFormatParsingThrowingException("strictBasicWeekDate", "5W414");
assertDateFormatParsingThrowingException("strictBasicWeekDate", "5W14");
// xxxxW'wweT'HHmmss.SSSZ
assertValidDateFormatParsing("basicWeekDateTime", "0005W414T124343.123Z");
assertValidDateFormatParsing("basicWeekDateTime", "5W414T124343.123Z", "0005W414T124343.123Z");
assertDateFormatParsingThrowingException("basicWeekDateTime", "5W14T124343.123Z");
assertValidDateFormatParsing("strictBasicWeekDateTime", "0005W414T124343.123Z");
assertDateFormatParsingThrowingException("strictBasicWeekDateTime", "0005W47T124343.123Z");
assertDateFormatParsingThrowingException("strictBasicWeekDateTime", "5W414T124343.123Z");
assertDateFormatParsingThrowingException("strictBasicWeekDateTime", "5W14T124343.123Z");
// xxxxW'wweT'HHmmssZ
assertValidDateFormatParsing("basicWeekDateTimeNoMillis", "0005W414T124343Z");
assertValidDateFormatParsing("basicWeekDateTimeNoMillis", "5W414T124343Z", "0005W414T124343Z");
assertDateFormatParsingThrowingException("basicWeekDateTimeNoMillis", "5W14T124343Z");
assertValidDateFormatParsing("strictBasicWeekDateTimeNoMillis", "0005W414T124343Z");
assertDateFormatParsingThrowingException("strictBasicWeekDateTimeNoMillis", "0005W47T124343Z");
assertDateFormatParsingThrowingException("strictBasicWeekDateTimeNoMillis", "5W414T124343Z");
assertDateFormatParsingThrowingException("strictBasicWeekDateTimeNoMillis", "5W14T124343Z");
// yyyy-MM-dd
assertValidDateFormatParsing("date", "0005-06-03");
assertValidDateFormatParsing("date", "5-6-3", "0005-06-03");
assertValidDateFormatParsing("strictDate", "0005-06-03");
assertDateFormatParsingThrowingException("strictDate", "5-6-3");
assertDateFormatParsingThrowingException("strictDate", "0005-06-3");
assertDateFormatParsingThrowingException("strictDate", "0005-6-03");
assertDateFormatParsingThrowingException("strictDate", "5-06-03");
// yyyy-MM-dd'T'HH
assertValidDateFormatParsing("dateHour", "0005-06-03T12");
assertValidDateFormatParsing("dateHour", "5-6-3T1", "0005-06-03T01");
assertValidDateFormatParsing("strictDateHour", "0005-06-03T12");
assertDateFormatParsingThrowingException("strictDateHour", "5-6-3T1");
// yyyy-MM-dd'T'HH:mm
assertValidDateFormatParsing("dateHourMinute", "0005-06-03T12:12");
assertValidDateFormatParsing("dateHourMinute", "5-6-3T12:1", "0005-06-03T12:01");
assertValidDateFormatParsing("strictDateHourMinute", "0005-06-03T12:12");
assertDateFormatParsingThrowingException("strictDateHourMinute", "5-6-3T12:1");
// yyyy-MM-dd'T'HH:mm:ss
assertValidDateFormatParsing("dateHourMinuteSecond", "0005-06-03T12:12:12");
assertValidDateFormatParsing("dateHourMinuteSecond", "5-6-3T12:12:1", "0005-06-03T12:12:01");
assertValidDateFormatParsing("strictDateHourMinuteSecond", "0005-06-03T12:12:12");
assertDateFormatParsingThrowingException("strictDateHourMinuteSecond", "5-6-3T12:12:1");
// yyyy-MM-ddT'HH:mm:ss.SSS
assertValidDateFormatParsing("dateHourMinuteSecondFraction", "0005-06-03T12:12:12.123");
assertValidDateFormatParsing("dateHourMinuteSecondFraction", "5-6-3T12:12:1.123", "0005-06-03T12:12:01.123");
assertValidDateFormatParsing("dateHourMinuteSecondFraction", "5-6-3T12:12:1.1", "0005-06-03T12:12:01.100");
assertValidDateFormatParsing("strictDateHourMinuteSecondFraction", "0005-06-03T12:12:12.123");
assertDateFormatParsingThrowingException("strictDateHourMinuteSecondFraction", "5-6-3T12:12:12.1");
assertDateFormatParsingThrowingException("strictDateHourMinuteSecondFraction", "5-6-3T12:12:12.12");
assertValidDateFormatParsing("dateHourMinuteSecondMillis", "0005-06-03T12:12:12.123");
assertValidDateFormatParsing("dateHourMinuteSecondMillis", "5-6-3T12:12:1.123", "0005-06-03T12:12:01.123");
assertValidDateFormatParsing("dateHourMinuteSecondMillis", "5-6-3T12:12:1.1", "0005-06-03T12:12:01.100");
assertValidDateFormatParsing("strictDateHourMinuteSecondMillis", "0005-06-03T12:12:12.123");
assertDateFormatParsingThrowingException("strictDateHourMinuteSecondMillis", "5-6-3T12:12:12.1");
assertDateFormatParsingThrowingException("strictDateHourMinuteSecondMillis", "5-6-3T12:12:12.12");
// yyyy-MM-dd'T'HH:mm:ss.SSSZ
assertValidDateFormatParsing("dateOptionalTime", "2014-03-03", "2014-03-03T00:00:00.000Z");
assertValidDateFormatParsing("dateOptionalTime", "1257-3-03", "1257-03-03T00:00:00.000Z");
assertValidDateFormatParsing("dateOptionalTime", "0005-03-3", "0005-03-03T00:00:00.000Z");
assertValidDateFormatParsing("dateOptionalTime", "5-03-03", "0005-03-03T00:00:00.000Z");
assertValidDateFormatParsing("dateOptionalTime", "5-03-03T1:1:1.1", "0005-03-03T01:01:01.100Z");
assertValidDateFormatParsing("strictDateOptionalTime", "2014-03-03", "2014-03-03T00:00:00.000Z");
assertDateFormatParsingThrowingException("strictDateOptionalTime", "5-03-03");
assertDateFormatParsingThrowingException("strictDateOptionalTime", "0005-3-03");
assertDateFormatParsingThrowingException("strictDateOptionalTime", "0005-03-3");
assertDateFormatParsingThrowingException("strictDateOptionalTime", "5-03-03T1:1:1.1");
assertDateFormatParsingThrowingException("strictDateOptionalTime", "5-03-03T01:01:01.1");
assertDateFormatParsingThrowingException("strictDateOptionalTime", "5-03-03T01:01:1.100");
assertDateFormatParsingThrowingException("strictDateOptionalTime", "5-03-03T01:1:01.100");
assertDateFormatParsingThrowingException("strictDateOptionalTime", "5-03-03T1:01:01.100");
// yyyy-MM-ddT'HH:mm:ss.SSSZZ
assertValidDateFormatParsing("dateTime", "5-03-03T1:1:1.1Z", "0005-03-03T01:01:01.100Z");
assertValidDateFormatParsing("strictDateTime", "2014-03-03T11:11:11.100Z", "2014-03-03T11:11:11.100Z");
assertDateFormatParsingThrowingException("strictDateTime", "0005-03-03T1:1:1.1Z");
assertDateFormatParsingThrowingException("strictDateTime", "0005-03-03T01:01:1.100Z");
assertDateFormatParsingThrowingException("strictDateTime", "0005-03-03T01:1:01.100Z");
assertDateFormatParsingThrowingException("strictDateTime", "0005-03-03T1:01:01.100Z");
// yyyy-MM-ddT'HH:mm:ssZZ
assertValidDateFormatParsing("dateTimeNoMillis", "5-03-03T1:1:1Z", "0005-03-03T01:01:01Z");
assertValidDateFormatParsing("strictDateTimeNoMillis", "2014-03-03T11:11:11Z", "2014-03-03T11:11:11Z");
assertDateFormatParsingThrowingException("strictDateTimeNoMillis", "0005-03-03T1:1:1Z");
assertDateFormatParsingThrowingException("strictDateTimeNoMillis", "0005-03-03T01:01:1Z");
assertDateFormatParsingThrowingException("strictDateTimeNoMillis", "0005-03-03T01:1:01Z");
assertDateFormatParsingThrowingException("strictDateTimeNoMillis", "0005-03-03T1:01:01Z");
// HH
assertValidDateFormatParsing("hour", "12");
assertValidDateFormatParsing("hour", "1", "01");
assertValidDateFormatParsing("strictHour", "12");
assertValidDateFormatParsing("strictHour", "01");
assertDateFormatParsingThrowingException("strictHour", "1");
// HH:mm
assertValidDateFormatParsing("hourMinute", "12:12");
assertValidDateFormatParsing("hourMinute", "12:1", "12:01");
assertValidDateFormatParsing("strictHourMinute", "12:12");
assertValidDateFormatParsing("strictHourMinute", "12:01");
assertDateFormatParsingThrowingException("strictHourMinute", "12:1");
// HH:mm:ss
assertValidDateFormatParsing("hourMinuteSecond", "12:12:12");
assertValidDateFormatParsing("hourMinuteSecond", "12:12:1", "12:12:01");
assertValidDateFormatParsing("strictHourMinuteSecond", "12:12:12");
assertValidDateFormatParsing("strictHourMinuteSecond", "12:12:01");
assertDateFormatParsingThrowingException("strictHourMinuteSecond", "12:12:1");
// HH:mm:ss.SSS
assertValidDateFormatParsing("hourMinuteSecondFraction", "12:12:12.123");
assertValidDateFormatParsing("hourMinuteSecondFraction", "12:12:12.1", "12:12:12.100");
assertValidDateFormatParsing("strictHourMinuteSecondFraction", "12:12:12.123");
assertValidDateFormatParsing("strictHourMinuteSecondFraction", "12:12:12.1", "12:12:12.100");
assertValidDateFormatParsing("hourMinuteSecondMillis", "12:12:12.123");
assertValidDateFormatParsing("hourMinuteSecondMillis", "12:12:12.1", "12:12:12.100");
assertValidDateFormatParsing("strictHourMinuteSecondMillis", "12:12:12.123");
assertValidDateFormatParsing("strictHourMinuteSecondMillis", "12:12:12.1", "12:12:12.100");
// yyyy-DDD
assertValidDateFormatParsing("ordinalDate", "5-3", "0005-003");
assertValidDateFormatParsing("strictOrdinalDate", "0005-003");
assertDateFormatParsingThrowingException("strictOrdinalDate", "5-3");
assertDateFormatParsingThrowingException("strictOrdinalDate", "0005-3");
assertDateFormatParsingThrowingException("strictOrdinalDate", "5-003");
// yyyy-DDDT'HH:mm:ss.SSSZZ
assertValidDateFormatParsing("ordinalDateTime", "5-3T12:12:12.100Z", "0005-003T12:12:12.100Z");
assertValidDateFormatParsing("strictOrdinalDateTime", "0005-003T12:12:12.100Z");
assertDateFormatParsingThrowingException("strictOrdinalDateTime", "5-3T1:12:12.123Z");
assertDateFormatParsingThrowingException("strictOrdinalDateTime", "5-3T12:1:12.123Z");
assertDateFormatParsingThrowingException("strictOrdinalDateTime", "5-3T12:12:1.123Z");
// yyyy-DDDT'HH:mm:ssZZ
assertValidDateFormatParsing("ordinalDateTimeNoMillis", "5-3T12:12:12Z", "0005-003T12:12:12Z");
assertValidDateFormatParsing("strictOrdinalDateTimeNoMillis", "0005-003T12:12:12Z");
assertDateFormatParsingThrowingException("strictOrdinalDateTimeNoMillis", "5-3T1:12:12Z");
assertDateFormatParsingThrowingException("strictOrdinalDateTimeNoMillis", "5-3T12:1:12Z");
assertDateFormatParsingThrowingException("strictOrdinalDateTimeNoMillis", "5-3T12:12:1Z");
// HH:mm:ss.SSSZZ
assertValidDateFormatParsing("time", "12:12:12.100Z");
assertValidDateFormatParsing("time", "01:01:01.1Z", "01:01:01.100Z");
assertValidDateFormatParsing("time", "1:1:1.1Z", "01:01:01.100Z");
assertValidDateFormatParsing("strictTime", "12:12:12.100Z");
assertDateFormatParsingThrowingException("strictTime", "12:12:1.100Z");
assertDateFormatParsingThrowingException("strictTime", "12:1:12.100Z");
assertDateFormatParsingThrowingException("strictTime", "1:12:12.100Z");
// HH:mm:ssZZ
assertValidDateFormatParsing("timeNoMillis", "12:12:12Z");
assertValidDateFormatParsing("timeNoMillis", "01:01:01Z", "01:01:01Z");
assertValidDateFormatParsing("timeNoMillis", "1:1:1Z", "01:01:01Z");
assertValidDateFormatParsing("strictTimeNoMillis", "12:12:12Z");
assertDateFormatParsingThrowingException("strictTimeNoMillis", "12:12:1Z");
assertDateFormatParsingThrowingException("strictTimeNoMillis", "12:1:12Z");
assertDateFormatParsingThrowingException("strictTimeNoMillis", "1:12:12Z");
// 'THH:mm:ss.SSSZZ
assertValidDateFormatParsing("tTime", "T12:12:12.100Z");
assertValidDateFormatParsing("tTime", "T01:01:01.1Z", "T01:01:01.100Z");
assertValidDateFormatParsing("tTime", "T1:1:1.1Z", "T01:01:01.100Z");
assertValidDateFormatParsing("strictTTime", "T12:12:12.100Z");
assertDateFormatParsingThrowingException("strictTTime", "T12:12:1.100Z");
assertDateFormatParsingThrowingException("strictTTime", "T12:1:12.100Z");
assertDateFormatParsingThrowingException("strictTTime", "T1:12:12.100Z");
// 'THH:mm:ssZZ
assertValidDateFormatParsing("tTimeNoMillis", "T12:12:12Z");
assertValidDateFormatParsing("tTimeNoMillis", "T01:01:01Z", "T01:01:01Z");
assertValidDateFormatParsing("tTimeNoMillis", "T1:1:1Z", "T01:01:01Z");
assertValidDateFormatParsing("strictTTimeNoMillis", "T12:12:12Z");
assertDateFormatParsingThrowingException("strictTTimeNoMillis", "T12:12:1Z");
assertDateFormatParsingThrowingException("strictTTimeNoMillis", "T12:1:12Z");
assertDateFormatParsingThrowingException("strictTTimeNoMillis", "T1:12:12Z");
// xxxx-'Www-e
assertValidDateFormatParsing("weekDate", "0005-W4-1", "0005-W04-1");
assertValidDateFormatParsing("strictWeekDate", "0005-W04-1");
assertDateFormatParsingThrowingException("strictWeekDate", "0005-W4-1");
// xxxx-'Www-eT'HH:mm:ss.SSSZZ
assertValidDateFormatParsing("weekDateTime", "0005-W41-4T12:43:43.123Z");
assertValidDateFormatParsing("weekDateTime", "5-W41-4T12:43:43.123Z", "0005-W41-4T12:43:43.123Z");
assertValidDateFormatParsing("strictWeekDateTime", "0005-W41-4T12:43:43.123Z");
assertValidDateFormatParsing("strictWeekDateTime", "0005-W06-4T12:43:43.123Z");
assertDateFormatParsingThrowingException("strictWeekDateTime", "0005-W4-7T12:43:43.123Z");
assertDateFormatParsingThrowingException("strictWeekDateTime", "5-W41-4T12:43:43.123Z");
assertDateFormatParsingThrowingException("strictWeekDateTime", "5-W1-4T12:43:43.123Z");
// xxxx-'Www-eT'HH:mm:ssZZ
assertValidDateFormatParsing("weekDateTimeNoMillis", "0005-W41-4T12:43:43Z");
assertValidDateFormatParsing("weekDateTimeNoMillis", "5-W41-4T12:43:43Z", "0005-W41-4T12:43:43Z");
assertValidDateFormatParsing("strictWeekDateTimeNoMillis", "0005-W41-4T12:43:43Z");
assertValidDateFormatParsing("strictWeekDateTimeNoMillis", "0005-W06-4T12:43:43Z");
assertDateFormatParsingThrowingException("strictWeekDateTimeNoMillis", "0005-W4-7T12:43:43Z");
assertDateFormatParsingThrowingException("strictWeekDateTimeNoMillis", "5-W41-4T12:43:43Z");
assertDateFormatParsingThrowingException("strictWeekDateTimeNoMillis", "5-W1-4T12:43:43Z");
// yyyy
assertValidDateFormatParsing("weekyear", "2014");
assertValidDateFormatParsing("weekyear", "5", "0005");
assertValidDateFormatParsing("weekyear", "0005");
assertValidDateFormatParsing("strictWeekyear", "2014");
assertValidDateFormatParsing("strictWeekyear", "0005");
assertDateFormatParsingThrowingException("strictWeekyear", "5");
// yyyy-'W'ee
assertValidDateFormatParsing("weekyearWeek", "2014-W41");
assertValidDateFormatParsing("weekyearWeek", "2014-W1", "2014-W01");
assertValidDateFormatParsing("strictWeekyearWeek", "2014-W41");
assertDateFormatParsingThrowingException("strictWeekyearWeek", "2014-W1");
// weekyearWeekDay
assertValidDateFormatParsing("weekyearWeekDay", "2014-W41-1");
assertValidDateFormatParsing("weekyearWeekDay", "2014-W1-1", "2014-W01-1");
assertValidDateFormatParsing("strictWeekyearWeekDay", "2014-W41-1");
assertDateFormatParsingThrowingException("strictWeekyearWeekDay", "2014-W1-1");
// yyyy
assertValidDateFormatParsing("year", "2014");
assertValidDateFormatParsing("year", "5", "0005");
assertValidDateFormatParsing("strictYear", "2014");
assertDateFormatParsingThrowingException("strictYear", "5");
// yyyy-mm
assertValidDateFormatParsing("yearMonth", "2014-12");
assertValidDateFormatParsing("yearMonth", "2014-5", "2014-05");
assertValidDateFormatParsing("strictYearMonth", "2014-12");
assertDateFormatParsingThrowingException("strictYearMonth", "2014-5");
// yyyy-mm-dd
assertValidDateFormatParsing("yearMonthDay", "2014-12-12");
assertValidDateFormatParsing("yearMonthDay", "2014-05-5", "2014-05-05");
assertValidDateFormatParsing("strictYearMonthDay", "2014-12-12");
assertDateFormatParsingThrowingException("strictYearMonthDay", "2014-05-5");
}
@Test
public void testThatRootObjectParsingIsStrict() throws Exception {
String[] datesThatWork = new String[] { "2014/10/10", "2014/10/10 12:12:12", "2014-05-05", "2014-05-05T12:12:12.123Z" };
String[] datesThatShouldNotWork = new String[]{ "5-05-05", "2014-5-05", "2014-05-5",
"2014-05-05T1:12:12.123Z", "2014-05-05T12:1:12.123Z", "2014-05-05T12:12:1.123Z",
"4/10/10", "2014/1/10", "2014/10/1",
"2014/10/10 1:12:12", "2014/10/10 12:1:12", "2014/10/10 12:12:1"
};
// good case
for (String date : datesThatWork) {
boolean dateParsingSuccessful = false;
for (FormatDateTimeFormatter dateTimeFormatter : RootObjectMapper.Defaults.DYNAMIC_DATE_TIME_FORMATTERS) {
try {
dateTimeFormatter.parser().parseMillis(date);
dateParsingSuccessful = true;
break;
} catch (Exception e) {}
}
if (!dateParsingSuccessful) {
fail("Parsing for date " + date + " in root object mapper failed, but shouldnt");
}
}
// bad case
for (String date : datesThatShouldNotWork) {
for (FormatDateTimeFormatter dateTimeFormatter : RootObjectMapper.Defaults.DYNAMIC_DATE_TIME_FORMATTERS) {
try {
dateTimeFormatter.parser().parseMillis(date);
fail(String.format(Locale.ROOT, "Expected exception when parsing date %s in root mapper", date));
} catch (Exception e) {}
}
}
}
private void assertValidDateFormatParsing(String pattern, String dateToParse) {
assertValidDateFormatParsing(pattern, dateToParse, dateToParse);
}
private void assertValidDateFormatParsing(String pattern, String dateToParse, String expectedDate) {
FormatDateTimeFormatter formatter = Joda.forPattern(pattern);
assertThat(formatter.printer().print(formatter.parser().parseMillis(dateToParse)), is(expectedDate));
}
private void assertDateFormatParsingThrowingException(String pattern, String invalidDate) {
try {
FormatDateTimeFormatter formatter = Joda.forPattern(pattern);
DateTimeFormatter parser = formatter.parser();
parser.parseMillis(invalidDate);
fail(String.format(Locale.ROOT, "Expected parsing exception for pattern [%s] with date [%s], but did not happen", pattern, invalidDate));
} catch (IllegalArgumentException e) {
}
}
private long utcTimeInMillis(String time) {
return ISODateTimeFormat.dateOptionalTimeParser().withZone(DateTimeZone.UTC).parseMillis(time);
}

@ -26,6 +26,8 @@ import org.apache.lucene.index.IndexableField;
import org.apache.lucene.search.NumericRangeQuery;
import org.apache.lucene.util.Constants;
import org.elasticsearch.Version;
import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.common.settings.Settings;
@ -45,6 +47,7 @@ import org.elasticsearch.index.mapper.core.StringFieldMapper;
import org.elasticsearch.search.internal.SearchContext;
import org.elasticsearch.test.ElasticsearchSingleNodeTest;
import org.elasticsearch.test.TestSearchContext;
import org.elasticsearch.test.VersionUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.junit.Before;
@ -55,7 +58,6 @@ import java.util.*;
import static com.carrotsearch.randomizedtesting.RandomizedTest.systemPropertyAsBoolean;
import static org.elasticsearch.common.settings.Settings.settingsBuilder;
import static org.elasticsearch.index.mapper.string.SimpleStringMappingTests.docValuesType;
import static org.elasticsearch.test.VersionUtils.randomVersionBetween;
import static org.hamcrest.Matchers.*;
public class SimpleDateMappingTests extends ElasticsearchSingleNodeTest {
@ -482,4 +484,94 @@ public class SimpleDateMappingTests extends ElasticsearchSingleNodeTest {
indexResponse = client().prepareIndex("test", "test").setSource(document).get();
assertThat(indexResponse.isCreated(), is(true));
}
public void testThatOlderIndicesAllowNonStrictDates() throws Exception {
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type")
.startObject("properties").startObject("date_field").field("type", "date").endObject().endObject()
.endObject().endObject().string();
Version randomVersion = VersionUtils.randomVersionBetween(getRandom(), Version.V_0_90_0, Version.V_1_6_1);
IndexService index = createIndex("test", settingsBuilder().put(IndexMetaData.SETTING_VERSION_CREATED, randomVersion).build());
client().admin().indices().preparePutMapping("test").setType("type").setSource(mapping).get();
assertDateFormat("epoch_millis||dateOptionalTime");
DocumentMapper defaultMapper = index.mapperService().documentMapper("type");
defaultMapper.parse("type", "1", XContentFactory.jsonBuilder()
.startObject()
.field("date_field", "1-1-1T00:00:44.000Z")
.endObject()
.bytes());
// also test normal date
defaultMapper.parse("type", "1", XContentFactory.jsonBuilder()
.startObject()
.field("date_field", "2015-06-06T00:00:44.000Z")
.endObject()
.bytes());
}
public void testThatNewIndicesOnlyAllowStrictDates() throws Exception {
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type")
.startObject("properties").startObject("date_field").field("type", "date").endObject().endObject()
.endObject().endObject().string();
IndexService index = createIndex("test");
client().admin().indices().preparePutMapping("test").setType("type").setSource(mapping).get();
assertDateFormat(DateFieldMapper.Defaults.DATE_TIME_FORMATTER.format());
DocumentMapper defaultMapper = index.mapperService().documentMapper("type");
// also test normal date
defaultMapper.parse("type", "1", XContentFactory.jsonBuilder()
.startObject()
.field("date_field", "2015-06-06T00:00:44.000Z")
.endObject()
.bytes());
try {
defaultMapper.parse("type", "1", XContentFactory.jsonBuilder()
.startObject()
.field("date_field", "1-1-1T00:00:44.000Z")
.endObject()
.bytes());
fail("non strict date indexing should have been failed");
} catch (MapperParsingException e) {
assertThat(e.getCause(), instanceOf(IllegalArgumentException.class));
}
}
public void testThatUpgradingAnOlderIndexToStrictDateWorks() throws Exception {
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type")
.startObject("properties").startObject("date_field").field("type", "date").field("format", "dateOptionalTime").endObject().endObject()
.endObject().endObject().string();
Version randomVersion = VersionUtils.randomVersionBetween(getRandom(), Version.V_0_90_0, Version.V_1_6_1);
createIndex("test", settingsBuilder().put(IndexMetaData.SETTING_VERSION_CREATED, randomVersion).build());
client().admin().indices().preparePutMapping("test").setType("type").setSource(mapping).get();
assertDateFormat("epoch_millis||dateOptionalTime");
// index doc
client().prepareIndex("test", "type", "1").setSource(XContentFactory.jsonBuilder()
.startObject()
.field("date_field", "2015-06-06T00:00:44.000Z")
.endObject()).get();
// update mapping
String newMapping = XContentFactory.jsonBuilder().startObject().startObject("type")
.startObject("properties").startObject("date_field")
.field("type", "date")
.field("format", "strictDateOptionalTime||epoch_millis")
.endObject().endObject().endObject().endObject().string();
PutMappingResponse putMappingResponse = client().admin().indices().preparePutMapping("test").setType("type").setSource(newMapping).get();
assertThat(putMappingResponse.isAcknowledged(), is(true));
assertDateFormat("strictDateOptionalTime||epoch_millis");
}
private void assertDateFormat(String expectedFormat) throws IOException {
GetMappingsResponse response = client().admin().indices().prepareGetMappings("test").setTypes("type").get();
Map<String, Object> mappingMap = response.getMappings().get("test").get("type").getSourceAsMap();
Map<String, Object> properties = (Map<String, Object>) mappingMap.get("properties");
Map<String, Object> dateField = (Map<String, Object>) properties.get("date_field");
assertThat((String) dateField.get("format"), is(expectedFormat));
}
}

@ -37,7 +37,6 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.mapper.*;
import org.elasticsearch.index.mapper.internal.TimestampFieldMapper;
import org.elasticsearch.test.ElasticsearchSingleNodeTest;
@ -56,14 +55,7 @@ import static org.elasticsearch.common.settings.Settings.settingsBuilder;
import static org.elasticsearch.test.VersionUtils.randomVersion;
import static org.elasticsearch.test.VersionUtils.randomVersionBetween;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasKey;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.isIn;
import static org.hamcrest.Matchers.lessThanOrEqualTo;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.*;
/**
*/
@ -113,8 +105,10 @@ public class TimestampMappingTests extends ElasticsearchSingleNodeTest {
assertThat(docMapper.timestampFieldMapper().fieldType().stored(), equalTo(version.onOrAfter(Version.V_2_0_0)));
assertThat(docMapper.timestampFieldMapper().fieldType().indexOptions(), equalTo(TimestampFieldMapper.Defaults.FIELD_TYPE.indexOptions()));
assertThat(docMapper.timestampFieldMapper().path(), equalTo(TimestampFieldMapper.Defaults.PATH));
assertThat(docMapper.timestampFieldMapper().fieldType().dateTimeFormatter().format(), equalTo(TimestampFieldMapper.DEFAULT_DATE_TIME_FORMAT));
assertThat(docMapper.timestampFieldMapper().fieldType().hasDocValues(), equalTo(version.onOrAfter(Version.V_2_0_0)));
String expectedFormat = version.onOrAfter(Version.V_2_0_0) ? TimestampFieldMapper.DEFAULT_DATE_TIME_FORMAT :
TimestampFieldMapper.Defaults.DATE_TIME_FORMATTER_BEFORE_2_0.format();
assertThat(docMapper.timestampFieldMapper().fieldType().dateTimeFormatter().format(), equalTo(expectedFormat));
assertAcked(client().admin().indices().prepareDelete("test").execute().get());
}
}
@ -755,7 +749,7 @@ public class TimestampMappingTests extends ElasticsearchSingleNodeTest {
IndexRequest request = new IndexRequest("test", "type", "1").source(doc);
request.process(metaData, mappingMetaData, true, "test");
assertEquals(request.timestamp(), "1");
assertThat(request.timestamp(), is("1"));
}
public void testIncludeInObjectBackcompat() throws Exception {

@ -1281,9 +1281,9 @@ public class DateHistogramTests extends ElasticsearchIntegrationTest {
public void testIssue8209() throws InterruptedException, ExecutionException {
assertAcked(client().admin().indices().prepareCreate("test8209").addMapping("type", "d", "type=date").get());
indexRandom(true,
client().prepareIndex("test8209", "type").setSource("d", "2014-01-01T0:00:00Z"),
client().prepareIndex("test8209", "type").setSource("d", "2014-04-01T0:00:00Z"),
client().prepareIndex("test8209", "type").setSource("d", "2014-04-30T0:00:00Z"));
client().prepareIndex("test8209", "type").setSource("d", "2014-01-01T00:00:00Z"),
client().prepareIndex("test8209", "type").setSource("d", "2014-04-01T00:00:00Z"),
client().prepareIndex("test8209", "type").setSource("d", "2014-04-30T00:00:00Z"));
ensureSearchable("test8209");
SearchResponse response = client().prepareSearch("test8209")
.addAggregation(dateHistogram("histo").field("d").interval(DateHistogramInterval.MONTH).timeZone("CET")

@ -42,6 +42,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.Locale;
import static org.elasticsearch.client.Requests.indexRequest;
import static org.elasticsearch.client.Requests.searchRequest;
@ -530,17 +531,17 @@ public class DecayFunctionScoreTests extends ElasticsearchIntegrationTest {
ensureYellow();
DateTime docDate = dt.minusDays(1);
String docDateString = docDate.getYear() + "-" + docDate.getMonthOfYear() + "-" + docDate.getDayOfMonth();
String docDateString = docDate.getYear() + "-" + String.format(Locale.ROOT, "%02d", docDate.getMonthOfYear()) + "-" + String.format(Locale.ROOT, "%02d", docDate.getDayOfMonth());
client().index(
indexRequest("test").type("type1").id("1")
.source(jsonBuilder().startObject().field("test", "value").field("num1", docDateString).endObject())).actionGet();
docDate = dt.minusDays(2);
docDateString = docDate.getYear() + "-" + docDate.getMonthOfYear() + "-" + docDate.getDayOfMonth();
docDateString = docDate.getYear() + "-" + String.format(Locale.ROOT, "%02d", docDate.getMonthOfYear()) + "-" + String.format(Locale.ROOT, "%02d", docDate.getDayOfMonth());
client().index(
indexRequest("test").type("type1").id("2")
.source(jsonBuilder().startObject().field("test", "value").field("num1", docDateString).endObject())).actionGet();
docDate = dt.minusDays(3);
docDateString = docDate.getYear() + "-" + docDate.getMonthOfYear() + "-" + docDate.getDayOfMonth();
docDateString = docDate.getYear() + "-" + String.format(Locale.ROOT, "%02d", docDate.getMonthOfYear()) + "-" + String.format(Locale.ROOT, "%02d", docDate.getDayOfMonth());
client().index(
indexRequest("test").type("type1").id("3")
.source(jsonBuilder().startObject().field("test", "value").field("num1", docDateString).endObject())).actionGet();

@ -49,6 +49,13 @@ first millisecond of the rounding scope. The semantics work as follows:
[[built-in]]
=== Built In Formats
Most of the below dates have a `strict` companion dates, which means, that
year, month and day parts of the week must have prepending zeros in order
to be valid. This means, that a date like `5/11/1` would not be valid, but
you would need to specify the full date, which would be `2005/11/01` in this
example. So instead of `date_optional_time` you would need to specify
`strict_date_optional_time`.
The following tables lists all the defaults ISO formats supported:
[cols="<,<",options="header",]
@ -92,112 +99,125 @@ offset prefixed by 'T' ('T'HHmmssZ).
|`basic_week_date`|A basic formatter for a full date as four digit
weekyear, two digit week of weekyear, and one digit day of week
(xxxx'W'wwe).
(xxxx'W'wwe). `strict_basic_week_date` is supported.
|`basic_week_date_time`|A basic formatter that combines a basic weekyear
date and time, separated by a 'T' (xxxx'W'wwe'T'HHmmss.SSSZ).
`strict_basic_week_date_time` is supported.
|`basic_week_date_time_no_millis`|A basic formatter that combines a basic
weekyear date and time without millis, separated by a 'T'
(xxxx'W'wwe'T'HHmmssZ).
(xxxx'W'wwe'T'HHmmssZ). `strict_week_date_time` is supported.
|`date`|A formatter for a full date as four digit year, two digit month
of year, and two digit day of month (yyyy-MM-dd).
of year, and two digit day of month (yyyy-MM-dd). `strict_date` is supported.
_
|`date_hour`|A formatter that combines a full date and two digit hour of
day.
day. strict_date_hour` is supported.
|`date_hour_minute`|A formatter that combines a full date, two digit hour
of day, and two digit minute of hour.
of day, and two digit minute of hour. strict_date_hour_minute` is supported.
|`date_hour_minute_second`|A formatter that combines a full date, two
digit hour of day, two digit minute of hour, and two digit second of
minute.
minute. `strict_date_hour_minute_second` is supported.
|`date_hour_minute_second_fraction`|A formatter that combines a full
date, two digit hour of day, two digit minute of hour, two digit second
of minute, and three digit fraction of second
(yyyy-MM-dd'T'HH:mm:ss.SSS).
(yyyy-MM-dd'T'HH:mm:ss.SSS). `strict_date_hour_minute_second_fraction` is supported.
|`date_hour_minute_second_millis`|A formatter that combines a full date,
two digit hour of day, two digit minute of hour, two digit second of
minute, and three digit fraction of second (yyyy-MM-dd'T'HH:mm:ss.SSS).
`strict_date_hour_minute_second_millis` is supported.
|`date_optional_time`|a generic ISO datetime parser where the date is
mandatory and the time is optional.
mandatory and the time is optional. `strict_date_optional_time` is supported.
|`date_time`|A formatter that combines a full date and time, separated by
a 'T' (yyyy-MM-dd'T'HH:mm:ss.SSSZZ).
a 'T' (yyyy-MM-dd'T'HH:mm:ss.SSSZZ). `strict_date_time` is supported.
|`date_time_no_millis`|A formatter that combines a full date and time
without millis, separated by a 'T' (yyyy-MM-dd'T'HH:mm:ssZZ).
`strict_date_time_no_millis` is supported.
|`hour`|A formatter for a two digit hour of day.
|`hour`|A formatter for a two digit hour of day. `strict_hour` is supported.
|`hour_minute`|A formatter for a two digit hour of day and two digit
minute of hour.
minute of hour. `strict_hour_minute` is supported.
|`hour_minute_second`|A formatter for a two digit hour of day, two digit
minute of hour, and two digit second of minute.
`strict_hour_minute_second` is supported.
|`hour_minute_second_fraction`|A formatter for a two digit hour of day,
two digit minute of hour, two digit second of minute, and three digit
fraction of second (HH:mm:ss.SSS).
`strict_hour_minute_second_fraction` is supported.
|`hour_minute_second_millis`|A formatter for a two digit hour of day, two
digit minute of hour, two digit second of minute, and three digit
fraction of second (HH:mm:ss.SSS).
`strict_hour_minute_second_millis` is supported.
|`ordinal_date`|A formatter for a full ordinal date, using a four digit
year and three digit dayOfYear (yyyy-DDD).
year and three digit dayOfYear (yyyy-DDD). `strict_ordinal_date` is supported.
|`ordinal_date_time`|A formatter for a full ordinal date and time, using
a four digit year and three digit dayOfYear (yyyy-DDD'T'HH:mm:ss.SSSZZ).
`strict_ordinal_date_time` is supported.
|`ordinal_date_time_no_millis`|A formatter for a full ordinal date and
time without millis, using a four digit year and three digit dayOfYear
(yyyy-DDD'T'HH:mm:ssZZ).
`strict_ordinal_date_time_no_millis` is supported.
|`time`|A formatter for a two digit hour of day, two digit minute of
hour, two digit second of minute, three digit fraction of second, and
time zone offset (HH:mm:ss.SSSZZ).
time zone offset (HH:mm:ss.SSSZZ). `strict_time` is supported.
|`time_no_millis`|A formatter for a two digit hour of day, two digit
minute of hour, two digit second of minute, and time zone offset
(HH:mm:ssZZ).
(HH:mm:ssZZ). `strict_time_no_millis` is supported.
|`t_time`|A formatter for a two digit hour of day, two digit minute of
hour, two digit second of minute, three digit fraction of second, and
time zone offset prefixed by 'T' ('T'HH:mm:ss.SSSZZ).
`strict_t_time` is supported.
|`t_time_no_millis`|A formatter for a two digit hour of day, two digit
minute of hour, two digit second of minute, and time zone offset
prefixed by 'T' ('T'HH:mm:ssZZ).
prefixed by 'T' ('T'HH:mm:ssZZ). `strict_t_time_no_millis` is supported.
|`week_date`|A formatter for a full date as four digit weekyear, two
digit week of weekyear, and one digit day of week (xxxx-'W'ww-e).
`strict_week_date` is supported.
|`week_date_time`|A formatter that combines a full weekyear date and
time, separated by a 'T' (xxxx-'W'ww-e'T'HH:mm:ss.SSSZZ).
`strict_week_date_time` is supported.
|`weekDateTimeNoMillis`|A formatter that combines a full weekyear date
|`week_date_time_no_millis`|A formatter that combines a full weekyear date
and time without millis, separated by a 'T' (xxxx-'W'ww-e'T'HH:mm:ssZZ).
`strict_week_date_time` is supported.
|`week_year`|A formatter for a four digit weekyear.
|`weekyear`|A formatter for a four digit weekyear. `strict_week_year` is supported.
|`weekyearWeek`|A formatter for a four digit weekyear and two digit week
of weekyear.
|`weekyear_week`|A formatter for a four digit weekyear and two digit week
of weekyear. `strict_weekyear_week` is supported.
|`weekyearWeekDay`|A formatter for a four digit weekyear, two digit week
of weekyear, and one digit day of week.
|`weekyear_week_day`|A formatter for a four digit weekyear, two digit week
of weekyear, and one digit day of week. `strict_weekyear_week_day` is supported.
|`year`|A formatter for a four digit year.
|`year`|A formatter for a four digit year. `strict_year` is supported.
|`year_month`|A formatter for a four digit year and two digit month of
year.
year. `strict_year_month` is supported.
|`year_month_day`|A formatter for a four digit year, two digit month of
year, and two digit day of month.
year, and two digit day of month. `strict_year_month_day` is supported.
|`epoch_second`|A formatter for the number of seconds since the epoch.
Note, that this timestamp allows a max length of 10 chars, so dates

@ -40,7 +40,7 @@ format>> used to parse the provided timestamp value. For example:
}
--------------------------------------------------
Note, the default format is `epoch_millis||dateOptionalTime`. The timestamp value will
Note, the default format is `epoch_millis||strictDateOptionalTime`. The timestamp value will
first be parsed as a number and if it fails the format will be tried.
[float]

@ -349,7 +349,7 @@ date type:
Defaults to the property/field name.
|`format` |The <<mapping-date-format,date
format>>. Defaults to `epoch_millis||dateOptionalTime`.
format>>. Defaults to `epoch_millis||strictDateOptionalTime`.
|`store` |Set to `true` to store actual field in the index, `false` to not
store it. Defaults to `false` (note, the JSON document itself is stored,

@ -42,7 +42,7 @@ and will use the matching format as its format attribute. The date
format itself is explained
<<mapping-date-format,here>>.
The default formats are: `dateOptionalTime` (ISO),
The default formats are: `strictDateOptionalTime` (ISO) and
`yyyy/MM/dd HH:mm:ss Z||yyyy/MM/dd Z` and `epoch_millis`.
*Note:* `dynamic_date_formats` are used *only* for dynamically added

@ -302,6 +302,13 @@ Meta fields can no longer be specified within a document. They should be specifi
via the API. For example, instead of adding a field `_parent` within a document,
use the `parent` url parameter when indexing that document.
==== Default date format now is `strictDateOptionalDate`
Instead of `dateOptionalTime` the new default date format now is `strictDateOptionalTime`,
which is more strict in parsing dates. This means, that dates now need to have a four digit year,
a two-digit month, day, hour, minute and second. This means, you may need to preprend a part of the date
with a zero to make it conform or switch back to the old `dateOptionalTime` format.
==== Date format does not support unix timestamps by default
In earlier versions of elasticsearch, every timestamp was always tried to be parsed as
@ -723,4 +730,4 @@ to prevent clashes with the watcher plugin
=== Percolator stats
Changed the `percolate.getTime` stat (total time spent on percolating) to `percolate.time` state.
Changed the `percolate.getTime` stat (total time spent on percolating) to `percolate.time` state.