1
0
Эх сурвалжийг харах

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 #6158
Alexander Reelsen 10 жил өмнө
parent
commit
b612cab96a

+ 110 - 1
core/src/main/java/org/elasticsearch/common/joda/Joda.java

@@ -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;

+ 17 - 9
core/src/main/java/org/elasticsearch/index/mapper/core/DateFieldMapper.java

@@ -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;
         }
     }

+ 26 - 9
core/src/main/java/org/elasticsearch/index/mapper/internal/TimestampFieldMapper.java

@@ -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)) {

+ 1 - 1
core/src/main/java/org/elasticsearch/index/mapper/object/RootObjectMapper.java

@@ -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;

+ 1 - 1
core/src/main/java/org/elasticsearch/snapshots/SnapshotInfo.java

@@ -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;
 

+ 2028 - 0
core/src/main/java/org/joda/time/format/StrictISODateTimeFormat.java

@@ -0,0 +1,2028 @@
+package org.joda.time.format;
+
+/*
+ *  Copyright 2001-2009 Stephen Colebourne
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+import org.joda.time.DateTimeFieldType;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+/*
+ * Elasticsearch Note: This class has been copied almost identically from joda, where the
+ * class is named ISODatetimeFormat
+ *
+ * However there has been done one huge modification in several methods, which forces the date
+ * year to be at least n digits, so that a year like "5" is invalid and must be "0005"
+ *
+ * All methods have been marked with an "// ES change" commentary
+ *
+ * In case you compare this with the original ISODateTimeFormat, make sure you use a diff
+ * call, that ignores whitespaces/tabs/indendetations like 'diff -b'
+ */
+/**
+ * Factory that creates instances of DateTimeFormatter based on the ISO8601 standard.
+ * <p>
+ * Date-time formatting is performed by the {@link DateTimeFormatter} class.
+ * Three classes provide factory methods to create formatters, and this is one.
+ * The others are {@link DateTimeFormat} and {@link DateTimeFormatterBuilder}.
+ * <p>
+ * ISO8601 is the international standard for data interchange. It defines a
+ * framework, rather than an absolute standard. As a result this provider has a
+ * number of methods that represent common uses of the framework. The most common
+ * formats are {@link #date() date}, {@link #time() time}, and {@link #dateTime() dateTime}.
+ * <p>
+ * For example, to format a date time in ISO format:
+ * <pre>
+ * DateTime dt = new DateTime();
+ * DateTimeFormatter fmt = ISODateTimeFormat.dateTime();
+ * String str = fmt.print(dt);
+ * </pre>
+ * <p>
+ * Note that these formatters mostly follow the ISO8601 standard for printing.
+ * For parsing, the formatters are more lenient and allow formats that are not
+ * in strict compliance with the standard.
+ * <p>
+ * It is important to understand that these formatters are not linked to
+ * the <code>ISOChronology</code>. These formatters may be used with any
+ * chronology, however there may be certain side effects with more unusual
+ * chronologies. For example, the ISO formatters rely on dayOfWeek being
+ * single digit, dayOfMonth being two digit and dayOfYear being three digit.
+ * A chronology with a ten day week would thus cause issues. However, in
+ * general, it is safe to use these formatters with other chronologies.
+ * <p>
+ * ISODateTimeFormat is thread-safe and immutable, and the formatters it
+ * returns are as well.
+ *
+ * @author Brian S O'Neill
+ * @since 1.0
+ * @see DateTimeFormat
+ * @see DateTimeFormatterBuilder
+ */
+public class StrictISODateTimeFormat {
+
+    /**
+     * Constructor.
+     *
+     * @since 1.1 (previously private)
+     */
+    protected StrictISODateTimeFormat() {
+        super();
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * Returns a formatter that outputs only those fields specified.
+     * <p>
+     * This method examines the fields provided and returns an ISO-style
+     * formatter that best fits. This can be useful for outputting
+     * less-common ISO styles, such as YearMonth (YYYY-MM) or MonthDay (--MM-DD).
+     * <p>
+     * The list provided may have overlapping fields, such as dayOfWeek and
+     * dayOfMonth. In this case, the style is chosen based on the following
+     * list, thus in the example, the calendar style is chosen as dayOfMonth
+     * is higher in priority than dayOfWeek:
+     * <ul>
+     * <li>monthOfYear - calendar date style
+     * <li>dayOfYear - ordinal date style
+     * <li>weekOfWeekYear - week date style
+     * <li>dayOfMonth - calendar date style
+     * <li>dayOfWeek - week date style
+     * <li>year
+     * <li>weekyear
+     * </ul>
+     * The supported formats are:
+     * <pre>
+     * Extended      Basic       Fields
+     * 2005-03-25    20050325    year/monthOfYear/dayOfMonth
+     * 2005-03       2005-03     year/monthOfYear
+     * 2005--25      2005--25    year/dayOfMonth *
+     * 2005          2005        year
+     * --03-25       --0325      monthOfYear/dayOfMonth
+     * --03          --03        monthOfYear
+     * ---03         ---03       dayOfMonth
+     * 2005-084      2005084     year/dayOfYear
+     * -084          -084        dayOfYear
+     * 2005-W12-5    2005W125    weekyear/weekOfWeekyear/dayOfWeek
+     * 2005-W-5      2005W-5     weekyear/dayOfWeek *
+     * 2005-W12      2005W12     weekyear/weekOfWeekyear
+     * -W12-5        -W125       weekOfWeekyear/dayOfWeek
+     * -W12          -W12        weekOfWeekyear
+     * -W-5          -W-5        dayOfWeek
+     * 10:20:30.040  102030.040  hour/minute/second/milli
+     * 10:20:30      102030      hour/minute/second
+     * 10:20         1020        hour/minute
+     * 10            10          hour
+     * -20:30.040    -2030.040   minute/second/milli
+     * -20:30        -2030       minute/second
+     * -20           -20         minute
+     * --30.040      --30.040    second/milli
+     * --30          --30        second
+     * ---.040       ---.040     milli *
+     * 10-30.040     10-30.040   hour/second/milli *
+     * 10:20-.040    1020-.040   hour/minute/milli *
+     * 10-30         10-30       hour/second *
+     * 10--.040      10--.040    hour/milli *
+     * -20-.040      -20-.040    minute/milli *
+     *   plus datetime formats like {date}T{time}
+     * </pre>
+     * * indiates that this is not an official ISO format and can be excluded
+     * by passing in <code>strictISO</code> as <code>true</code>.
+     * <p>
+     * This method can side effect the input collection of fields.
+     * If the input collection is modifiable, then each field that was added to
+     * the formatter will be removed from the collection, including any duplicates.
+     * If the input collection is unmodifiable then no side effect occurs.
+     * <p>
+     * This side effect processing is useful if you need to know whether all
+     * the fields were converted into the formatter or not. To achieve this,
+     * pass in a modifiable list, and check that it is empty on exit.
+     *
+     * @param fields  the fields to get a formatter for, not null,
+     *  updated by the method call unless unmodifiable,
+     *  removing those fields built in the formatter
+     * @param extended  true to use the extended format (with separators)
+     * @param strictISO  true to stick exactly to ISO8601, false to include additional formats
+     * @return a suitable formatter
+     * @throws IllegalArgumentException if there is no format for the fields
+     * @since 1.1
+     */
+    public static DateTimeFormatter forFields(
+            Collection<DateTimeFieldType> fields,
+            boolean extended,
+            boolean strictISO) {
+
+        if (fields == null || fields.size() == 0) {
+            throw new IllegalArgumentException("The fields must not be null or empty");
+        }
+        Set<DateTimeFieldType> workingFields = new HashSet<DateTimeFieldType>(fields);
+        int inputSize = workingFields.size();
+        boolean reducedPrec = false;
+        DateTimeFormatterBuilder bld = new DateTimeFormatterBuilder();
+        // date
+        if (workingFields.contains(DateTimeFieldType.monthOfYear())) {
+            reducedPrec = dateByMonth(bld, workingFields, extended, strictISO);
+        } else if (workingFields.contains(DateTimeFieldType.dayOfYear())) {
+            reducedPrec = dateByOrdinal(bld, workingFields, extended, strictISO);
+        } else if (workingFields.contains(DateTimeFieldType.weekOfWeekyear())) {
+            reducedPrec = dateByWeek(bld, workingFields, extended, strictISO);
+        } else if (workingFields.contains(DateTimeFieldType.dayOfMonth())) {
+            reducedPrec = dateByMonth(bld, workingFields, extended, strictISO);
+        } else if (workingFields.contains(DateTimeFieldType.dayOfWeek())) {
+            reducedPrec = dateByWeek(bld, workingFields, extended, strictISO);
+        } else if (workingFields.remove(DateTimeFieldType.year())) {
+            bld.append(Constants.ye);
+            reducedPrec = true;
+        } else if (workingFields.remove(DateTimeFieldType.weekyear())) {
+            bld.append(Constants.we);
+            reducedPrec = true;
+        }
+        boolean datePresent = (workingFields.size() < inputSize);
+
+        // time
+        time(bld, workingFields, extended, strictISO, reducedPrec, datePresent);
+
+        // result
+        if (bld.canBuildFormatter() == false) {
+            throw new IllegalArgumentException("No valid format for fields: " + fields);
+        }
+
+        // side effect the input collection to indicate the processed fields
+        // handling unmodifiable collections with no side effect
+        try {
+            fields.retainAll(workingFields);
+        } catch (UnsupportedOperationException ex) {
+            // ignore, so we can handle unmodifiable collections
+        }
+        return bld.toFormatter();
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * Creates a date using the calendar date format.
+     * Specification reference: 5.2.1.
+     *
+     * @param bld  the builder
+     * @param fields  the fields
+     * @param extended  true to use extended format
+     * @param strictISO  true to only allow ISO formats
+     * @return true if reduced precision
+     * @since 1.1
+     */
+    private static boolean dateByMonth(
+            DateTimeFormatterBuilder bld,
+            Collection<DateTimeFieldType> fields,
+            boolean extended,
+            boolean strictISO) {
+
+        boolean reducedPrec = false;
+        if (fields.remove(DateTimeFieldType.year())) {
+            bld.append(Constants.ye);
+            if (fields.remove(DateTimeFieldType.monthOfYear())) {
+                if (fields.remove(DateTimeFieldType.dayOfMonth())) {
+                    // YYYY-MM-DD/YYYYMMDD
+                    appendSeparator(bld, extended);
+                    bld.appendMonthOfYear(2);
+                    appendSeparator(bld, extended);
+                    bld.appendDayOfMonth(2);
+                } else {
+                    // YYYY-MM/YYYY-MM
+                    bld.appendLiteral('-');
+                    bld.appendMonthOfYear(2);
+                    reducedPrec = true;
+                }
+            } else {
+                if (fields.remove(DateTimeFieldType.dayOfMonth())) {
+                    // YYYY--DD/YYYY--DD (non-iso)
+                    checkNotStrictISO(fields, strictISO);
+                    bld.appendLiteral('-');
+                    bld.appendLiteral('-');
+                    bld.appendDayOfMonth(2);
+                } else {
+                    // YYYY/YYYY
+                    reducedPrec = true;
+                }
+            }
+
+        } else if (fields.remove(DateTimeFieldType.monthOfYear())) {
+            bld.appendLiteral('-');
+            bld.appendLiteral('-');
+            bld.appendMonthOfYear(2);
+            if (fields.remove(DateTimeFieldType.dayOfMonth())) {
+                // --MM-DD/--MMDD
+                appendSeparator(bld, extended);
+                bld.appendDayOfMonth(2);
+            } else {
+                // --MM/--MM
+                reducedPrec = true;
+            }
+        } else if (fields.remove(DateTimeFieldType.dayOfMonth())) {
+            // ---DD/---DD
+            bld.appendLiteral('-');
+            bld.appendLiteral('-');
+            bld.appendLiteral('-');
+            bld.appendDayOfMonth(2);
+        }
+        return reducedPrec;
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * Creates a date using the ordinal date format.
+     * Specification reference: 5.2.2.
+     *
+     * @param bld  the builder
+     * @param fields  the fields
+     * @param extended  true to use extended format
+     * @param strictISO  true to only allow ISO formats
+     * @since 1.1
+     */
+    private static boolean dateByOrdinal(
+            DateTimeFormatterBuilder bld,
+            Collection<DateTimeFieldType> fields,
+            boolean extended,
+            boolean strictISO) {
+
+        boolean reducedPrec = false;
+        if (fields.remove(DateTimeFieldType.year())) {
+            bld.append(Constants.ye);
+            if (fields.remove(DateTimeFieldType.dayOfYear())) {
+                // YYYY-DDD/YYYYDDD
+                appendSeparator(bld, extended);
+                bld.appendDayOfYear(3);
+            } else {
+                // YYYY/YYYY
+                reducedPrec = true;
+            }
+
+        } else if (fields.remove(DateTimeFieldType.dayOfYear())) {
+            // -DDD/-DDD
+            bld.appendLiteral('-');
+            bld.appendDayOfYear(3);
+        }
+        return reducedPrec;
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * Creates a date using the calendar date format.
+     * Specification reference: 5.2.3.
+     *
+     * @param bld  the builder
+     * @param fields  the fields
+     * @param extended  true to use extended format
+     * @param strictISO  true to only allow ISO formats
+     * @since 1.1
+     */
+    private static boolean dateByWeek(
+            DateTimeFormatterBuilder bld,
+            Collection<DateTimeFieldType> fields,
+            boolean extended,
+            boolean strictISO) {
+
+        boolean reducedPrec = false;
+        if (fields.remove(DateTimeFieldType.weekyear())) {
+            bld.append(Constants.we);
+            if (fields.remove(DateTimeFieldType.weekOfWeekyear())) {
+                appendSeparator(bld, extended);
+                bld.appendLiteral('W');
+                bld.appendWeekOfWeekyear(2);
+                if (fields.remove(DateTimeFieldType.dayOfWeek())) {
+                    // YYYY-WWW-D/YYYYWWWD
+                    appendSeparator(bld, extended);
+                    bld.appendDayOfWeek(1);
+                } else {
+                    // YYYY-WWW/YYYY-WWW
+                    reducedPrec = true;
+                }
+            } else {
+                if (fields.remove(DateTimeFieldType.dayOfWeek())) {
+                    // YYYY-W-D/YYYYW-D (non-iso)
+                    checkNotStrictISO(fields, strictISO);
+                    appendSeparator(bld, extended);
+                    bld.appendLiteral('W');
+                    bld.appendLiteral('-');
+                    bld.appendDayOfWeek(1);
+                } else {
+                    // YYYY/YYYY
+                    reducedPrec = true;
+                }
+            }
+
+        } else if (fields.remove(DateTimeFieldType.weekOfWeekyear())) {
+            bld.appendLiteral('-');
+            bld.appendLiteral('W');
+            bld.appendWeekOfWeekyear(2);
+            if (fields.remove(DateTimeFieldType.dayOfWeek())) {
+                // -WWW-D/-WWWD
+                appendSeparator(bld, extended);
+                bld.appendDayOfWeek(1);
+            } else {
+                // -WWW/-WWW
+                reducedPrec = true;
+            }
+        } else if (fields.remove(DateTimeFieldType.dayOfWeek())) {
+            // -W-D/-W-D
+            bld.appendLiteral('-');
+            bld.appendLiteral('W');
+            bld.appendLiteral('-');
+            bld.appendDayOfWeek(1);
+        }
+        return reducedPrec;
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * Adds the time fields to the builder.
+     * Specification reference: 5.3.1.
+     *
+     * @param bld  the builder
+     * @param fields  the fields
+     * @param extended  whether to use the extended format
+     * @param strictISO  whether to be strict
+     * @param reducedPrec  whether the date was reduced precision
+     * @param datePresent  whether there was a date
+     * @since 1.1
+     */
+    private static void time(
+            DateTimeFormatterBuilder bld,
+            Collection<DateTimeFieldType> fields,
+            boolean extended,
+            boolean strictISO,
+            boolean reducedPrec,
+            boolean datePresent) {
+
+        boolean hour = fields.remove(DateTimeFieldType.hourOfDay());
+        boolean minute = fields.remove(DateTimeFieldType.minuteOfHour());
+        boolean second = fields.remove(DateTimeFieldType.secondOfMinute());
+        boolean milli = fields.remove(DateTimeFieldType.millisOfSecond());
+        if (!hour && !minute && !second && !milli) {
+            return;
+        }
+        if (hour || minute || second || milli) {
+            if (strictISO && reducedPrec) {
+                throw new IllegalArgumentException("No valid ISO8601 format for fields because Date was reduced precision: " + fields);
+            }
+            if (datePresent) {
+                bld.appendLiteral('T');
+            }
+        }
+        if (hour && minute && second || (hour && !second && !milli)) {
+            // OK - HMSm/HMS/HM/H - valid in combination with date
+        } else {
+            if (strictISO && datePresent) {
+                throw new IllegalArgumentException("No valid ISO8601 format for fields because Time was truncated: " + fields);
+            }
+            if (!hour && (minute && second || (minute && !milli) || second)) {
+                // OK - MSm/MS/M/Sm/S - valid ISO formats
+            } else {
+                if (strictISO) {
+                    throw new IllegalArgumentException("No valid ISO8601 format for fields: " + fields);
+                }
+            }
+        }
+        if (hour) {
+            bld.appendHourOfDay(2);
+        } else if (minute || second || milli) {
+            bld.appendLiteral('-');
+        }
+        if (extended && hour && minute) {
+            bld.appendLiteral(':');
+        }
+        if (minute) {
+            bld.appendMinuteOfHour(2);
+        } else if (second || milli) {
+            bld.appendLiteral('-');
+        }
+        if (extended && minute && second) {
+            bld.appendLiteral(':');
+        }
+        if (second) {
+            bld.appendSecondOfMinute(2);
+        } else if (milli) {
+            bld.appendLiteral('-');
+        }
+        if (milli) {
+            bld.appendLiteral('.');
+            bld.appendMillisOfSecond(3);
+        }
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * Checks that the iso only flag is not set, throwing an exception if it is.
+     *
+     * @param fields  the fields
+     * @param strictISO  true if only ISO formats allowed
+     * @since 1.1
+     */
+    private static void checkNotStrictISO(Collection<DateTimeFieldType> fields, boolean strictISO) {
+        if (strictISO) {
+            throw new IllegalArgumentException("No valid ISO8601 format for fields: " + fields);
+        }
+    }
+
+    /**
+     * Appends the separator if necessary.
+     *
+     * @param bld  the builder
+     * @param extended  whether to append the separator
+     * @since 1.1
+     */
+    private static void appendSeparator(DateTimeFormatterBuilder bld, boolean extended) {
+        if (extended) {
+            bld.appendLiteral('-');
+        }
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * Returns a generic ISO date parser for parsing dates with a possible zone.
+     * <p>
+     * The returned formatter can only be used for parsing, printing is unsupported.
+     * <p>
+     * It accepts formats described by the following syntax:
+     * <pre>
+     * date              = date-element ['T' offset]
+     * date-element      = std-date-element | ord-date-element | week-date-element
+     * std-date-element  = yyyy ['-' MM ['-' dd]]
+     * ord-date-element  = yyyy ['-' DDD]
+     * week-date-element = xxxx '-W' ww ['-' e]
+     * offset            = 'Z' | (('+' | '-') HH [':' mm [':' ss [('.' | ',') SSS]]])
+     * </pre>
+     */
+    public static DateTimeFormatter dateParser() {
+        return Constants.dp;
+    }
+
+    /**
+     * Returns a generic ISO date parser for parsing local dates.
+     * <p>
+     * The returned formatter can only be used for parsing, printing is unsupported.
+     * <p>
+     * This parser is initialised with the local (UTC) time zone.
+     * <p>
+     * It accepts formats described by the following syntax:
+     * <pre>
+     * date-element      = std-date-element | ord-date-element | week-date-element
+     * std-date-element  = yyyy ['-' MM ['-' dd]]
+     * ord-date-element  = yyyy ['-' DDD]
+     * week-date-element = xxxx '-W' ww ['-' e]
+     * </pre>
+     * @since 1.3
+     */
+    public static DateTimeFormatter localDateParser() {
+        return Constants.ldp;
+    }
+
+    /**
+     * Returns a generic ISO date parser for parsing dates.
+     * <p>
+     * The returned formatter can only be used for parsing, printing is unsupported.
+     * <p>
+     * It accepts formats described by the following syntax:
+     * <pre>
+     * date-element      = std-date-element | ord-date-element | week-date-element
+     * std-date-element  = yyyy ['-' MM ['-' dd]]
+     * ord-date-element  = yyyy ['-' DDD]
+     * week-date-element = xxxx '-W' ww ['-' e]
+     * </pre>
+     */
+    public static DateTimeFormatter dateElementParser() {
+        return Constants.dpe;
+    }
+
+    /**
+     * Returns a generic ISO time parser for parsing times with a possible zone.
+     * <p>
+     * The returned formatter can only be used for parsing, printing is unsupported.
+     * <p>
+     * The parser is strict by default, thus time string {@code 24:00} cannot be parsed.
+     * <p>
+     * It accepts formats described by the following syntax:
+     * <pre>
+     * time           = ['T'] time-element [offset]
+     * time-element   = HH [minute-element] | [fraction]
+     * minute-element = ':' mm [second-element] | [fraction]
+     * second-element = ':' ss [fraction]
+     * fraction       = ('.' | ',') digit+
+     * offset         = 'Z' | (('+' | '-') HH [':' mm [':' ss [('.' | ',') SSS]]])
+     * </pre>
+     */
+    public static DateTimeFormatter timeParser() {
+        return Constants.tp;
+    }
+
+    /**
+     * Returns a generic ISO time parser for parsing local times.
+     * <p>
+     * The returned formatter can only be used for parsing, printing is unsupported.
+     * <p>
+     * This parser is initialised with the local (UTC) time zone.
+     * The parser is strict by default, thus time string {@code 24:00} cannot be parsed.
+     * <p>
+     * It accepts formats described by the following syntax:
+     * <pre>
+     * time           = ['T'] time-element
+     * time-element   = HH [minute-element] | [fraction]
+     * minute-element = ':' mm [second-element] | [fraction]
+     * second-element = ':' ss [fraction]
+     * fraction       = ('.' | ',') digit+
+     * </pre>
+     * @since 1.3
+     */
+    public static DateTimeFormatter localTimeParser() {
+        return Constants.ltp;
+    }
+
+    /**
+     * Returns a generic ISO time parser.
+     * <p>
+     * The returned formatter can only be used for parsing, printing is unsupported.
+     * <p>
+     * The parser is strict by default, thus time string {@code 24:00} cannot be parsed.
+     * <p>
+     * It accepts formats described by the following syntax:
+     * <pre>
+     * time-element   = HH [minute-element] | [fraction]
+     * minute-element = ':' mm [second-element] | [fraction]
+     * second-element = ':' ss [fraction]
+     * fraction       = ('.' | ',') digit+
+     * </pre>
+     */
+    public static DateTimeFormatter timeElementParser() {
+        return Constants.tpe;
+    }
+
+    /**
+     * Returns a generic ISO datetime parser which parses either a date or a time or both.
+     * <p>
+     * The returned formatter can only be used for parsing, printing is unsupported.
+     * <p>
+     * The parser is strict by default, thus time string {@code 24:00} cannot be parsed.
+     * <p>
+     * It accepts formats described by the following syntax:
+     * <pre>
+     * datetime          = time | date-opt-time
+     * time              = 'T' time-element [offset]
+     * date-opt-time     = date-element ['T' [time-element] [offset]]
+     * date-element      = std-date-element | ord-date-element | week-date-element
+     * std-date-element  = yyyy ['-' MM ['-' dd]]
+     * ord-date-element  = yyyy ['-' DDD]
+     * week-date-element = xxxx '-W' ww ['-' e]
+     * time-element      = HH [minute-element] | [fraction]
+     * minute-element    = ':' mm [second-element] | [fraction]
+     * second-element    = ':' ss [fraction]
+     * fraction          = ('.' | ',') digit+
+     * offset            = 'Z' | (('+' | '-') HH [':' mm [':' ss [('.' | ',') SSS]]])
+     * </pre>
+     */
+    public static DateTimeFormatter dateTimeParser() {
+        return Constants.dtp;
+    }
+
+    /**
+     * Returns a generic ISO datetime parser where the date is mandatory and the time is optional.
+     * <p>
+     * The returned formatter can only be used for parsing, printing is unsupported.
+     * <p>
+     * This parser can parse zoned datetimes.
+     * The parser is strict by default, thus time string {@code 24:00} cannot be parsed.
+     * <p>
+     * It accepts formats described by the following syntax:
+     * <pre>
+     * date-opt-time     = date-element ['T' [time-element] [offset]]
+     * date-element      = std-date-element | ord-date-element | week-date-element
+     * std-date-element  = yyyy ['-' MM ['-' dd]]
+     * ord-date-element  = yyyy ['-' DDD]
+     * week-date-element = xxxx '-W' ww ['-' e]
+     * time-element      = HH [minute-element] | [fraction]
+     * minute-element    = ':' mm [second-element] | [fraction]
+     * second-element    = ':' ss [fraction]
+     * fraction          = ('.' | ',') digit+
+     * </pre>
+     * @since 1.3
+     */
+    public static DateTimeFormatter dateOptionalTimeParser() {
+        return Constants.dotp;
+    }
+
+    /**
+     * Returns a generic ISO datetime parser where the date is mandatory and the time is optional.
+     * <p>
+     * The returned formatter can only be used for parsing, printing is unsupported.
+     * <p>
+     * This parser only parses local datetimes.
+     * This parser is initialised with the local (UTC) time zone.
+     * The parser is strict by default, thus time string {@code 24:00} cannot be parsed.
+     * <p>
+     * It accepts formats described by the following syntax:
+     * <pre>
+     * datetime          = date-element ['T' time-element]
+     * date-element      = std-date-element | ord-date-element | week-date-element
+     * std-date-element  = yyyy ['-' MM ['-' dd]]
+     * ord-date-element  = yyyy ['-' DDD]
+     * week-date-element = xxxx '-W' ww ['-' e]
+     * time-element      = HH [minute-element] | [fraction]
+     * minute-element    = ':' mm [second-element] | [fraction]
+     * second-element    = ':' ss [fraction]
+     * fraction          = ('.' | ',') digit+
+     * </pre>
+     * @since 1.3
+     */
+    public static DateTimeFormatter localDateOptionalTimeParser() {
+        return Constants.ldotp;
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * Returns a formatter for a full date as four digit year, two digit month
+     * of year, and two digit day of month (yyyy-MM-dd).
+     * <p>
+     * The returned formatter prints and parses only this format.
+     * See {@link #dateParser()} for a more flexible parser that accepts different formats.
+     *
+     * @return a formatter for yyyy-MM-dd
+     */
+    public static DateTimeFormatter date() {
+        return yearMonthDay();
+    }
+
+    /**
+     * Returns 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).
+     * <p>
+     * The time zone offset is 'Z' for zero, and of the form '\u00b1HH:mm' for non-zero.
+     * The parser is strict by default, thus time string {@code 24:00} cannot be parsed.
+     * <p>
+     * The returned formatter prints and parses only this format, which includes milliseconds.
+     * See {@link #timeParser()} for a more flexible parser that accepts different formats.
+     *
+     * @return a formatter for HH:mm:ss.SSSZZ
+     */
+    public static DateTimeFormatter time() {
+        return Constants.t;
+    }
+
+    /**
+     * Returns 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).
+     * <p>
+     * The time zone offset is 'Z' for zero, and of the form '\u00b1HH:mm' for non-zero.
+     * The parser is strict by default, thus time string {@code 24:00} cannot be parsed.
+     * <p>
+     * The returned formatter prints and parses only this format, which excludes milliseconds.
+     * See {@link #timeParser()} for a more flexible parser that accepts different formats.
+     *
+     * @return a formatter for HH:mm:ssZZ
+     */
+    public static DateTimeFormatter timeNoMillis() {
+        return Constants.tx;
+    }
+
+    /**
+     * Returns 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).
+     * <p>
+     * The time zone offset is 'Z' for zero, and of the form '\u00b1HH:mm' for non-zero.
+     * The parser is strict by default, thus time string {@code 24:00} cannot be parsed.
+     * <p>
+     * The returned formatter prints and parses only this format, which includes milliseconds.
+     * See {@link #timeParser()} for a more flexible parser that accepts different formats.
+     *
+     * @return a formatter for 'T'HH:mm:ss.SSSZZ
+     */
+    public static DateTimeFormatter tTime() {
+        return Constants.tt;
+    }
+
+    /**
+     * Returns 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).
+     * <p>
+     * The time zone offset is 'Z' for zero, and of the form '\u00b1HH:mm' for non-zero.
+     * The parser is strict by default, thus time string {@code 24:00} cannot be parsed.
+     * <p>
+     * The returned formatter prints and parses only this format, which excludes milliseconds.
+     * See {@link #timeParser()} for a more flexible parser that accepts different formats.
+     *
+     * @return a formatter for 'T'HH:mm:ssZZ
+     */
+    public static DateTimeFormatter tTimeNoMillis() {
+        return Constants.ttx;
+    }
+
+    /**
+     * Returns a formatter that combines a full date and time, separated by a 'T'
+     * (yyyy-MM-dd'T'HH:mm:ss.SSSZZ).
+     * <p>
+     * The time zone offset is 'Z' for zero, and of the form '\u00b1HH:mm' for non-zero.
+     * The parser is strict by default, thus time string {@code 24:00} cannot be parsed.
+     * <p>
+     * The returned formatter prints and parses only this format, which includes milliseconds.
+     * See {@link #dateTimeParser()} for a more flexible parser that accepts different formats.
+     *
+     * @return a formatter for yyyy-MM-dd'T'HH:mm:ss.SSSZZ
+     */
+    public static DateTimeFormatter dateTime() {
+        return Constants.dt;
+    }
+
+    /**
+     * Returns a formatter that combines a full date and time without millis,
+     * separated by a 'T' (yyyy-MM-dd'T'HH:mm:ssZZ).
+     * <p>
+     * The time zone offset is 'Z' for zero, and of the form '\u00b1HH:mm' for non-zero.
+     * The parser is strict by default, thus time string {@code 24:00} cannot be parsed.
+     * <p>
+     * The returned formatter prints and parses only this format, which excludes milliseconds.
+     * See {@link #dateTimeParser()} for a more flexible parser that accepts different formats.
+     *
+     * @return a formatter for yyyy-MM-dd'T'HH:mm:ssZZ
+     */
+    public static DateTimeFormatter dateTimeNoMillis() {
+        return Constants.dtx;
+    }
+
+    /**
+     * Returns a formatter for a full ordinal date, using a four
+     * digit year and three digit dayOfYear (yyyy-DDD).
+     * <p>
+     * The returned formatter prints and parses only this format.
+     * See {@link #dateParser()} for a more flexible parser that accepts different formats.
+     *
+     * @return a formatter for yyyy-DDD
+     * @since 1.1
+     */
+    public static DateTimeFormatter ordinalDate() {
+        return Constants.od;
+    }
+
+    /**
+     * Returns 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).
+     * <p>
+     * The time zone offset is 'Z' for zero, and of the form '\u00b1HH:mm' for non-zero.
+     * The parser is strict by default, thus time string {@code 24:00} cannot be parsed.
+     * <p>
+     * The returned formatter prints and parses only this format, which includes milliseconds.
+     * See {@link #dateTimeParser()} for a more flexible parser that accepts different formats.
+     *
+     * @return a formatter for yyyy-DDD'T'HH:mm:ss.SSSZZ
+     * @since 1.1
+     */
+    public static DateTimeFormatter ordinalDateTime() {
+        return Constants.odt;
+    }
+
+    /**
+     * Returns 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).
+     * <p>
+     * The time zone offset is 'Z' for zero, and of the form '\u00b1HH:mm' for non-zero.
+     * The parser is strict by default, thus time string {@code 24:00} cannot be parsed.
+     * <p>
+     * The returned formatter prints and parses only this format, which excludes milliseconds.
+     * See {@link #dateTimeParser()} for a more flexible parser that accepts different formats.
+     *
+     * @return a formatter for yyyy-DDD'T'HH:mm:ssZZ
+     * @since 1.1
+     */
+    public static DateTimeFormatter ordinalDateTimeNoMillis() {
+        return Constants.odtx;
+    }
+
+    /**
+     * Returns 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).
+     * <p>
+     * The returned formatter prints and parses only this format.
+     * See {@link #dateParser()} for a more flexible parser that accepts different formats.
+     *
+     * @return a formatter for xxxx-'W'ww-e
+     */
+    public static DateTimeFormatter weekDate() {
+        return Constants.wwd;
+    }
+
+    /**
+     * Returns a formatter that combines a full weekyear date and time,
+     * separated by a 'T' (xxxx-'W'ww-e'T'HH:mm:ss.SSSZZ).
+     * <p>
+     * The time zone offset is 'Z' for zero, and of the form '\u00b1HH:mm' for non-zero.
+     * The parser is strict by default, thus time string {@code 24:00} cannot be parsed.
+     * <p>
+     * The returned formatter prints and parses only this format, which includes milliseconds.
+     * See {@link #dateTimeParser()} for a more flexible parser that accepts different formats.
+     *
+     * @return a formatter for xxxx-'W'ww-e'T'HH:mm:ss.SSSZZ
+     */
+    public static DateTimeFormatter weekDateTime() {
+        return Constants.wdt;
+    }
+
+    /**
+     * Returns a formatter that combines a full weekyear date and time without millis,
+     * separated by a 'T' (xxxx-'W'ww-e'T'HH:mm:ssZZ).
+     * <p>
+     * The time zone offset is 'Z' for zero, and of the form '\u00b1HH:mm' for non-zero.
+     * The parser is strict by default, thus time string {@code 24:00} cannot be parsed.
+     * <p>
+     * The returned formatter prints and parses only this format, which excludes milliseconds.
+     * See {@link #dateTimeParser()} for a more flexible parser that accepts different formats.
+     *
+     * @return a formatter for xxxx-'W'ww-e'T'HH:mm:ssZZ
+     */
+    public static DateTimeFormatter weekDateTimeNoMillis() {
+        return Constants.wdtx;
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * Returns a basic formatter for a full date as four digit year, two digit
+     * month of year, and two digit day of month (yyyyMMdd).
+     * <p>
+     * The returned formatter prints and parses only this format.
+     *
+     * @return a formatter for yyyyMMdd
+     */
+    public static DateTimeFormatter basicDate() {
+        return Constants.bd;
+    }
+
+    /**
+     * Returns a basic formatter for a two digit hour of day, two digit minute
+     * of hour, two digit second of minute, three digit millis, and time zone
+     * offset (HHmmss.SSSZ).
+     * <p>
+     * The time zone offset is 'Z' for zero, and of the form '\u00b1HHmm' for non-zero.
+     * The parser is strict by default, thus time string {@code 24:00} cannot be parsed.
+     * <p>
+     * The returned formatter prints and parses only this format, which includes milliseconds.
+     *
+     * @return a formatter for HHmmss.SSSZ
+     */
+    public static DateTimeFormatter basicTime() {
+        return Constants.bt;
+    }
+
+    /**
+     * Returns a basic formatter for a two digit hour of day, two digit minute
+     * of hour, two digit second of minute, and time zone offset (HHmmssZ).
+     * <p>
+     * The time zone offset is 'Z' for zero, and of the form '\u00b1HHmm' for non-zero.
+     * The parser is strict by default, thus time string {@code 24:00} cannot be parsed.
+     * <p>
+     * The returned formatter prints and parses only this format, which excludes milliseconds.
+     *
+     * @return a formatter for HHmmssZ
+     */
+    public static DateTimeFormatter basicTimeNoMillis() {
+        return Constants.btx;
+    }
+
+    /**
+     * Returns a basic formatter for a two digit hour of day, two digit minute
+     * of hour, two digit second of minute, three digit millis, and time zone
+     * offset prefixed by 'T' ('T'HHmmss.SSSZ).
+     * <p>
+     * The time zone offset is 'Z' for zero, and of the form '\u00b1HHmm' for non-zero.
+     * The parser is strict by default, thus time string {@code 24:00} cannot be parsed.
+     * <p>
+     * The returned formatter prints and parses only this format, which includes milliseconds.
+     *
+     * @return a formatter for 'T'HHmmss.SSSZ
+     */
+    public static DateTimeFormatter basicTTime() {
+        return Constants.btt;
+    }
+
+    /**
+     * Returns a basic 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'HHmmssZ).
+     * <p>
+     * The time zone offset is 'Z' for zero, and of the form '\u00b1HHmm' for non-zero.
+     * The parser is strict by default, thus time string {@code 24:00} cannot be parsed.
+     * <p>
+     * The returned formatter prints and parses only this format, which excludes milliseconds.
+     *
+     * @return a formatter for 'T'HHmmssZ
+     */
+    public static DateTimeFormatter basicTTimeNoMillis() {
+        return Constants.bttx;
+    }
+
+    /**
+     * Returns a basic formatter that combines a basic date and time, separated
+     * by a 'T' (yyyyMMdd'T'HHmmss.SSSZ).
+     * <p>
+     * The time zone offset is 'Z' for zero, and of the form '\u00b1HHmm' for non-zero.
+     * The parser is strict by default, thus time string {@code 24:00} cannot be parsed.
+     * <p>
+     * The returned formatter prints and parses only this format, which includes milliseconds.
+     *
+     * @return a formatter for yyyyMMdd'T'HHmmss.SSSZ
+     */
+    public static DateTimeFormatter basicDateTime() {
+        return Constants.bdt;
+    }
+
+    /**
+     * Returns a basic formatter that combines a basic date and time without millis,
+     * separated by a 'T' (yyyyMMdd'T'HHmmssZ).
+     * <p>
+     * The time zone offset is 'Z' for zero, and of the form '\u00b1HHmm' for non-zero.
+     * The parser is strict by default, thus time string {@code 24:00} cannot be parsed.
+     * <p>
+     * The returned formatter prints and parses only this format, which excludes milliseconds.
+     *
+     * @return a formatter for yyyyMMdd'T'HHmmssZ
+     */
+    public static DateTimeFormatter basicDateTimeNoMillis() {
+        return Constants.bdtx;
+    }
+
+    /**
+     * Returns a formatter for a full ordinal date, using a four
+     * digit year and three digit dayOfYear (yyyyDDD).
+     * <p>
+     * The returned formatter prints and parses only this format.
+     *
+     * @return a formatter for yyyyDDD
+     * @since 1.1
+     */
+    public static DateTimeFormatter basicOrdinalDate() {
+        return Constants.bod;
+    }
+
+    /**
+     * Returns a formatter for a full ordinal date and time, using a four
+     * digit year and three digit dayOfYear (yyyyDDD'T'HHmmss.SSSZ).
+     * <p>
+     * The time zone offset is 'Z' for zero, and of the form '\u00b1HHmm' for non-zero.
+     * The parser is strict by default, thus time string {@code 24:00} cannot be parsed.
+     * <p>
+     * The returned formatter prints and parses only this format, which includes milliseconds.
+     *
+     * @return a formatter for yyyyDDD'T'HHmmss.SSSZ
+     * @since 1.1
+     */
+    public static DateTimeFormatter basicOrdinalDateTime() {
+        return Constants.bodt;
+    }
+
+    /**
+     * Returns a formatter for a full ordinal date and time without millis,
+     * using a four digit year and three digit dayOfYear (yyyyDDD'T'HHmmssZ).
+     * <p>
+     * The time zone offset is 'Z' for zero, and of the form '\u00b1HHmm' for non-zero.
+     * The parser is strict by default, thus time string {@code 24:00} cannot be parsed.
+     * <p>
+     * The returned formatter prints and parses only this format, which excludes milliseconds.
+     *
+     * @return a formatter for yyyyDDD'T'HHmmssZ
+     * @since 1.1
+     */
+    public static DateTimeFormatter basicOrdinalDateTimeNoMillis() {
+        return Constants.bodtx;
+    }
+
+    /**
+     * Returns 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).
+     * <p>
+     * The returned formatter prints and parses only this format.
+     *
+     * @return a formatter for xxxx'W'wwe
+     */
+    public static DateTimeFormatter basicWeekDate() {
+        return Constants.bwd;
+    }
+
+    /**
+     * Returns a basic formatter that combines a basic weekyear date and time,
+     * separated by a 'T' (xxxx'W'wwe'T'HHmmss.SSSZ).
+     * <p>
+     * The time zone offset is 'Z' for zero, and of the form '\u00b1HHmm' for non-zero.
+     * The parser is strict by default, thus time string {@code 24:00} cannot be parsed.
+     * <p>
+     * The returned formatter prints and parses only this format, which includes milliseconds.
+     *
+     * @return a formatter for xxxx'W'wwe'T'HHmmss.SSSZ
+     */
+    public static DateTimeFormatter basicWeekDateTime() {
+        return Constants.bwdt;
+    }
+
+    /**
+     * Returns a basic formatter that combines a basic weekyear date and time
+     * without millis, separated by a 'T' (xxxx'W'wwe'T'HHmmssZ).
+     * <p>
+     * The time zone offset is 'Z' for zero, and of the form '\u00b1HHmm' for non-zero.
+     * The parser is strict by default, thus time string {@code 24:00} cannot be parsed.
+     * <p>
+     * The returned formatter prints and parses only this format, which excludes milliseconds.
+     *
+     * @return a formatter for xxxx'W'wwe'T'HHmmssZ
+     */
+    public static DateTimeFormatter basicWeekDateTimeNoMillis() {
+        return Constants.bwdtx;
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * Returns a formatter for a four digit year. (yyyy)
+     *
+     * @return a formatter for yyyy
+     */
+    public static DateTimeFormatter year() {
+        return Constants.ye;
+    }
+
+    /**
+     * Returns a formatter for a four digit year and two digit month of
+     * year. (yyyy-MM)
+     *
+     * @return a formatter for yyyy-MM
+     */
+    public static DateTimeFormatter yearMonth() {
+        return Constants.ym;
+    }
+
+    /**
+     * Returns a formatter for a four digit year, two digit month of year, and
+     * two digit day of month. (yyyy-MM-dd)
+     *
+     * @return a formatter for yyyy-MM-dd
+     */
+    public static DateTimeFormatter yearMonthDay() {
+        return Constants.ymd;
+    }
+
+    /**
+     * Returns a formatter for a four digit weekyear. (xxxx)
+     *
+     * @return a formatter for xxxx
+     */
+    public static DateTimeFormatter weekyear() {
+        return Constants.we;
+    }
+
+    /**
+     * Returns a formatter for a four digit weekyear and two digit week of
+     * weekyear. (xxxx-'W'ww)
+     *
+     * @return a formatter for xxxx-'W'ww
+     */
+    public static DateTimeFormatter weekyearWeek() {
+        return Constants.ww;
+    }
+
+    /**
+     * Returns a formatter for a four digit weekyear, two digit week of
+     * weekyear, and one digit day of week. (xxxx-'W'ww-e)
+     *
+     * @return a formatter for xxxx-'W'ww-e
+     */
+    public static DateTimeFormatter weekyearWeekDay() {
+        return Constants.wwd;
+    }
+
+    /**
+     * Returns a formatter for a two digit hour of day. (HH)
+     *
+     * @return a formatter for HH
+     */
+    public static DateTimeFormatter hour() {
+        return Constants.hde;
+    }
+
+    /**
+     * Returns a formatter for a two digit hour of day and two digit minute of
+     * hour. (HH:mm)
+     *
+     * @return a formatter for HH:mm
+     */
+    public static DateTimeFormatter hourMinute() {
+        return Constants.hm;
+    }
+
+    /**
+     * Returns a formatter for a two digit hour of day, two digit minute of
+     * hour, and two digit second of minute. (HH:mm:ss)
+     *
+     * @return a formatter for HH:mm:ss
+     */
+    public static DateTimeFormatter hourMinuteSecond() {
+        return Constants.hms;
+    }
+
+    /**
+     * Returns 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). Parsing will parse up to 3 fractional second
+     * digits.
+     *
+     * @return a formatter for HH:mm:ss.SSS
+     */
+    public static DateTimeFormatter hourMinuteSecondMillis() {
+        return Constants.hmsl;
+    }
+
+    /**
+     * Returns 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). Parsing will parse up to 9 fractional second
+     * digits, throwing away all except the first three.
+     *
+     * @return a formatter for HH:mm:ss.SSS
+     */
+    public static DateTimeFormatter hourMinuteSecondFraction() {
+        return Constants.hmsf;
+    }
+
+    /**
+     * Returns a formatter that combines a full date and two digit hour of
+     * day. (yyyy-MM-dd'T'HH)
+     *
+     * @return a formatter for yyyy-MM-dd'T'HH
+     */
+    public static DateTimeFormatter dateHour() {
+        return Constants.dh;
+    }
+
+    /**
+     * Returns a formatter that combines a full date, two digit hour of day,
+     * and two digit minute of hour. (yyyy-MM-dd'T'HH:mm)
+     *
+     * @return a formatter for yyyy-MM-dd'T'HH:mm
+     */
+    public static DateTimeFormatter dateHourMinute() {
+        return Constants.dhm;
+    }
+
+    /**
+     * Returns a formatter that combines a full date, two digit hour of day,
+     * two digit minute of hour, and two digit second of
+     * minute. (yyyy-MM-dd'T'HH:mm:ss)
+     *
+     * @return a formatter for yyyy-MM-dd'T'HH:mm:ss
+     */
+    public static DateTimeFormatter dateHourMinuteSecond() {
+        return Constants.dhms;
+    }
+
+    /**
+     * Returns 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). Parsing will parse up
+     * to 3 fractional second digits.
+     *
+     * @return a formatter for yyyy-MM-dd'T'HH:mm:ss.SSS
+     */
+    public static DateTimeFormatter dateHourMinuteSecondMillis() {
+        return Constants.dhmsl;
+    }
+
+    /**
+     * Returns 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). Parsing will parse up
+     * to 9 fractional second digits, throwing away all except the first three.
+     *
+     * @return a formatter for yyyy-MM-dd'T'HH:mm:ss.SSS
+     */
+    public static DateTimeFormatter dateHourMinuteSecondFraction() {
+        return Constants.dhmsf;
+    }
+
+    //-----------------------------------------------------------------------
+    static final class Constants {
+        private static final DateTimeFormatter
+                ye = yearElement(),  // year element (yyyy)
+                mye = monthElement(), // monthOfYear element (-MM)
+                dme = dayOfMonthElement(), // dayOfMonth element (-dd)
+                we = weekyearElement(),  // weekyear element (xxxx)
+                wwe = weekElement(), // weekOfWeekyear element (-ww)
+                dwe = dayOfWeekElement(), // dayOfWeek element (-ee)
+                dye = dayOfYearElement(), // dayOfYear element (-DDD)
+                hde = hourElement(), // hourOfDay element (HH)
+                mhe = minuteElement(), // minuteOfHour element (:mm)
+                sme = secondElement(), // secondOfMinute element (:ss)
+                fse = fractionElement(), // fractionOfSecond element (.SSSSSSSSS)
+                ze = offsetElement(),  // zone offset element
+                lte = literalTElement(), // literal 'T' element
+
+        //y,   // year (same as year element)
+        ym = yearMonth(),  // year month
+                ymd = yearMonthDay(), // year month day
+
+        //w,   // weekyear (same as weekyear element)
+        ww = weekyearWeek(),  // weekyear week
+                wwd = weekyearWeekDay(), // weekyear week day
+
+        //h,    // hour (same as hour element)
+        hm = hourMinute(),   // hour minute
+                hms = hourMinuteSecond(),  // hour minute second
+                hmsl = hourMinuteSecondMillis(), // hour minute second millis
+                hmsf = hourMinuteSecondFraction(), // hour minute second fraction
+
+        dh = dateHour(),    // date hour
+                dhm = dateHourMinute(),   // date hour minute
+                dhms = dateHourMinuteSecond(),  // date hour minute second
+                dhmsl = dateHourMinuteSecondMillis(), // date hour minute second millis
+                dhmsf = dateHourMinuteSecondFraction(), // date hour minute second fraction
+
+        //d,  // date (same as ymd)
+        t = time(),  // time
+                tx = timeNoMillis(),  // time no millis
+                tt = tTime(),  // Ttime
+                ttx = tTimeNoMillis(),  // Ttime no millis
+                dt = dateTime(), // date time
+                dtx = dateTimeNoMillis(), // date time no millis
+
+        //wd,  // week date (same as wwd)
+        wdt = weekDateTime(), // week date time
+                wdtx = weekDateTimeNoMillis(), // week date time no millis
+
+        od = ordinalDate(),  // ordinal date (same as yd)
+                odt = ordinalDateTime(), // ordinal date time
+                odtx = ordinalDateTimeNoMillis(), // ordinal date time no millis
+
+        bd = basicDate(),  // basic date
+                bt = basicTime(),  // basic time
+                btx = basicTimeNoMillis(),  // basic time no millis
+                btt = basicTTime(), // basic Ttime
+                bttx = basicTTimeNoMillis(), // basic Ttime no millis
+                bdt = basicDateTime(), // basic date time
+                bdtx = basicDateTimeNoMillis(), // basic date time no millis
+
+        bod = basicOrdinalDate(),  // basic ordinal date
+                bodt = basicOrdinalDateTime(), // basic ordinal date time
+                bodtx = basicOrdinalDateTimeNoMillis(), // basic ordinal date time no millis
+
+        bwd = basicWeekDate(),  // basic week date
+                bwdt = basicWeekDateTime(), // basic week date time
+                bwdtx = basicWeekDateTimeNoMillis(), // basic week date time no millis
+
+        dpe = dateElementParser(), // date parser element
+                tpe = timeElementParser(), // time parser element
+                dp = dateParser(),  // date parser
+                ldp = localDateParser(), // local date parser
+                tp = timeParser(),  // time parser
+                ltp = localTimeParser(), // local time parser
+                dtp = dateTimeParser(), // date time parser
+                dotp = dateOptionalTimeParser(), // date optional time parser
+                ldotp = localDateOptionalTimeParser(); // local date optional time parser
+
+        //-----------------------------------------------------------------------
+        private static DateTimeFormatter dateParser() {
+            if (dp == null) {
+                DateTimeParser tOffset = new DateTimeFormatterBuilder()
+                        .appendLiteral('T')
+                        .append(offsetElement()).toParser();
+                return new DateTimeFormatterBuilder()
+                        .append(dateElementParser())
+                        .appendOptional(tOffset)
+                        .toFormatter();
+            }
+            return dp;
+        }
+
+        private static DateTimeFormatter localDateParser() {
+            if (ldp == null) {
+                return dateElementParser().withZoneUTC();
+            }
+            return ldp;
+        }
+
+        private static DateTimeFormatter dateElementParser() {
+            if (dpe == null) {
+                return new DateTimeFormatterBuilder()
+                        .append(null, new DateTimeParser[] {
+                                new DateTimeFormatterBuilder()
+                                        .append(yearElement())
+                                        .appendOptional
+                                                (new DateTimeFormatterBuilder()
+                                                        .append(monthElement())
+                                                        .appendOptional(dayOfMonthElement().getParser())
+                                                        .toParser())
+                                        .toParser(),
+                                new DateTimeFormatterBuilder()
+                                        .append(weekyearElement())
+                                        .append(weekElement())
+                                        .appendOptional(dayOfWeekElement().getParser())
+                                        .toParser(),
+                                new DateTimeFormatterBuilder()
+                                        .append(yearElement())
+                                        .append(dayOfYearElement())
+                                        .toParser()
+                        })
+                        .toFormatter();
+            }
+            return dpe;
+        }
+
+        private static DateTimeFormatter timeParser() {
+            if (tp == null) {
+                return new DateTimeFormatterBuilder()
+                        .appendOptional(literalTElement().getParser())
+                        .append(timeElementParser())
+                        .appendOptional(offsetElement().getParser())
+                        .toFormatter();
+            }
+            return tp;
+        }
+
+        private static DateTimeFormatter localTimeParser() {
+            if (ltp == null) {
+                return new DateTimeFormatterBuilder()
+                        .appendOptional(literalTElement().getParser())
+                        .append(timeElementParser())
+                        .toFormatter().withZoneUTC();
+            }
+            return ltp;
+        }
+
+        private static DateTimeFormatter timeElementParser() {
+            if (tpe == null) {
+                // Decimal point can be either '.' or ','
+                DateTimeParser decimalPoint = new DateTimeFormatterBuilder()
+                        .append(null, new DateTimeParser[] {
+                                new DateTimeFormatterBuilder()
+                                        .appendLiteral('.')
+                                        .toParser(),
+                                new DateTimeFormatterBuilder()
+                                        .appendLiteral(',')
+                                        .toParser()
+                        })
+                        .toParser();
+
+                return new DateTimeFormatterBuilder()
+                        // time-element
+                        .append(hourElement())
+                        .append
+                                (null, new DateTimeParser[] {
+                                        new DateTimeFormatterBuilder()
+                                                // minute-element
+                                                .append(minuteElement())
+                                                .append
+                                                        (null, new DateTimeParser[] {
+                                                                new DateTimeFormatterBuilder()
+                                                                        // second-element
+                                                                        .append(secondElement())
+                                                                                // second fraction
+                                                                        .appendOptional(new DateTimeFormatterBuilder()
+                                                                                .append(decimalPoint)
+                                                                                .appendFractionOfSecond(1, 9)
+                                                                                .toParser())
+                                                                        .toParser(),
+                                                                // minute fraction
+                                                                new DateTimeFormatterBuilder()
+                                                                        .append(decimalPoint)
+                                                                        .appendFractionOfMinute(1, 9)
+                                                                        .toParser(),
+                                                                null
+                                                        })
+                                                .toParser(),
+                                        // hour fraction
+                                        new DateTimeFormatterBuilder()
+                                                .append(decimalPoint)
+                                                .appendFractionOfHour(1, 9)
+                                                .toParser(),
+                                        null
+                                })
+                        .toFormatter();
+            }
+            return tpe;
+        }
+
+        private static DateTimeFormatter dateTimeParser() {
+            if (dtp == null) {
+                // This is different from the general time parser in that the 'T'
+                // is required.
+                DateTimeParser time = new DateTimeFormatterBuilder()
+                        .appendLiteral('T')
+                        .append(timeElementParser())
+                        .appendOptional(offsetElement().getParser())
+                        .toParser();
+                return new DateTimeFormatterBuilder()
+                        .append(null, new DateTimeParser[] {time, dateOptionalTimeParser().getParser()})
+                        .toFormatter();
+            }
+            return dtp;
+        }
+
+        private static DateTimeFormatter dateOptionalTimeParser() {
+            if (dotp == null) {
+                DateTimeParser timeOrOffset = new DateTimeFormatterBuilder()
+                        .appendLiteral('T')
+                        .appendOptional(timeElementParser().getParser())
+                        .appendOptional(offsetElement().getParser())
+                        .toParser();
+                return new DateTimeFormatterBuilder()
+                        .append(dateElementParser())
+                        .appendOptional(timeOrOffset)
+                        .toFormatter();
+            }
+            return dotp;
+        }
+
+        private static DateTimeFormatter localDateOptionalTimeParser() {
+            if (ldotp == null) {
+                DateTimeParser time = new DateTimeFormatterBuilder()
+                        .appendLiteral('T')
+                        .append(timeElementParser())
+                        .toParser();
+                return new DateTimeFormatterBuilder()
+                        .append(dateElementParser())
+                        .appendOptional(time)
+                        .toFormatter().withZoneUTC();
+            }
+            return ldotp;
+        }
+
+        //-----------------------------------------------------------------------
+        private static DateTimeFormatter time() {
+            if (t == null) {
+                return new DateTimeFormatterBuilder()
+                        .append(hourMinuteSecondFraction())
+                        .append(offsetElement())
+                        .toFormatter();
+            }
+            return t;
+        }
+
+        private static DateTimeFormatter timeNoMillis() {
+            if (tx == null) {
+                return new DateTimeFormatterBuilder()
+                        .append(hourMinuteSecond())
+                        .append(offsetElement())
+                        .toFormatter();
+            }
+            return tx;
+        }
+
+        private static DateTimeFormatter tTime() {
+            if (tt == null) {
+                return new DateTimeFormatterBuilder()
+                        .append(literalTElement())
+                        .append(time())
+                        .toFormatter();
+            }
+            return tt;
+        }
+
+        private static DateTimeFormatter tTimeNoMillis() {
+            if (ttx == null) {
+                return new DateTimeFormatterBuilder()
+                        .append(literalTElement())
+                        .append(timeNoMillis())
+                        .toFormatter();
+            }
+            return ttx;
+        }
+
+        private static DateTimeFormatter dateTime() {
+            if (dt == null) {
+                return new DateTimeFormatterBuilder()
+                        .append(date())
+                        .append(tTime())
+                        .toFormatter();
+            }
+            return dt;
+        }
+
+        private static DateTimeFormatter dateTimeNoMillis() {
+            if (dtx == null) {
+                return new DateTimeFormatterBuilder()
+                        .append(date())
+                        .append(tTimeNoMillis())
+                        .toFormatter();
+            }
+            return dtx;
+        }
+
+        private static DateTimeFormatter ordinalDate() {
+            if (od == null) {
+                return new DateTimeFormatterBuilder()
+                        .append(yearElement())
+                        .append(dayOfYearElement())
+                        .toFormatter();
+            }
+            return od;
+        }
+
+        private static DateTimeFormatter ordinalDateTime() {
+            if (odt == null) {
+                return new DateTimeFormatterBuilder()
+                        .append(ordinalDate())
+                        .append(tTime())
+                        .toFormatter();
+            }
+            return odt;
+        }
+
+        private static DateTimeFormatter ordinalDateTimeNoMillis() {
+            if (odtx == null) {
+                return new DateTimeFormatterBuilder()
+                        .append(ordinalDate())
+                        .append(tTimeNoMillis())
+                        .toFormatter();
+            }
+            return odtx;
+        }
+
+        private static DateTimeFormatter weekDateTime() {
+            if (wdt == null) {
+                return new DateTimeFormatterBuilder()
+                        .append(weekDate())
+                        .append(tTime())
+                        .toFormatter();
+            }
+            return wdt;
+        }
+
+        private static DateTimeFormatter weekDateTimeNoMillis() {
+            if (wdtx == null) {
+                return new DateTimeFormatterBuilder()
+                        .append(weekDate())
+                        .append(tTimeNoMillis())
+                        .toFormatter();
+            }
+            return wdtx;
+        }
+
+        //-----------------------------------------------------------------------
+        private static DateTimeFormatter basicDate() {
+            if (bd == null) {
+                return new DateTimeFormatterBuilder()
+                        .appendYear(4, 4)
+                        .appendFixedDecimal(DateTimeFieldType.monthOfYear(), 2)
+                        .appendFixedDecimal(DateTimeFieldType.dayOfMonth(), 2)
+                        .toFormatter();
+            }
+            return bd;
+        }
+
+        private static DateTimeFormatter basicTime() {
+            if (bt == null) {
+                return new DateTimeFormatterBuilder()
+                        .appendFixedDecimal(DateTimeFieldType.hourOfDay(), 2)
+                        .appendFixedDecimal(DateTimeFieldType.minuteOfHour(), 2)
+                        .appendFixedDecimal(DateTimeFieldType.secondOfMinute(), 2)
+                        .appendLiteral('.')
+                        .appendFractionOfSecond(3, 9)
+                        .appendTimeZoneOffset("Z", false, 2, 2)
+                        .toFormatter();
+            }
+            return bt;
+        }
+
+        private static DateTimeFormatter basicTimeNoMillis() {
+            if (btx == null) {
+                return new DateTimeFormatterBuilder()
+                        .appendFixedDecimal(DateTimeFieldType.hourOfDay(), 2)
+                        .appendFixedDecimal(DateTimeFieldType.minuteOfHour(), 2)
+                        .appendFixedDecimal(DateTimeFieldType.secondOfMinute(), 2)
+                        .appendTimeZoneOffset("Z", false, 2, 2)
+                        .toFormatter();
+            }
+            return btx;
+        }
+
+        private static DateTimeFormatter basicTTime() {
+            if (btt == null) {
+                return new DateTimeFormatterBuilder()
+                        .append(literalTElement())
+                        .append(basicTime())
+                        .toFormatter();
+            }
+            return btt;
+        }
+
+        private static DateTimeFormatter basicTTimeNoMillis() {
+            if (bttx == null) {
+                return new DateTimeFormatterBuilder()
+                        .append(literalTElement())
+                        .append(basicTimeNoMillis())
+                        .toFormatter();
+            }
+            return bttx;
+        }
+
+        private static DateTimeFormatter basicDateTime() {
+            if (bdt == null) {
+                return new DateTimeFormatterBuilder()
+                        .append(basicDate())
+                        .append(basicTTime())
+                        .toFormatter();
+            }
+            return bdt;
+        }
+
+        private static DateTimeFormatter basicDateTimeNoMillis() {
+            if (bdtx == null) {
+                return new DateTimeFormatterBuilder()
+                        .append(basicDate())
+                        .append(basicTTimeNoMillis())
+                        .toFormatter();
+            }
+            return bdtx;
+        }
+
+        private static DateTimeFormatter basicOrdinalDate() {
+            if (bod == null) {
+                return new DateTimeFormatterBuilder()
+                        .appendYear(4, 4)
+                        .appendFixedDecimal(DateTimeFieldType.dayOfYear(), 3)
+                        .toFormatter();
+            }
+            return bod;
+        }
+
+        private static DateTimeFormatter basicOrdinalDateTime() {
+            if (bodt == null) {
+                return new DateTimeFormatterBuilder()
+                        .append(basicOrdinalDate())
+                        .append(basicTTime())
+                        .toFormatter();
+            }
+            return bodt;
+        }
+
+        private static DateTimeFormatter basicOrdinalDateTimeNoMillis() {
+            if (bodtx == null) {
+                return new DateTimeFormatterBuilder()
+                        .append(basicOrdinalDate())
+                        .append(basicTTimeNoMillis())
+                        .toFormatter();
+            }
+            return bodtx;
+        }
+
+        private static DateTimeFormatter basicWeekDate() {
+            if (bwd == null) {
+                return new DateTimeFormatterBuilder()
+                        .appendFixedSignedDecimal(DateTimeFieldType.weekyear(), 4) // ES change, was .appendWeekyear(4, 4)
+                        .appendLiteral('W')
+                        .appendFixedDecimal(DateTimeFieldType.weekOfWeekyear(), 2)
+                        .appendFixedDecimal(DateTimeFieldType.dayOfWeek(), 1)
+                        .toFormatter();
+            }
+            return bwd;
+        }
+
+        private static DateTimeFormatter basicWeekDateTime() {
+            if (bwdt == null) {
+                return new DateTimeFormatterBuilder()
+                        .append(basicWeekDate())
+                        .append(basicTTime())
+                        .toFormatter();
+            }
+            return bwdt;
+        }
+
+        private static DateTimeFormatter basicWeekDateTimeNoMillis() {
+            if (bwdtx == null) {
+                return new DateTimeFormatterBuilder()
+                        .append(basicWeekDate())
+                        .append(basicTTimeNoMillis())
+                        .toFormatter();
+            }
+            return bwdtx;
+        }
+
+        //-----------------------------------------------------------------------
+        private static DateTimeFormatter yearMonth() {
+            if (ym == null) {
+                return new DateTimeFormatterBuilder()
+                        .append(yearElement())
+                        .append(monthElement())
+                        .toFormatter();
+            }
+            return ym;
+        }
+
+        private static DateTimeFormatter yearMonthDay() {
+            if (ymd == null) {
+                return new DateTimeFormatterBuilder()
+                        .append(yearElement())
+                        .append(monthElement())
+                        .append(dayOfMonthElement())
+                        .toFormatter();
+            }
+            return ymd;
+        }
+
+        private static DateTimeFormatter weekyearWeek() {
+            if (ww == null) {
+                return new DateTimeFormatterBuilder()
+                        .append(weekyearElement())
+                        .append(weekElement())
+                        .toFormatter();
+            }
+            return ww;
+        }
+
+        private static DateTimeFormatter weekyearWeekDay() {
+            if (wwd == null) {
+                return new DateTimeFormatterBuilder()
+                        .append(weekyearElement())
+                        .append(weekElement())
+                        .append(dayOfWeekElement())
+                        .toFormatter();
+            }
+            return wwd;
+        }
+
+        private static DateTimeFormatter hourMinute() {
+            if (hm == null) {
+                return new DateTimeFormatterBuilder()
+                        .append(hourElement())
+                        .append(minuteElement())
+                        .toFormatter();
+            }
+            return hm;
+        }
+
+        private static DateTimeFormatter hourMinuteSecond() {
+            if (hms == null) {
+                return new DateTimeFormatterBuilder()
+                        .append(hourElement())
+                        .append(minuteElement())
+                        .append(secondElement())
+                        .toFormatter();
+            }
+            return hms;
+        }
+
+        private static DateTimeFormatter hourMinuteSecondMillis() {
+            if (hmsl == null) {
+                return new DateTimeFormatterBuilder()
+                        .append(hourElement())
+                        .append(minuteElement())
+                        .append(secondElement())
+                        .appendLiteral('.')
+                        .appendFractionOfSecond(3, 3)
+                        .toFormatter();
+            }
+            return hmsl;
+        }
+
+        private static DateTimeFormatter hourMinuteSecondFraction() {
+            if (hmsf == null) {
+                return new DateTimeFormatterBuilder()
+                        .append(hourElement())
+                        .append(minuteElement())
+                        .append(secondElement())
+                        .append(fractionElement())
+                        .toFormatter();
+            }
+            return hmsf;
+        }
+
+        private static DateTimeFormatter dateHour() {
+            if (dh == null) {
+                return new DateTimeFormatterBuilder()
+                        .append(date())
+                        .append(literalTElement())
+                        .append(hour())
+                        .toFormatter();
+            }
+            return dh;
+        }
+
+        private static DateTimeFormatter dateHourMinute() {
+            if (dhm == null) {
+                return new DateTimeFormatterBuilder()
+                        .append(date())
+                        .append(literalTElement())
+                        .append(hourMinute())
+                        .toFormatter();
+            }
+            return dhm;
+        }
+
+        private static DateTimeFormatter dateHourMinuteSecond() {
+            if (dhms == null) {
+                return new DateTimeFormatterBuilder()
+                        .append(date())
+                        .append(literalTElement())
+                        .append(hourMinuteSecond())
+                        .toFormatter();
+            }
+            return dhms;
+        }
+
+        private static DateTimeFormatter dateHourMinuteSecondMillis() {
+            if (dhmsl == null) {
+                return new DateTimeFormatterBuilder()
+                        .append(date())
+                        .append(literalTElement())
+                        .append(hourMinuteSecondMillis())
+                        .toFormatter();
+            }
+            return dhmsl;
+        }
+
+        private static DateTimeFormatter dateHourMinuteSecondFraction() {
+            if (dhmsf == null) {
+                return new DateTimeFormatterBuilder()
+                        .append(date())
+                        .append(literalTElement())
+                        .append(hourMinuteSecondFraction())
+                        .toFormatter();
+            }
+            return dhmsf;
+        }
+
+        //-----------------------------------------------------------------------
+        private static DateTimeFormatter yearElement() {
+            if (ye == null) {
+                return new DateTimeFormatterBuilder()
+                        .appendFixedSignedDecimal(DateTimeFieldType.year(), 4) // ES change, was .appendYear(4, 9)
+                        .toFormatter();
+            }
+            return ye;
+        }
+
+        private static DateTimeFormatter monthElement() {
+            if (mye == null) {
+                return new DateTimeFormatterBuilder()
+                        .appendLiteral('-')
+                        .appendFixedSignedDecimal(DateTimeFieldType.monthOfYear(), 2) // ES change, was .appendMonthOfYear(2)
+                        .toFormatter();
+            }
+            return mye;
+        }
+
+        private static DateTimeFormatter dayOfMonthElement() {
+            if (dme == null) {
+                return new DateTimeFormatterBuilder()
+                        .appendLiteral('-')
+                        .appendFixedSignedDecimal(DateTimeFieldType.dayOfMonth(), 2) // ES change, was .appendDayOfMonth(2)
+                        .toFormatter();
+            }
+            return dme;
+        }
+
+        private static DateTimeFormatter weekyearElement() {
+            if (we == null) {
+                return new DateTimeFormatterBuilder()
+                        .appendFixedSignedDecimal(DateTimeFieldType.weekyear(), 4) // ES change, was .appendWeekyear(4, 9)
+                        .toFormatter();
+            }
+            return we;
+        }
+
+        private static DateTimeFormatter weekElement() {
+            if (wwe == null) {
+                return new DateTimeFormatterBuilder()
+                        .appendLiteral("-W")
+                        .appendFixedSignedDecimal(DateTimeFieldType.weekOfWeekyear(), 2) // ES change, was .appendWeekOfWeekyear(2)
+                        .toFormatter();
+            }
+            return wwe;
+        }
+
+        private static DateTimeFormatter dayOfWeekElement() {
+            if (dwe == null) {
+                return new DateTimeFormatterBuilder()
+                        .appendLiteral('-')
+                        .appendDayOfWeek(1)
+                        .toFormatter();
+            }
+            return dwe;
+        }
+
+        private static DateTimeFormatter dayOfYearElement() {
+            if (dye == null) {
+                return new DateTimeFormatterBuilder()
+                        .appendLiteral('-')
+                        .appendFixedSignedDecimal(DateTimeFieldType.dayOfYear(), 3) // ES change, was .appendDayOfYear(3)
+                        .toFormatter();
+            }
+            return dye;
+        }
+
+        private static DateTimeFormatter literalTElement() {
+            if (lte == null) {
+                return new DateTimeFormatterBuilder()
+                        .appendLiteral('T')
+                        .toFormatter();
+            }
+            return lte;
+        }
+
+        private static DateTimeFormatter hourElement() {
+            if (hde == null) {
+                return new DateTimeFormatterBuilder()
+                        .appendFixedSignedDecimal(DateTimeFieldType.hourOfDay(), 2) // ES change, was .appendHourOfDay(2)
+                        .toFormatter();
+            }
+            return hde;
+        }
+
+        private static DateTimeFormatter minuteElement() {
+            if (mhe == null) {
+                return new DateTimeFormatterBuilder()
+                        .appendLiteral(':')
+                        .appendFixedSignedDecimal(DateTimeFieldType.minuteOfHour(), 2) // ES change, was .appendMinuteOfHour(2)
+                        .toFormatter();
+            }
+            return mhe;
+        }
+
+        private static DateTimeFormatter secondElement() {
+            if (sme == null) {
+                return new DateTimeFormatterBuilder()
+                        .appendLiteral(':')
+                        .appendFixedSignedDecimal(DateTimeFieldType.secondOfMinute(), 2) // ES change, was .appendSecondOfMinute(2)
+                        .toFormatter();
+            }
+            return sme;
+        }
+
+        private static DateTimeFormatter fractionElement() {
+            if (fse == null) {
+                return new DateTimeFormatterBuilder()
+                        .appendLiteral('.')
+                                // Support parsing up to nanosecond precision even though
+                                // those extra digits will be dropped.
+                        .appendFractionOfSecond(3, 9)
+                        .toFormatter();
+            }
+            return fse;
+        }
+
+        private static DateTimeFormatter offsetElement() {
+            if (ze == null) {
+                return new DateTimeFormatterBuilder()
+                        .appendTimeZoneOffset("Z", true, 2, 4)
+                        .toFormatter();
+            }
+            return ze;
+        }
+
+    }
+
+}

+ 365 - 0
core/src/test/java/org/elasticsearch/deps/joda/SimpleJodaTests.java

@@ -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");
+
+        // yyyyMMdd’T'HHmmss.SSSZ
+        assertValidDateFormatParsing("basicDateTime", "20140303T124343.123Z");
+        assertValidDateFormatParsing("basicDateTime", "00050303T124343.123Z");
+        assertDateFormatParsingThrowingException("basicDateTime", "50303T124343.123Z");
+
+        // yyyyMMdd’T'HHmmssZ
+        assertValidDateFormatParsing("basicDateTimeNoMillis", "20140303T124343Z");
+        assertValidDateFormatParsing("basicDateTimeNoMillis", "00050303T124343Z");
+        assertDateFormatParsingThrowingException("basicDateTimeNoMillis", "50303T124343Z");
+
+        // yyyyDDD
+        assertValidDateFormatParsing("basicOrdinalDate", "0005165");
+        assertDateFormatParsingThrowingException("basicOrdinalDate", "5165");
+
+        // yyyyDDD’T'HHmmss.SSSZ
+        assertValidDateFormatParsing("basicOrdinalDateTime", "0005165T124343.123Z");
+        assertValidDateFormatParsing("basicOrdinalDateTime", "0005165T124343.123Z");
+        assertDateFormatParsingThrowingException("basicOrdinalDateTime", "5165T124343.123Z");
+
+        // yyyyDDD’T'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");
+
+        // 'T’HHmmss.SSSZ
+        assertValidDateFormatParsing("basicTTime", "T090909.123Z");
+        assertDateFormatParsingThrowingException("basicTTime", "T90909.123Z");
+
+        // T’HHmmssZ
+        assertValidDateFormatParsing("basicTTimeNoMillis", "T090909Z");
+        assertDateFormatParsingThrowingException("basicTTimeNoMillis", "T90909Z");
+
+        // xxxx’W'wwe
+        assertValidDateFormatParsing("basicWeekDate", "0005W414");
+        assertValidDateFormatParsing("basicWeekDate", "5W414", "0005W414");
+        assertDateFormatParsingThrowingException("basicWeekDate", "5W14");
+
+        assertValidDateFormatParsing("strictBasicWeekDate", "0005W414");
+        assertDateFormatParsingThrowingException("strictBasicWeekDate", "0005W47");
+        assertDateFormatParsingThrowingException("strictBasicWeekDate", "5W414");
+        assertDateFormatParsingThrowingException("strictBasicWeekDate", "5W14");
+
+        // xxxx’W'wwe’T'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");
+
+        // xxxx’W'wwe’T'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-dd’T'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-dd’T'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-dd’T'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-DDD’T'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-DDD’T'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");
+
+        // 'T’HH: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");
+
+        // 'T’HH: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-'W’ww-e
+        assertValidDateFormatParsing("weekDate", "0005-W4-1", "0005-W04-1");
+        assertValidDateFormatParsing("strictWeekDate", "0005-W04-1");
+        assertDateFormatParsingThrowingException("strictWeekDate", "0005-W4-1");
+
+        // xxxx-'W’ww-e’T'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-'W’ww-e’T'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);
     }

+ 93 - 1
core/src/test/java/org/elasticsearch/index/mapper/date/SimpleDateMappingTests.java

@@ -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));
+    }
 }

+ 5 - 11
core/src/test/java/org/elasticsearch/index/mapper/timestamp/TimestampMappingTests.java

@@ -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 {

+ 3 - 3
core/src/test/java/org/elasticsearch/search/aggregations/bucket/DateHistogramTests.java

@@ -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")

+ 4 - 3
core/src/test/java/org/elasticsearch/search/functionscore/DecayFunctionScoreTests.java

@@ -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();

+ 45 - 25
docs/reference/mapping/date-format.asciidoc

@@ -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

+ 1 - 1
docs/reference/mapping/fields/timestamp-field.asciidoc

@@ -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]

+ 1 - 1
docs/reference/mapping/types/core-types.asciidoc

@@ -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,

+ 1 - 1
docs/reference/mapping/types/root-object-type.asciidoc

@@ -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

+ 8 - 1
docs/reference/migration/migrate_2_0.asciidoc

@@ -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.